Merge branch 'next' into tests/LoginPageTest
This commit is contained in:
@@ -22,3 +22,4 @@ yarn-error.log
|
|||||||
/_data
|
/_data
|
||||||
.rnd
|
.rnd
|
||||||
/.ssh
|
/.ssh
|
||||||
|
.ignition.json
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ APP_KEY=
|
|||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
APP_PORT=8000
|
APP_PORT=8000
|
||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
SSH_MUX_ENABLED=false
|
SSH_MUX_ENABLED=true
|
||||||
|
|
||||||
# Enable Laravel Telescope for debugging
|
# Enable Laravel Telescope for debugging
|
||||||
TELESCOPE_ENABLED=false
|
TELESCOPE_ENABLED=false
|
||||||
@@ -27,11 +27,7 @@ DB_PORT=5432
|
|||||||
# Set to true to enable Ray
|
# Set to true to enable Ray
|
||||||
RAY_ENABLED=false
|
RAY_ENABLED=false
|
||||||
# Set custom ray port
|
# Set custom ray port
|
||||||
RAY_PORT=
|
# RAY_PORT=
|
||||||
|
|
||||||
# Clockwork Configuration
|
|
||||||
CLOCKWORK_ENABLED=false
|
|
||||||
CLOCKWORK_QUEUE_COLLECT=true
|
|
||||||
|
|
||||||
# Enable Laravel Telescope for debugging
|
# Enable Laravel Telescope for debugging
|
||||||
TELESCOPE_ENABLED=false
|
TELESCOPE_ENABLED=false
|
||||||
|
|||||||
65
.github/ISSUE_TEMPLATE/01_BUG_REPORT.yml
vendored
Normal file
65
.github/ISSUE_TEMPLATE/01_BUG_REPORT.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
name: 🐞 Bug Report
|
||||||
|
description: "File a new bug report."
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: ["🐛 Bug", "🔍 Triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **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).
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Error Message and Logs
|
||||||
|
description: Provide a detailed description of the error or exception you encountered, along with any relevant log output.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Steps to Reproduce
|
||||||
|
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
|
||||||
|
attributes:
|
||||||
|
label: Example Repository URL
|
||||||
|
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:
|
||||||
|
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/02_ENHANCEMENT_BOUNTY.yml
vendored
Normal file
31
.github/ISSUE_TEMPLATE/02_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
|
||||||
46
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
46
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
@@ -1,46 +0,0 @@
|
|||||||
name: Bug report
|
|
||||||
description: "Create a new bug report."
|
|
||||||
title: "[Bug]: "
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: >-
|
|
||||||
# 💎 Bounty program (with
|
|
||||||
[algora.io](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
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: A clear and concise description of the problem
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Minimal Reproduction (if possible, example repository)
|
|
||||||
description: Please provide a step by step guide to reproduce the issue.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Exception or Error
|
|
||||||
description: Please provide error logs if possible.
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: Version
|
|
||||||
description: Coolify's version (see top of your screen).
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Cloud?
|
|
||||||
description: "Are you using the cloud version of Coolify?"
|
|
||||||
options:
|
|
||||||
- label: 'Yes'
|
|
||||||
required: false
|
|
||||||
- label: 'No'
|
|
||||||
required: false
|
|
||||||
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/new-features
|
- 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.
|
||||||
|
|||||||
14
.github/pull_request_template.md
vendored
14
.github/pull_request_template.md
vendored
@@ -1 +1,13 @@
|
|||||||
> Always use `next` branch as destination branch for PRs, not `main`
|
## Submit Checklist (REMOVE THIS SECTION BEFORE SUBMITTING)
|
||||||
|
- [ ] I have selected the `next` branch as the destination for my PR, not `main`.
|
||||||
|
- [ ] I have listed all changes in the `Changes` section.
|
||||||
|
- [ ] I have filled out the `Issues` section with the issue/discussion link(s) (if applicable).
|
||||||
|
- [ ] I have tested my changes.
|
||||||
|
- [ ] I have considered backwards compatibility.
|
||||||
|
- [ ] I have removed this checklist and any unused sections.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
-
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
- fix #
|
||||||
|
|||||||
17
.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml
vendored
Normal file
17
.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Lock closed Issues, Discussions, and PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 1 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lock-threads:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Lock threads after 30 days of inactivity
|
||||||
|
uses: dessant/lock-threads@v5
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
issue-inactive-days: '30'
|
||||||
|
pr-inactive-days: '30'
|
||||||
|
discussion-inactive-days: '30'
|
||||||
28
.github/workflows/chore-manage-stale-issues-and-prs.yml
vendored
Normal file
28
.github/workflows/chore-manage-stale-issues-and-prs.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
name: Manage Stale Issues and PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 2 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
manage-stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Manage stale issues and PRs
|
||||||
|
uses: actions/stale@v9
|
||||||
|
id: stale
|
||||||
|
with:
|
||||||
|
stale-issue-message: 'This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information.'
|
||||||
|
stale-pr-message: 'This pull request will be automatically closed in a few days if no response is received. Please update your PR or comment if you would like to continue working on it.'
|
||||||
|
close-issue-message: 'This issue has been automatically closed due to inactivity.'
|
||||||
|
close-pr-message: 'This pull request has been automatically closed due to inactivity.'
|
||||||
|
days-before-stale: 14
|
||||||
|
days-before-close: 7
|
||||||
|
stale-issue-label: '⏱︎ Stale'
|
||||||
|
stale-pr-label: '⏱︎ Stale'
|
||||||
|
only-labels: '💤 Waiting for feedback'
|
||||||
|
remove-stale-when-updated: true
|
||||||
|
operations-per-run: 100
|
||||||
|
labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback'
|
||||||
|
close-issue-reason: 'not_planned'
|
||||||
|
exempt-all-milestones: false
|
||||||
78
.github/workflows/chore-remove-labels-and-assignees-on-close.yml
vendored
Normal file
78
.github/workflows/chore-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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
.github/workflows/coolify-helper-next.yml
vendored
89
.github/workflows/coolify-helper-next.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Coolify Helper Image Development (v4)
|
name: Coolify Helper Image Development
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -8,7 +8,8 @@ on:
|
|||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
DOCKER_REGISTRY: docker.io
|
||||||
IMAGE_NAME: "coollabsio/coolify-helper"
|
IMAGE_NAME: "coollabsio/coolify-helper"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -19,25 +20,36 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
|
||||||
context: .
|
context: .
|
||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
|
||||||
labels: |
|
labels: |
|
||||||
coolify.managed=true
|
coolify.managed=true
|
||||||
aarch64:
|
aarch64:
|
||||||
@@ -47,27 +59,39 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
|
||||||
context: .
|
context: .
|
||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
|
||||||
labels: |
|
labels: |
|
||||||
coolify.managed=true
|
coolify.managed=true
|
||||||
|
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -75,25 +99,42 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
needs: [ amd64, aarch64 ]
|
needs: [ amd64, aarch64 ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
- uses: docker/setup-buildx-action@v3
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- name: Create & publish manifest
|
|
||||||
|
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||||
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
92
.github/workflows/coolify-helper.yml
vendored
92
.github/workflows/coolify-helper.yml
vendored
@@ -1,14 +1,15 @@
|
|||||||
name: Coolify Helper Image (v4)
|
name: Coolify Helper Image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "next" ]
|
branches: [ "main" ]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/coolify-helper.yml
|
- .github/workflows/coolify-helper.yml
|
||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
DOCKER_REGISTRY: docker.io
|
||||||
IMAGE_NAME: "coollabsio/coolify-helper"
|
IMAGE_NAME: "coollabsio/coolify-helper"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -19,25 +20,36 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
|
||||||
context: .
|
context: .
|
||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||||
labels: |
|
labels: |
|
||||||
coolify.managed=true
|
coolify.managed=true
|
||||||
aarch64:
|
aarch64:
|
||||||
@@ -47,25 +59,36 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
|
||||||
context: .
|
context: .
|
||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||||
labels: |
|
labels: |
|
||||||
coolify.managed=true
|
coolify.managed=true
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
@@ -75,26 +98,45 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
needs: [ amd64, aarch64 ]
|
needs: [ amd64, aarch64 ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up QEMU
|
- uses: docker/setup-buildx-action@v3
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Set up Docker Buildx
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
- name: Create & publish manifest
|
|
||||||
|
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||||
|
|
||||||
|
|||||||
143
.github/workflows/coolify-production-build.yml
vendored
Normal file
143
.github/workflows/coolify-production-build.yml
vendored
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
name: Production Build (v4)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
paths-ignore:
|
||||||
|
- .github/workflows/coolify-helper.yml
|
||||||
|
- .github/workflows/coolify-helper-next.yml
|
||||||
|
- .github/workflows/coolify-realtime.yml
|
||||||
|
- .github/workflows/coolify-realtime-next.yml
|
||||||
|
- docker/coolify-helper/Dockerfile
|
||||||
|
- docker/coolify-realtime/Dockerfile
|
||||||
|
- docker/testing-host/Dockerfile
|
||||||
|
- templates/service-templates.json
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
DOCKER_REGISTRY: docker.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT]
|
||||||
|
|
||||||
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/prod/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
|
||||||
|
aarch64:
|
||||||
|
runs-on: [self-hosted, arm64]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT]
|
||||||
|
|
||||||
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/prod/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs: [amd64, aarch64]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||||
146
.github/workflows/coolify-realtime-next.yml
vendored
Normal file
146
.github/workflows/coolify-realtime-next.yml
vendored
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
name: Coolify Realtime Development
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "next" ]
|
||||||
|
paths:
|
||||||
|
- .github/workflows/coolify-realtime-next.yml
|
||||||
|
- docker/coolify-realtime/Dockerfile
|
||||||
|
- docker/coolify-realtime/terminal-server.js
|
||||||
|
- docker/coolify-realtime/package.json
|
||||||
|
- docker/coolify-realtime/soketi-entrypoint.sh
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
DOCKER_REGISTRY: docker.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 ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-realtime/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
|
||||||
|
aarch64:
|
||||||
|
runs-on: [ self-hosted, arm64 ]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: 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 and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-realtime/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs: [ amd64, aarch64 ]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||||
|
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
146
.github/workflows/coolify-realtime.yml
vendored
Normal file
146
.github/workflows/coolify-realtime.yml
vendored
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
name: Coolify Realtime
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
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:
|
||||||
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
DOCKER_REGISTRY: docker.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 ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-realtime/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
|
||||||
|
aarch64:
|
||||||
|
runs-on: [ self-hosted, arm64 ]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: 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 and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-realtime/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs: [ amd64, aarch64 ]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||||
129
.github/workflows/coolify-staging-build.yml
vendored
Normal file
129
.github/workflows/coolify-staging-build.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
name: Staging Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore: ["main", "v3"]
|
||||||
|
paths-ignore:
|
||||||
|
- .github/workflows/coolify-helper.yml
|
||||||
|
- .github/workflows/coolify-helper-next.yml
|
||||||
|
- .github/workflows/coolify-realtime.yml
|
||||||
|
- .github/workflows/coolify-realtime-next.yml
|
||||||
|
- docker/coolify-helper/Dockerfile
|
||||||
|
- docker/coolify-realtime/Dockerfile
|
||||||
|
- docker/testing-host/Dockerfile
|
||||||
|
- templates/service-templates.json
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
DOCKER_REGISTRY: docker.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/prod/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
|
||||||
|
aarch64:
|
||||||
|
runs-on: [self-hosted, arm64]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/prod/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs: [amd64, aarch64]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||||
|
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
92
.github/workflows/coolify-testing-host.yml
vendored
92
.github/workflows/coolify-testing-host.yml
vendored
@@ -1,14 +1,15 @@
|
|||||||
name: Coolify Testing Host (v4-non-prod)
|
name: Coolify Testing Host
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "next" ]
|
branches: [ "next" ]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/coolify-testing-host.yml
|
- .github/workflows/coolify-testing-host.yml
|
||||||
- docker/testing-host/Dockerfile
|
- docker/testing-host/Dockerfile
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
DOCKER_REGISTRY: docker.io
|
||||||
IMAGE_NAME: "coollabsio/coolify-testing-host"
|
IMAGE_NAME: "coollabsio/coolify-testing-host"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -19,21 +20,34 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
|
||||||
context: .
|
context: .
|
||||||
file: docker/testing-host/Dockerfile
|
file: docker/testing-host/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
|
||||||
aarch64:
|
aarch64:
|
||||||
runs-on: [ self-hosted, arm64 ]
|
runs-on: [ self-hosted, arm64 ]
|
||||||
permissions:
|
permissions:
|
||||||
@@ -41,21 +55,34 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
|
||||||
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and Push Image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
no-cache: true
|
|
||||||
context: .
|
context: .
|
||||||
file: docker/testing-host/Dockerfile
|
file: docker/testing-host/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
tags: |
|
||||||
|
${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
||||||
|
${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
||||||
|
labels: |
|
||||||
|
coolify.managed=true
|
||||||
|
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -63,21 +90,36 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
needs: [ amd64, aarch64 ]
|
needs: [ amd64, aarch64 ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up QEMU
|
- uses: docker/setup-buildx-action@v3
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Set up Docker Buildx
|
- name: Login to ${{ env.GITHUB_REGISTRY }}
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.GITHUB_REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Create & publish manifest
|
|
||||||
|
- name: Login to ${{ env.DOCKER_REGISTRY }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.DOCKER_REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 \
|
||||||
|
--tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
|
- name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 \
|
||||||
|
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
79
.github/workflows/development-build.yml
vendored
79
.github/workflows/development-build.yml
vendored
@@ -1,79 +0,0 @@
|
|||||||
name: Development Build (v4)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches-ignore: ["main", "v3"]
|
|
||||||
paths-ignore:
|
|
||||||
- .github/workflows/coolify-helper.yml
|
|
||||||
- docker/coolify-helper/Dockerfile
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
IMAGE_NAME: "coollabsio/coolify"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
amd64:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/prod/Dockerfile
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
|
||||||
aarch64:
|
|
||||||
runs-on: [self-hosted, arm64]
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/prod/Dockerfile
|
|
||||||
platforms: linux/aarch64
|
|
||||||
push: true
|
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
|
|
||||||
merge-manifest:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
needs: [amd64, aarch64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Create & publish manifest
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
|
||||||
44
.github/workflows/docker-image.yml
vendored
44
.github/workflows/docker-image.yml
vendored
@@ -1,44 +0,0 @@
|
|||||||
name: Docker Image CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
# push:
|
|
||||||
# branches: [ "main" ]
|
|
||||||
# pull_request:
|
|
||||||
# branches: [ "*" ]
|
|
||||||
push:
|
|
||||||
branches: ["this-does-not-exist"]
|
|
||||||
pull_request:
|
|
||||||
branches: ["this-does-not-exist"]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
/usr/local/share/ca-certificates
|
|
||||||
/var/cache/apt/archives
|
|
||||||
/var/lib/apt/lists
|
|
||||||
~/.cache
|
|
||||||
key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-docker-
|
|
||||||
- name: Build the Docker image
|
|
||||||
run: |
|
|
||||||
cp .env.example .env
|
|
||||||
docker run --rm -u "$(id -u):$(id -g)" \
|
|
||||||
-v "$(pwd):/app" \
|
|
||||||
-w /app composer:2 \
|
|
||||||
composer install --ignore-platform-reqs
|
|
||||||
./vendor/bin/spin build
|
|
||||||
- name: Start the stack
|
|
||||||
run: |
|
|
||||||
./vendor/bin/spin up -d
|
|
||||||
./vendor/bin/spin exec coolify php artisan key:generate
|
|
||||||
./vendor/bin/spin exec coolify php artisan migrate:fresh --seed
|
|
||||||
- name: Test (missing E2E tests)
|
|
||||||
run: |
|
|
||||||
./vendor/bin/spin exec coolify php artisan test
|
|
||||||
25
.github/workflows/fix-php-code-style-issues
vendored
25
.github/workflows/fix-php-code-style-issues
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Fix PHP code style issues
|
|
||||||
|
|
||||||
on: [push]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
php-code-styling:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.head_ref }}
|
|
||||||
|
|
||||||
- name: Fix PHP code style issues
|
|
||||||
uses: aglipanci/laravel-pint-action@2.4
|
|
||||||
|
|
||||||
- name: Commit changes
|
|
||||||
uses: stefanzweifel/git-auto-commit-action@v5
|
|
||||||
with:
|
|
||||||
commit_message: Fix styling
|
|
||||||
93
.github/workflows/pr-build.yml
vendored
93
.github/workflows/pr-build.yml
vendored
@@ -1,93 +0,0 @@
|
|||||||
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 }}
|
|
||||||
89
.github/workflows/production-build.yml
vendored
89
.github/workflows/production-build.yml
vendored
@@ -1,89 +0,0 @@
|
|||||||
name: Production Build (v4)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["main"]
|
|
||||||
paths-ignore:
|
|
||||||
- .github/workflows/coolify-helper.yml
|
|
||||||
- docker/coolify-helper/Dockerfile
|
|
||||||
- templates/service-templates.json
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
IMAGE_NAME: "coollabsio/coolify"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
amd64:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Get Version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/prod/Dockerfile
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
|
||||||
aarch64:
|
|
||||||
runs-on: [self-hosted, arm64]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Get Version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
|
||||||
- name: Build image and push to registry
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: docker/prod/Dockerfile
|
|
||||||
platforms: linux/aarch64
|
|
||||||
push: true
|
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
|
||||||
merge-manifest:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
needs: [amd64, aarch64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Get Version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
|
||||||
- name: Create & publish manifest
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -32,3 +32,4 @@ _ide_helper_models.php
|
|||||||
.rnd
|
.rnd
|
||||||
/.ssh
|
/.ssh
|
||||||
scripts/load-test/*
|
scripts/load-test/*
|
||||||
|
.ignition.json
|
||||||
|
|||||||
120
CONTRIBUTING.md
120
CONTRIBUTING.md
@@ -12,9 +12,10 @@ You can ask for guidance anytime on our [Discord server](https://coollabs.io/dis
|
|||||||
4. [Set up Environment Variables](#4-set-up-environment-variables)
|
4. [Set up Environment Variables](#4-set-up-environment-variables)
|
||||||
5. [Start Coolify](#5-start-coolify)
|
5. [Start Coolify](#5-start-coolify)
|
||||||
6. [Start Development](#6-start-development)
|
6. [Start Development](#6-start-development)
|
||||||
7. [Development Notes](#7-development-notes)
|
7. [Create a Pull Request](#7-create-a-pull-request)
|
||||||
8. [Create a Pull Request](#8-create-a-pull-request)
|
8. [Development Notes](#development-notes)
|
||||||
9. [Additional Contribution Guidelines](#additional-contribution-guidelines)
|
9. [Resetting Development Environment](#resetting-development-environment)
|
||||||
|
10. [Additional Contribution Guidelines](#additional-contribution-guidelines)
|
||||||
|
|
||||||
## 1. Setup Development Environment
|
## 1. Setup Development Environment
|
||||||
|
|
||||||
@@ -25,15 +26,15 @@ Follow the steps below for your operating system:
|
|||||||
|
|
||||||
1. Install `docker-ce`, Docker Desktop (or similar):
|
1. Install `docker-ce`, Docker Desktop (or similar):
|
||||||
- Docker CE (recommended):
|
- 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)
|
- Install Windows Subsystem for Linux v2 (WSL2) by following this guide: [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install?ref=coolify)
|
||||||
- After installing WSL2, install Docker CE for your Linux distribution by following this guide: [Install Docker Engine](https://docs.docker.com/engine/install/)
|
- After installing WSL2, install Docker CE for your Linux distribution by following this guide: [Install Docker Engine](https://docs.docker.com/engine/install/?ref=coolify)
|
||||||
- Make sure to choose the appropriate Linux distribution (e.g., Ubuntu) when following the Docker installation guide
|
- Make sure to choose the appropriate Linux distribution (e.g., Ubuntu) when following the Docker installation guide
|
||||||
- Install Docker Desktop (easier):
|
- Install Docker Desktop (easier):
|
||||||
- Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)
|
- Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/?ref=coolify)
|
||||||
- Ensure WSL2 backend is enabled in Docker Desktop settings
|
- Ensure WSL2 backend is enabled in Docker Desktop settings
|
||||||
|
|
||||||
2. Install Spin:
|
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)
|
- 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?ref=coolify)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -42,12 +43,12 @@ Follow the steps below for your operating system:
|
|||||||
|
|
||||||
1. Install Orbstack, Docker Desktop (or similar):
|
1. Install Orbstack, Docker Desktop (or similar):
|
||||||
- Orbstack (recommended, as it is a faster and lighter alternative to Docker Desktop):
|
- Orbstack (recommended, as it is a faster and lighter alternative to Docker Desktop):
|
||||||
- Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation)
|
- Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation?ref=coolify)
|
||||||
- Docker Desktop:
|
- Docker Desktop:
|
||||||
- Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/)
|
- Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/?ref=coolify)
|
||||||
|
|
||||||
2. Install Spin:
|
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)
|
- 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?ref=coolify)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -56,12 +57,12 @@ Follow the steps below for your operating system:
|
|||||||
|
|
||||||
1. Install Docker Engine, Docker Desktop (or similar):
|
1. Install Docker Engine, Docker Desktop (or similar):
|
||||||
- Docker Engine (recommended, as there is no VM overhead):
|
- 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
|
- Follow the official [Docker Engine installation guide](https://docs.docker.com/engine/install/?ref=coolify) for your Linux distribution
|
||||||
- Docker Desktop:
|
- Docker Desktop:
|
||||||
- If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/)
|
- If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/?ref=coolify)
|
||||||
|
|
||||||
2. Install Spin:
|
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)
|
- 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?ref=coolify)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -85,14 +86,14 @@ After installing Docker (or Orbstack) and Spin, verify the installation:
|
|||||||
|
|
||||||
| Editor | Platform | Download Link |
|
| Editor | Platform | Download Link |
|
||||||
|--------|----------|---------------|
|
|--------|----------|---------------|
|
||||||
| Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download) |
|
| Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download?ref=coolify) |
|
||||||
| Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/) |
|
| Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/?ref=coolify) |
|
||||||
| Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download) |
|
| Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download?ref=coolify) |
|
||||||
|
|
||||||
3. Clone the Coolify Repository from your fork to your local machine
|
3. Clone the Coolify Repository from your fork to your local machine
|
||||||
- Use `git clone` in the command line, or
|
- Use `git clone` in the command line, or
|
||||||
- Use GitHub Desktop (recommended):
|
- Use GitHub Desktop (recommended):
|
||||||
- Download and install from [https://desktop.github.com/](https://desktop.github.com/)
|
- Download and install from [https://desktop.github.com/](https://desktop.github.com/?ref=coolify)
|
||||||
- Open GitHub Desktop and login with your GitHub account
|
- 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`
|
- 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`
|
||||||
|
|
||||||
@@ -145,7 +146,36 @@ After installing Docker (or Orbstack) and Spin, verify the installation:
|
|||||||
> TELESCOPE_ENABLED=true
|
> TELESCOPE_ENABLED=true
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
## 7. Development Notes
|
## 7. 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.
|
||||||
|
- Use the Pull Request Template provided and fill in the details.
|
||||||
|
|
||||||
|
> [!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.
|
||||||
|
|
||||||
|
## Development Notes
|
||||||
|
|
||||||
When working on Coolify, keep the following in mind:
|
When working on Coolify, keep the following in mind:
|
||||||
|
|
||||||
@@ -164,35 +194,41 @@ When working on Coolify, keep the following in mind:
|
|||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches.
|
> 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
|
## Resetting Development Environment
|
||||||
|
|
||||||
1. After making changes or adding a new service:
|
If you encounter issues or break your database or something else, follow these steps to start from a clean slate (works since `v4.0.0-beta.342`):
|
||||||
- Commit your changes to your forked repository.
|
|
||||||
- Push the changes to your GitHub account.
|
|
||||||
|
|
||||||
2. Creating the Pull Request (PR):
|
1. Stop all running containers `ctrl + c`.
|
||||||
- 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:
|
2. Remove all Coolify containers:
|
||||||
- Give your PR a descriptive title.
|
```bash
|
||||||
- In the description, explain the changes you've made.
|
docker rm coolify coolify-db coolify-redis coolify-realtime coolify-testing-host coolify-minio coolify-vite-1 coolify-mail
|
||||||
- Reference any related issues by using keywords like "Fixes #123" or "Closes #456".
|
```
|
||||||
|
|
||||||
|
3. Remove Coolify volumes (it is possible that the volumes have no `coolify` prefix on your machine, in that case remove the prefix from the command):
|
||||||
|
```bash
|
||||||
|
docker volume rm coolify_dev_backups_data coolify_dev_postgres_data coolify_dev_redis_data coolify_dev_coolify_data coolify_dev_minio_data
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Remove unused images:
|
||||||
|
```bash
|
||||||
|
docker image prune -a
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Start Coolify again:
|
||||||
|
```bash
|
||||||
|
spin up
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Run database migrations and seeders:
|
||||||
|
```bash
|
||||||
|
docker exec -it coolify php artisan migrate:fresh --seed
|
||||||
|
```
|
||||||
|
|
||||||
|
After completing these steps, you'll have a fresh development setup.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
|
> Always run database migrations and seeders after switching branches or pulling updates to ensure your local database structure matches the current codebase and includes necessary seed data.
|
||||||
|
|
||||||
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
|
## Additional Contribution Guidelines
|
||||||
|
|
||||||
|
|||||||
113
RELEASE.md
113
RELEASE.md
@@ -2,35 +2,120 @@
|
|||||||
|
|
||||||
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed.
|
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Release Process](#release-process)
|
||||||
|
- [Version Types](#version-types)
|
||||||
|
- [Stable](#stable)
|
||||||
|
- [Nightly](#nightly)
|
||||||
|
- [Beta](#beta)
|
||||||
|
- [Version Availability](#version-availability)
|
||||||
|
- [Self-Hosted](#self-hosted)
|
||||||
|
- [Cloud](#cloud)
|
||||||
|
- [Manually Update to Specific Versions](#manually-update-to-specific-versions)
|
||||||
|
|
||||||
## Release Process
|
## Release Process
|
||||||
|
|
||||||
1. **Development on `next` or separate branches**
|
1. **Development on `next` or Feature Branches**
|
||||||
- Changes, fixes and new features are developed on the `next` or even separate branches.
|
- Improvements, fixes, and new features are developed on the `next` branch or separate feature branches.
|
||||||
|
|
||||||
2. **Merging to `main`**
|
2. **Merging to `main`**
|
||||||
- Once changes are ready, they are merged from `next` into the `main` branch.
|
- Once ready, changes are merged from the `next` branch into the `main` branch.
|
||||||
|
|
||||||
3. **Building the release**
|
3. **Building the Release**
|
||||||
- After merging to `main`, a new release is built.
|
- After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry with the version tag and the `latest` tag.
|
||||||
- Note: A push to `main` does not automatically mean a new version is released.
|
|
||||||
|
|
||||||
4. **Creating a GitHub release**
|
4. **Creating a GitHub Release**
|
||||||
- A new release is created on GitHub with the new version details.
|
- A new GitHub release is manually created with details of the changes made in the version.
|
||||||
|
|
||||||
5. **Updating the CDN**
|
5. **Updating the CDN**
|
||||||
- The final step is updating the version information on the CDN:
|
- To make a new version publicly available, the version information on the CDN needs to be updated: [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json)
|
||||||
[https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json)
|
|
||||||
|
|
||||||
> [!NOTE]
|
> [!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.
|
> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated.**
|
||||||
|
|
||||||
|
## Version Types
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Stable (coming soon)</strong></summary>
|
||||||
|
|
||||||
|
- **Stable**
|
||||||
|
- The production version suitable for stable, production environments (generally recommended).
|
||||||
|
- **Update Frequency:** Every 2 to 4 weeks, with more frequent possible hotfixes.
|
||||||
|
- **Release Size:** Larger but less frequent releases. Multiple nightly versions are consolidated into a single stable release.
|
||||||
|
- **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`).
|
||||||
|
- **Installation Command:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Nightly</strong></summary>
|
||||||
|
|
||||||
|
- **Nightly**
|
||||||
|
- The latest development version, suitable for testing the latest changes and experimenting with new features.
|
||||||
|
- **Update Frequency:** Daily or bi-weekly updates.
|
||||||
|
- **Release Size:** Smaller, more frequent releases.
|
||||||
|
- **Versioning Scheme:** TO BE DETERMINED
|
||||||
|
- **Installation Command:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify-nightly/install.sh | bash -s next
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Beta</strong></summary>
|
||||||
|
|
||||||
|
- **Beta**
|
||||||
|
- Test releases for the upcoming stable version.
|
||||||
|
- **Purpose:** Allows users to test and provide feedback on new features and changes before they become stable.
|
||||||
|
- **Update Frequency:** Available if we think beta testing is necessary.
|
||||||
|
- **Release Size:** Same size as stable release as it will become the next stabe release after some time.
|
||||||
|
- **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`).
|
||||||
|
- **Installation Command:**
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Do not use nightly/beta builds in production as there is no guarantee of stability.
|
||||||
|
|
||||||
## Version Availability
|
## 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).
|
When a new version is released and a new GitHub release is created, it doesn't immediately become available for your instance. Here's how version availability works for different instance types.
|
||||||
|
|
||||||
|
### Self-Hosted
|
||||||
|
|
||||||
|
- **Update Frequency:** More frequent updates, especially on the nightly release channel.
|
||||||
|
- **Update Availability:** New versions are available once the CDN has been updated.
|
||||||
|
- **Update Methods:**
|
||||||
|
1. **Manual Update in Instance Settings:**
|
||||||
|
- Go to `Settings > Update Check Frequency` and click the `Check Manually` button.
|
||||||
|
- If an update is available, an upgrade button will appear on the sidebar.
|
||||||
|
2. **Automatic Update:**
|
||||||
|
- If enabled, the instance will update automatically at the time set in the settings.
|
||||||
|
3. **Re-run Installation Script:**
|
||||||
|
- Run the installation script again to upgrade to the latest version available on the CDN:
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!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.
|
> If a new release is available on GitHub but your instance hasn't updated yet or no upgrade button is shown in the UI, the CDN might not have been updated yet. This intentional delay ensures stability and allows for hotfixes before official release.
|
||||||
|
|
||||||
|
### Cloud
|
||||||
|
|
||||||
|
- **Update Frequency:** Less frequent as it's a managed service.
|
||||||
|
- **Update Availability:** New versions are available once Andras has updated the cloud version manually.
|
||||||
|
- **Update Method:**
|
||||||
|
- Updates are managed by Andras, who ensures each cloud version is thoroughly tested and stable before releasing it.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> The cloud version of Coolify may be several versions behind the latest GitHub releases even if the CDN is updated. This is intentional to ensure stability and reliability for cloud users and Andras will manully update the cloud version when the update is ready.
|
||||||
|
|
||||||
## Manually Update to Specific Versions
|
## Manually Update to Specific Versions
|
||||||
|
|
||||||
@@ -42,4 +127,4 @@ To update your Coolify instance to a specific (unreleased) version, use the foll
|
|||||||
```bash
|
```bash
|
||||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s <version>
|
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`).
|
Replace `<version>` with the version you want to update to (for example `4.0.0-beta.332`).
|
||||||
|
|||||||
17
app/Actions/Application/GenerateConfig.php
Normal file
17
app/Actions/Application/GenerateConfig.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class GenerateConfig
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Application $application, bool $is_json = false)
|
||||||
|
{
|
||||||
|
ray()->clearAll();
|
||||||
|
return $application->generateConfig(is_json: $is_json);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Application;
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use App\Actions\Server\CleanupDocker;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
@@ -9,44 +10,35 @@ class StopApplication
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Application $application, bool $previewDeployments = false)
|
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 'Server is not functional';
|
||||||
|
}
|
||||||
|
ray('Stopping application: '.$application->name);
|
||||||
|
|
||||||
|
if ($server->isSwarm()) {
|
||||||
|
instant_remote_process(["docker stack rm {$application->uuid}"], $server);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$servers = collect([]);
|
$containersToStop = $application->getContainersToStop($previewDeployments);
|
||||||
$servers->push($application->destination->server);
|
$application->stopContainers($containersToStop, $server);
|
||||||
$application->additional_servers->map(function ($server) use ($servers) {
|
|
||||||
$servers->push($server);
|
|
||||||
});
|
|
||||||
foreach ($servers as $server) {
|
|
||||||
if (! $server->isFunctional()) {
|
|
||||||
return 'Server is not functional';
|
|
||||||
}
|
|
||||||
if ($previewDeployments) {
|
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, includePullrequests: true);
|
|
||||||
} else {
|
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
|
||||||
}
|
|
||||||
if ($containers->count() > 0) {
|
|
||||||
foreach ($containers as $container) {
|
|
||||||
$containerName = data_get($container, 'Names');
|
|
||||||
if ($containerName) {
|
|
||||||
instant_remote_process(command: ["docker stop --time=30 $containerName"], server: $server, throwError: false);
|
|
||||||
instant_remote_process(command: ["docker rm $containerName"], server: $server, throwError: false);
|
|
||||||
instant_remote_process(command: ["docker rm -f {$containerName}"], server: $server, throwError: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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::dispatch($server, true);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
|
||||||
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Actions\CoolifyTask;
|
|||||||
|
|
||||||
use App\Enums\ActivityTypes;
|
use App\Enums\ActivityTypes;
|
||||||
use App\Enums\ProcessStatus;
|
use App\Enums\ProcessStatus;
|
||||||
|
use App\Helpers\SshMultiplexingHelper;
|
||||||
use App\Jobs\ApplicationDeploymentJob;
|
use App\Jobs\ApplicationDeploymentJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Process\ProcessResult;
|
use Illuminate\Process\ProcessResult;
|
||||||
@@ -137,7 +138,7 @@ class RunRemoteProcess
|
|||||||
$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)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class StartDragonfly
|
|||||||
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
|
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -46,9 +46,6 @@ class StartDragonfly
|
|||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network,
|
$this->database->destination->network,
|
||||||
],
|
],
|
||||||
'ulimits' => [
|
|
||||||
'memlock' => '-1',
|
|
||||||
],
|
|
||||||
'labels' => [
|
'labels' => [
|
||||||
'coolify.managed' => 'true',
|
'coolify.managed' => 'true',
|
||||||
],
|
],
|
||||||
@@ -75,7 +72,7 @@ class StartDragonfly
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -118,10 +115,10 @@ class StartDragonfly
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +149,7 @@ class StartDragonfly
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class StartKeydb
|
|||||||
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
|
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -74,7 +74,7 @@ class StartKeydb
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -94,10 +94,10 @@ class StartKeydb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->keydb_conf) || !empty($this->database->keydb_conf)) {
|
if (! is_null($this->database->keydb_conf) || ! empty($this->database->keydb_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/keydb.conf',
|
'source' => $this->configuration_dir.'/keydb.conf',
|
||||||
'target' => '/etc/keydb/keydb.conf',
|
'target' => '/etc/keydb/keydb.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
@@ -125,10 +125,10 @@ class StartKeydb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ class StartKeydb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class StartMariadb
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -69,7 +69,7 @@ class StartMariadb
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -89,10 +89,10 @@ class StartMariadb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) {
|
if (! is_null($this->database->mariadb_conf) || ! empty($this->database->mariadb_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/custom-config.cnf',
|
'source' => $this->configuration_dir.'/custom-config.cnf',
|
||||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
@@ -120,10 +120,10 @@ class StartMariadb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,18 +154,18 @@ class StartMariadb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class StartMongodb
|
|||||||
$startCommand = 'mongod';
|
$startCommand = 'mongod';
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -77,7 +77,7 @@ class StartMongodb
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -97,19 +97,19 @@ class StartMongodb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) {
|
if (! is_null($this->database->mongo_conf) || ! empty($this->database->mongo_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/mongod.conf',
|
'source' => $this->configuration_dir.'/mongod.conf',
|
||||||
'target' => '/etc/mongo/mongod.conf',
|
'target' => '/etc/mongo/mongod.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
|
$docker_compose['services'][$container_name]['command'] = $startCommand.' --config /etc/mongo/mongod.conf';
|
||||||
}
|
}
|
||||||
$this->add_default_database();
|
$this->add_default_database();
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
|
'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d',
|
||||||
'target' => '/docker-entrypoint-initdb.d',
|
'target' => '/docker-entrypoint-initdb.d',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
@@ -136,10 +136,10 @@ class StartMongodb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,15 +170,15 @@ class StartMongodb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class StartMysql
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -69,7 +69,7 @@ class StartMysql
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -89,10 +89,10 @@ class StartMysql
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) {
|
if (! is_null($this->database->mysql_conf) || ! empty($this->database->mysql_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/custom-config.cnf',
|
'source' => $this->configuration_dir.'/custom-config.cnf',
|
||||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
@@ -120,10 +120,10 @@ class StartMysql
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,18 +154,18 @@ class StartMysql
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class StartPostgresql
|
|||||||
$this->generate_init_scripts();
|
$this->generate_init_scripts();
|
||||||
$this->add_custom_conf();
|
$this->add_custom_conf();
|
||||||
|
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class StartRedis
|
|||||||
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -78,7 +78,7 @@ class StartRedis
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (!is_null($this->database->limits_cpuset)) {
|
if (! is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -98,10 +98,10 @@ class StartRedis
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) {
|
if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/redis.conf',
|
'source' => $this->configuration_dir.'/redis.conf',
|
||||||
'target' => '/usr/local/etc/redis/redis.conf',
|
'target' => '/usr/local/etc/redis/redis.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
@@ -130,10 +130,10 @@ class StartRedis
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ class StartRedis
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,25 +11,65 @@ use App\Models\StandaloneMongodb;
|
|||||||
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(command: ["docker stop --time=30 $database->uuid"], server: $server, throwError: false);
|
$this->stopContainer($database, $database->uuid, 300);
|
||||||
instant_remote_process(command: ["docker rm $database->uuid"], server: $server, throwError: false);
|
if (! $isDeleteOperation) {
|
||||||
instant_remote_process(command: ["docker rm -f $database->uuid"], server: $server, throwError: false);
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::dispatch($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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,7 +543,7 @@ class GetContainersStatus
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$exitedServices = $exitedServices->unique('id');
|
$exitedServices = $exitedServices->unique('uuid');
|
||||||
foreach ($exitedServices as $exitedService) {
|
foreach ($exitedServices as $exitedService) {
|
||||||
if (str($exitedService->status)->startsWith('exited')) {
|
if (str($exitedService->status)->startsWith('exited')) {
|
||||||
continue;
|
continue;
|
||||||
@@ -651,8 +651,9 @@ class GetContainersStatus
|
|||||||
// $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
|
if (! $this->server->proxySet() || $this->server->proxy->force_stop) {
|
||||||
$this->server->proxyType();
|
return;
|
||||||
|
}
|
||||||
$foundProxyContainer = $this->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';
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Actions\Fortify;
|
namespace App\Actions\Fortify;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
@@ -20,7 +19,7 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
*/
|
*/
|
||||||
public function create(array $input): User
|
public function create(array $input): User
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
if (! $settings->is_registration_enabled) {
|
if (! $settings->is_registration_enabled) {
|
||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
@@ -48,7 +47,7 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
$team = $user->teams()->first();
|
$team = $user->teams()->first();
|
||||||
|
|
||||||
// Disable registration after first user is created
|
// Disable registration after first user is created
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$settings->is_registration_enabled = false;
|
$settings->is_registration_enabled = false;
|
||||||
$settings->save();
|
$settings->save();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Actions\License;
|
namespace App\Actions\License;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ class CheckResaleLicense
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$settings->update([
|
$settings->update([
|
||||||
'is_resale_license_active' => true,
|
'is_resale_license_active' => true,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class CheckConfiguration
|
|||||||
];
|
];
|
||||||
$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();
|
||||||
}
|
}
|
||||||
if (! $proxy_configuration || is_null($proxy_configuration)) {
|
if (! $proxy_configuration || is_null($proxy_configuration)) {
|
||||||
throw new \Exception('Could not generate proxy configuration');
|
throw new \Exception('Could not generate proxy configuration');
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
|
|
||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class CheckProxy
|
class CheckProxy
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, $fromUI = false)
|
// It should return if the proxy should be started (true) or not (false)
|
||||||
|
public function handle(Server $server, $fromUI = false): bool
|
||||||
{
|
{
|
||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -26,7 +29,7 @@ class CheckProxy
|
|||||||
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);
|
||||||
}
|
}
|
||||||
@@ -62,23 +65,43 @@ class CheckProxy
|
|||||||
$ip = 'host.docker.internal';
|
$ip = 'host.docker.internal';
|
||||||
}
|
}
|
||||||
|
|
||||||
$connection80 = @fsockopen($ip, '80');
|
$portsToCheck = ['80', '443'];
|
||||||
$connection443 = @fsockopen($ip, '443');
|
|
||||||
$port80 = is_resource($connection80) && fclose($connection80);
|
try {
|
||||||
$port443 = is_resource($connection443) && fclose($connection443);
|
if ($server->proxyType() !== ProxyTypes::NONE->value) {
|
||||||
if ($port80) {
|
$proxyCompose = CheckConfiguration::run($server);
|
||||||
|
if (isset($proxyCompose)) {
|
||||||
|
$yaml = Yaml::parse($proxyCompose);
|
||||||
|
$portsToCheck = [];
|
||||||
|
if ($server->proxyType() === ProxyTypes::TRAEFIK->value) {
|
||||||
|
$ports = data_get($yaml, 'services.traefik.ports');
|
||||||
|
} elseif ($server->proxyType() === ProxyTypes::CADDY->value) {
|
||||||
|
$ports = data_get($yaml, 'services.caddy.ports');
|
||||||
|
}
|
||||||
|
if (isset($ports)) {
|
||||||
|
foreach ($ports as $port) {
|
||||||
|
$portsToCheck[] = str($port)->before(':')->value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$portsToCheck = [];
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
}
|
||||||
|
if (count($portsToCheck) === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach ($portsToCheck as $port) {
|
||||||
|
$connection = @fsockopen($ip, $port);
|
||||||
|
if (is_resource($connection) && fclose($connection)) {
|
||||||
if ($fromUI) {
|
if ($fromUI) {
|
||||||
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
throw new \Exception("Port $port is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($port443) {
|
|
||||||
if ($fromUI) {
|
|
||||||
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class StartProxy
|
|||||||
}
|
}
|
||||||
SaveConfiguration::run($server, $configuration);
|
SaveConfiguration::run($server, $configuration);
|
||||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||||
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
|
||||||
$server->save();
|
$server->save();
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
$commands = $commands->merge([
|
$commands = $commands->merge([
|
||||||
@@ -35,7 +35,7 @@ class StartProxy
|
|||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
'docker stack deploy -c docker-compose.yml coolify-proxy',
|
'docker stack deploy -c docker-compose.yml coolify-proxy',
|
||||||
"echo 'Proxy started successfully.'",
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$caddfile = 'import /dynamic/*.caddy';
|
$caddfile = 'import /dynamic/*.caddy';
|
||||||
@@ -46,11 +46,14 @@ class StartProxy
|
|||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Pulling docker image.'",
|
"echo 'Pulling docker image.'",
|
||||||
'docker compose pull',
|
'docker compose pull',
|
||||||
"echo 'Stopping existing coolify-proxy.'",
|
'if docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then',
|
||||||
'docker compose down -v --remove-orphans > /dev/null 2>&1',
|
" echo 'Stopping and removing existing coolify-proxy.'",
|
||||||
|
' docker rm -f coolify-proxy || true',
|
||||||
|
" echo 'Successfully stopped and removed existing coolify-proxy.'",
|
||||||
|
'fi',
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
'docker compose up -d --remove-orphans',
|
'docker compose up -d --remove-orphans',
|
||||||
"echo 'Proxy started successfully.'",
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -12,28 +11,29 @@ class CleanupDocker
|
|||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
|
$settings = instanceSettings();
|
||||||
|
$helperImageVersion = data_get($settings, 'helper_version');
|
||||||
|
$helperImage = config('coolify.helper_image');
|
||||||
|
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
||||||
|
|
||||||
$commands = $this->getCommands();
|
$commands = [
|
||||||
|
'docker container prune -f --filter "label=coolify.managed=true" --filter "label!=coolify.proxy=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 -f",
|
||||||
|
];
|
||||||
|
|
||||||
|
$serverSettings = $server->settings;
|
||||||
|
if ($serverSettings->delete_unused_volumes) {
|
||||||
|
$commands[] = 'docker volume prune -af';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($serverSettings->delete_unused_networks) {
|
||||||
|
$commands[] = 'docker network prune -f';
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($commands as $command) {
|
foreach ($commands as $command) {
|
||||||
instant_remote_process([$command], $server, false);
|
instant_remote_process([$command], $server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getCommands(): array
|
|
||||||
{
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
$helperImageVersion = data_get($settings, 'helper_version');
|
|
||||||
$helperImage = config('coolify.helper_image');
|
|
||||||
$helperImageWithVersion = config('coolify.helper_image').':'.$helperImageVersion;
|
|
||||||
|
|
||||||
$commonCommands = [
|
|
||||||
'docker container prune -f --filter "label=coolify.managed=true"',
|
|
||||||
'docker image prune -af --filter "label!=coolify.managed=true"',
|
|
||||||
'docker builder prune -af',
|
|
||||||
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi",
|
|
||||||
];
|
|
||||||
|
|
||||||
return $commonCommands;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Events\CloudflareTunnelConfigured;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
@@ -40,12 +41,17 @@ class ConfigureCloudflared
|
|||||||
instant_remote_process($commands, $server);
|
instant_remote_process($commands, $server);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e);
|
ray($e);
|
||||||
|
$server->settings->is_cloudflare_tunnel = false;
|
||||||
|
$server->settings->save();
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
|
CloudflareTunnelConfigured::dispatch($server->team_id);
|
||||||
|
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
'rm -fr /tmp/cloudflared',
|
'rm -fr /tmp/cloudflared',
|
||||||
]);
|
]);
|
||||||
instant_remote_process($commands, $server);
|
instant_remote_process($commands, $server);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
app/Actions/Server/DeleteServer.php
Normal file
17
app/Actions/Server/DeleteServer.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class DeleteServer
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
StopSentinel::run($server);
|
||||||
|
$server->forceDelete();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ class InstallDocker
|
|||||||
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.');
|
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.');
|
||||||
}
|
}
|
||||||
ray('Installing Docker on server: '.$server->name.' ('.$server->ip.')'.' with OS type: '.$supported_os_type);
|
ray('Installing Docker on server: '.$server->name.' ('.$server->ip.')'.' with OS type: '.$supported_os_type);
|
||||||
$dockerVersion = '24.0';
|
$dockerVersion = '26.0';
|
||||||
$config = base64_encode('{
|
$config = base64_encode('{
|
||||||
"log-driver": "json-file",
|
"log-driver": "json-file",
|
||||||
"log-opts": {
|
"log-opts": {
|
||||||
|
|||||||
@@ -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,18 +10,48 @@ class StartSentinel
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, $version = 'latest', bool $restart = false)
|
public function handle(Server $server, $version = 'next', bool $restart = false)
|
||||||
{
|
{
|
||||||
if ($restart) {
|
if ($restart) {
|
||||||
StopSentinel::run($server);
|
StopSentinel::run($server);
|
||||||
}
|
}
|
||||||
$metrics_history = $server->settings->metrics_history_days;
|
$metrics_history = data_get($server, 'settings.sentinel_metrics_history_days');
|
||||||
$refresh_rate = $server->settings->metrics_refresh_rate_seconds;
|
$refresh_rate = data_get($server, 'settings.sentinel_metrics_refresh_rate_seconds');
|
||||||
$token = $server->settings->metrics_token;
|
$push_interval = data_get($server, 'settings.sentinel_push_interval_seconds');
|
||||||
|
$token = data_get($server, 'settings.sentinel_token');
|
||||||
|
$endpoint = data_get($server, 'settings.sentinel_custom_url');
|
||||||
|
$mount_dir = '/data/coolify/sentinel';
|
||||||
|
$image = "ghcr.io/coollabsio/sentinel:$version";
|
||||||
|
if (! $endpoint) {
|
||||||
|
throw new \Exception('You should set FQDN in Instance Settings.');
|
||||||
|
}
|
||||||
|
$environments = [
|
||||||
|
'TOKEN' => $token,
|
||||||
|
'PUSH_ENDPOINT' => $endpoint,
|
||||||
|
'PUSH_INTERVAL_SECONDS' => $push_interval,
|
||||||
|
'COLLECTOR_ENABLED' => $server->isMetricsEnabled() ? 'true' : 'false',
|
||||||
|
'COLLECTOR_REFRESH_RATE_SECONDS' => $refresh_rate,
|
||||||
|
'COLLECTOR_RETENTION_PERIOD_DAYS' => $metrics_history,
|
||||||
|
];
|
||||||
|
if (isDev()) {
|
||||||
|
// data_set($environments, 'DEBUG', 'true');
|
||||||
|
$mount_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/sentinel';
|
||||||
|
// $image = 'sentinel';
|
||||||
|
}
|
||||||
|
$docker_environments = '-e "' . implode('" -e "', array_map(fn($key, $value) => "$key=$value", array_keys($environments), $environments)) . '"';
|
||||||
|
|
||||||
|
$docker_command = "docker run -d $docker_environments --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v $mount_dir:/app/db --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 --add-host=host.docker.internal:host-gateway $image";
|
||||||
|
|
||||||
instant_remote_process([
|
instant_remote_process([
|
||||||
"docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
|
'docker rm -f coolify-sentinel || true',
|
||||||
'chown -R 9999:root /data/coolify/metrics /data/coolify/logs',
|
"mkdir -p $mount_dir",
|
||||||
'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
|
$docker_command,
|
||||||
], $server, true);
|
"chown -R 9999:root $mount_dir",
|
||||||
|
"chmod -R 700 $mount_dir",
|
||||||
|
], $server);
|
||||||
|
|
||||||
|
$server->settings->is_sentinel_enabled = true;
|
||||||
|
$server->settings->save();
|
||||||
|
$server->sentinelHeartbeat();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopSentinel
|
class StopSentinel
|
||||||
@@ -12,5 +13,6 @@ class StopSentinel
|
|||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
|
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
|
||||||
|
$server->sentinelHeartbeat(isReset: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Jobs\PullHelperImageJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ class UpdateCoolify
|
|||||||
public function handle($manual_update = false)
|
public function handle($manual_update = false)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$this->server = Server::find(0);
|
$this->server = Server::find(0);
|
||||||
if (! $this->server) {
|
if (! $this->server) {
|
||||||
return;
|
return;
|
||||||
@@ -55,6 +55,13 @@ class UpdateCoolify
|
|||||||
|
|
||||||
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);
|
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
|
||||||
|
|
||||||
remote_process([
|
remote_process([
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use App\Actions\Server\CleanupDocker;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
@@ -9,11 +10,11 @@ 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 @@ class DeleteService
|
|||||||
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 @@ class DeleteService
|
|||||||
$task->delete();
|
$task->delete();
|
||||||
}
|
}
|
||||||
$service->tags()->detach();
|
$service->tags()->detach();
|
||||||
|
$service->forceDelete();
|
||||||
|
|
||||||
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::dispatch($server, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class StartService
|
|||||||
$service->saveComposeConfigs();
|
$service->saveComposeConfigs();
|
||||||
$commands[] = 'cd '.$service->workdir();
|
$commands[] = 'cd '.$service->workdir();
|
||||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||||
if($service->networks()->count() > 0){
|
if ($service->networks()->count() > 0) {
|
||||||
$commands[] = "echo 'Creating Docker network.'";
|
$commands[] = "echo 'Creating Docker network.'";
|
||||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ class StartService
|
|||||||
$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');
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use App\Actions\Server\CleanupDocker;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
@@ -9,40 +10,27 @@ 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) {
|
|
||||||
if ($applications->count() < 6) {
|
|
||||||
instant_remote_process(command: ["docker stop --time=10 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
|
||||||
}
|
|
||||||
instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
|
||||||
instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
|
||||||
$application->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
$dbs = $service->databases()->get();
|
|
||||||
foreach ($dbs as $db) {
|
|
||||||
if ($dbs->count() < 6) {
|
|
||||||
|
|
||||||
instant_remote_process(command: ["docker stop --time=10 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
$containersToStop = $service->getContainersToStop();
|
||||||
|
$service->stopContainers($containersToStop, $server);
|
||||||
|
|
||||||
|
if (! $isDeleteOperation) {
|
||||||
|
$service->delete_connected_networks($service->uuid);
|
||||||
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::dispatch($server, true);
|
||||||
}
|
}
|
||||||
instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
|
||||||
instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
|
||||||
$db->update(['status' => 'exited']);
|
|
||||||
}
|
}
|
||||||
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) {
|
} catch (\Exception $e) {
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
50
app/Console/Commands/CheckApplicationDeploymentQueue.php
Normal file
50
app/Console/Commands/CheckApplicationDeploymentQueue.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CheckApplicationDeploymentQueue extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'check:deployment-queue {--force} {--seconds=3600}';
|
||||||
|
|
||||||
|
protected $description = 'Check application deployment queue.';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$seconds = $this->option('seconds');
|
||||||
|
$deployments = ApplicationDeploymentQueue::whereIn('status', [
|
||||||
|
ApplicationDeploymentStatus::IN_PROGRESS,
|
||||||
|
ApplicationDeploymentStatus::QUEUED,
|
||||||
|
])->where('created_at', '<=', now()->subSeconds($seconds))->get();
|
||||||
|
if ($deployments->isEmpty()) {
|
||||||
|
$this->info('No deployments found in the last '.$seconds.' seconds.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Found '.$deployments->count().' deployments created in the last '.$seconds.' seconds.');
|
||||||
|
|
||||||
|
foreach ($deployments as $deployment) {
|
||||||
|
if ($this->option('force')) {
|
||||||
|
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
|
||||||
|
$this->cancelDeployment($deployment);
|
||||||
|
} else {
|
||||||
|
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
|
||||||
|
if ($this->confirm('Do you want to cancel this deployment?', true)) {
|
||||||
|
$this->cancelDeployment($deployment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cancelDeployment(ApplicationDeploymentQueue $deployment)
|
||||||
|
{
|
||||||
|
$deployment->update(['status' => ApplicationDeploymentStatus::FAILED]);
|
||||||
|
if ($deployment->server?->isFunctional()) {
|
||||||
|
remote_process(['docker rm -f '.$deployment->deployment_uuid], $deployment->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,9 +7,9 @@ use Illuminate\Console\Command;
|
|||||||
|
|
||||||
class CleanupApplicationDeploymentQueue extends Command
|
class CleanupApplicationDeploymentQueue extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
|
protected $signature = 'cleanup:deployment-queue {--team-id=}';
|
||||||
|
|
||||||
protected $description = 'CleanupApplicationDeploymentQueue';
|
protected $description = 'Cleanup application deployment queue.';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
|
|
||||||
class CleanupQueue extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'cleanup:queue';
|
|
||||||
|
|
||||||
protected $description = 'Cleanup Queue';
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
echo "Running queue cleanup...\n";
|
|
||||||
$prefix = config('database.redis.options.prefix');
|
|
||||||
$keys = Redis::connection()->keys('*:laravel*');
|
|
||||||
foreach ($keys as $key) {
|
|
||||||
$keyWithoutPrefix = str_replace($prefix, '', $key);
|
|
||||||
Redis::connection()->del($keyWithoutPrefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
app/Console/Commands/CleanupRedis.php
Normal file
31
app/Console/Commands/CleanupRedis.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
|
class CleanupRedis extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:redis';
|
||||||
|
|
||||||
|
protected $description = 'Cleanup Redis';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
echo "Cleanup Redis keys.\n";
|
||||||
|
$prefix = config('database.redis.options.prefix');
|
||||||
|
|
||||||
|
$keys = Redis::connection()->keys('*:laravel*');
|
||||||
|
collect($keys)->each(function ($key) use ($prefix) {
|
||||||
|
$keyWithoutPrefix = str_replace($prefix, '', $key);
|
||||||
|
Redis::connection()->del($keyWithoutPrefix);
|
||||||
|
});
|
||||||
|
|
||||||
|
$queueOverlaps = Redis::connection()->keys('*laravel-queue-overlap*');
|
||||||
|
collect($queueOverlaps)->each(function ($key) {
|
||||||
|
Redis::connection()->del($key);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
@@ -35,6 +38,27 @@ class CleanupStuckedResources extends Command
|
|||||||
private function cleanup_stucked_resources()
|
private function cleanup_stucked_resources()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
try {
|
||||||
|
$servers = Server::all()->filter(function ($server) {
|
||||||
|
return $server->isFunctional();
|
||||||
|
});
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
CleanupHelperContainersJob::dispatch($server);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stucked resources: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$applicationsDeploymentQueue = ApplicationDeploymentQueue::get();
|
||||||
|
foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) {
|
||||||
|
if (is_null($applicationDeploymentQueue->application)) {
|
||||||
|
echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n";
|
||||||
|
$applicationDeploymentQueue->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck application deployment queue: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
|
|||||||
@@ -48,6 +48,13 @@ class Dev extends Command
|
|||||||
echo "Generating APP_KEY.\n";
|
echo "Generating APP_KEY.\n";
|
||||||
Artisan::call('key:generate');
|
Artisan::call('key:generate');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate STORAGE link if not exists
|
||||||
|
if (! file_exists(public_path('storage'))) {
|
||||||
|
echo "Generating STORAGE link.\n";
|
||||||
|
Artisan::call('storage:link');
|
||||||
|
}
|
||||||
|
|
||||||
// Seed database if it's empty
|
// Seed database if it's empty
|
||||||
$settings = InstanceSettings::find(0);
|
$settings = InstanceSettings::find(0);
|
||||||
if (! $settings) {
|
if (! $settings) {
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ namespace App\Console\Commands;
|
|||||||
use App\Actions\Server\StopSentinel;
|
use App\Actions\Server\StopSentinel;
|
||||||
use App\Enums\ActivityTypes;
|
use App\Enums\ActivityTypes;
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\CleanupHelperContainersJob;
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Environment;
|
use App\Models\Environment;
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
@@ -18,7 +16,7 @@ use Illuminate\Support\Facades\Http;
|
|||||||
|
|
||||||
class Init extends Command
|
class Init extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments} {--cleanup-proxy-networks}';
|
protected $signature = 'app:init {--force-cloud}';
|
||||||
|
|
||||||
protected $description = 'Cleanup instance related stuffs';
|
protected $description = 'Cleanup instance related stuffs';
|
||||||
|
|
||||||
@@ -26,9 +24,63 @@ class Init extends Command
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
if (isCloud() && ! $this->option('force-cloud')) {
|
||||||
|
echo "Skipping init as we are on cloud and --force-cloud option is not set\n";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->servers = Server::all();
|
$this->servers = Server::all();
|
||||||
$this->alive();
|
if (isCloud()) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->send_alive_signal();
|
||||||
get_public_ips();
|
get_public_ips();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backward compatibility
|
||||||
|
$this->disable_metrics();
|
||||||
|
$this->replace_slash_in_environment_name();
|
||||||
|
$this->restore_coolify_db_backup();
|
||||||
|
//
|
||||||
|
$this->update_traefik_labels();
|
||||||
|
if (! isCloud() || $this->option('force-cloud')) {
|
||||||
|
$this->cleanup_unused_network_from_coolify_proxy();
|
||||||
|
}
|
||||||
|
if (isCloud()) {
|
||||||
|
$this->cleanup_unnecessary_dynamic_proxy_configuration();
|
||||||
|
} else {
|
||||||
|
$this->cleanup_in_progress_application_deployments();
|
||||||
|
}
|
||||||
|
$this->call('cleanup:redis');
|
||||||
|
$this->call('cleanup:stucked-resources');
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$localhost = $this->servers->where('id', 0)->first();
|
||||||
|
$localhost->setupDynamicProxyConfiguration();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
$settings = instanceSettings();
|
||||||
|
if (! is_null(env('AUTOUPDATE', null))) {
|
||||||
|
if (env('AUTOUPDATE') == true) {
|
||||||
|
$settings->update(['is_auto_update_enabled' => true]);
|
||||||
|
} else {
|
||||||
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function disable_metrics()
|
||||||
|
{
|
||||||
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
if ($server->settings->is_metrics_enabled === true) {
|
if ($server->settings->is_metrics_enabled === true) {
|
||||||
@@ -39,62 +91,6 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$full_cleanup = $this->option('full-cleanup');
|
|
||||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
|
||||||
$cleanup_proxy_networks = $this->option('cleanup-proxy-networks');
|
|
||||||
$this->replace_slash_in_environment_name();
|
|
||||||
if ($cleanup_deployments) {
|
|
||||||
echo "Running cleanup deployments.\n";
|
|
||||||
$this->cleanup_in_progress_application_deployments();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($cleanup_proxy_networks) {
|
|
||||||
echo "Running cleanup proxy networks.\n";
|
|
||||||
$this->cleanup_unused_network_from_coolify_proxy();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($full_cleanup) {
|
|
||||||
// Required for falsely deleted coolify db
|
|
||||||
$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_stucked_helper_containers();
|
|
||||||
$this->call('cleanup:queue');
|
|
||||||
$this->call('cleanup:stucked-resources');
|
|
||||||
if (! isCloud()) {
|
|
||||||
try {
|
|
||||||
$localhost = $this->servers->where('id', 0)->first();
|
|
||||||
$localhost->setupDynamicProxyConfiguration();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
if (! is_null(env('AUTOUPDATE', null))) {
|
|
||||||
if (env('AUTOUPDATE') == true) {
|
|
||||||
$settings->update(['is_auto_update_enabled' => true]);
|
|
||||||
} else {
|
|
||||||
$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;
|
|
||||||
}
|
|
||||||
$this->cleanup_stucked_helper_containers();
|
|
||||||
$this->call('cleanup:stucked-resources');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function update_traefik_labels()
|
private function update_traefik_labels()
|
||||||
@@ -108,7 +104,6 @@ class Init extends Command
|
|||||||
|
|
||||||
private function cleanup_unnecessary_dynamic_proxy_configuration()
|
private function cleanup_unnecessary_dynamic_proxy_configuration()
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
try {
|
try {
|
||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
@@ -128,13 +123,9 @@ class Init extends Command
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private function cleanup_unused_network_from_coolify_proxy()
|
private function cleanup_unused_network_from_coolify_proxy()
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -175,6 +166,7 @@ class Init extends Command
|
|||||||
|
|
||||||
private function restore_coolify_db_backup()
|
private function restore_coolify_db_backup()
|
||||||
{
|
{
|
||||||
|
if (version_compare('4.0.0-beta.179', config('version'), '<=')) {
|
||||||
try {
|
try {
|
||||||
$database = StandalonePostgresql::withTrashed()->find(0);
|
$database = StandalonePostgresql::withTrashed()->find(0);
|
||||||
if ($database && $database->trashed()) {
|
if ($database && $database->trashed()) {
|
||||||
@@ -197,21 +189,13 @@ class Init extends Command
|
|||||||
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
|
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanup_stucked_helper_containers()
|
|
||||||
{
|
|
||||||
foreach ($this->servers as $server) {
|
|
||||||
if ($server->isFunctional()) {
|
|
||||||
CleanupHelperContainersJob::dispatch($server);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function alive()
|
private function send_alive_signal()
|
||||||
{
|
{
|
||||||
$id = config('app.id');
|
$id = config('app.id');
|
||||||
$version = config('version');
|
$version = config('version');
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$do_not_track = data_get($settings, 'do_not_track');
|
$do_not_track = data_get($settings, 'do_not_track');
|
||||||
if ($do_not_track == true) {
|
if ($do_not_track == true) {
|
||||||
echo "Skipping alive as do_not_track is enabled\n";
|
echo "Skipping alive as do_not_track is enabled\n";
|
||||||
@@ -225,23 +209,7 @@ class Init extends Command
|
|||||||
echo "Error in alive: {$e->getMessage()}\n";
|
echo "Error in alive: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// private function cleanup_ssh()
|
|
||||||
// {
|
|
||||||
|
|
||||||
// TODO: it will cleanup id.root@host.docker.internal
|
|
||||||
// try {
|
|
||||||
// $files = Storage::allFiles('ssh/keys');
|
|
||||||
// foreach ($files as $file) {
|
|
||||||
// Storage::delete($file);
|
|
||||||
// }
|
|
||||||
// $files = Storage::allFiles('ssh/mux');
|
|
||||||
// foreach ($files as $file) {
|
|
||||||
// Storage::delete($file);
|
|
||||||
// }
|
|
||||||
// } catch (\Throwable $e) {
|
|
||||||
// echo "Error in cleaning ssh: {$e->getMessage()}\n";
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
private function cleanup_in_progress_application_deployments()
|
private function cleanup_in_progress_application_deployments()
|
||||||
{
|
{
|
||||||
// Cleanup any failed deployments
|
// Cleanup any failed deployments
|
||||||
@@ -263,6 +231,7 @@ class Init extends Command
|
|||||||
|
|
||||||
private function replace_slash_in_environment_name()
|
private function replace_slash_in_environment_name()
|
||||||
{
|
{
|
||||||
|
if (version_compare('4.0.0-beta.298', config('version'), '<=')) {
|
||||||
$environments = Environment::all();
|
$environments = Environment::all();
|
||||||
foreach ($environments as $environment) {
|
foreach ($environments as $environment) {
|
||||||
if (str_contains($environment->name, '/')) {
|
if (str_contains($environment->name, '/')) {
|
||||||
@@ -271,4 +240,5 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ class ServicesGenerate extends Command
|
|||||||
$serviceTemplatesJson[$name] = $parsed;
|
$serviceTemplatesJson[$name] = $parsed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$serviceTemplatesJson = json_encode($serviceTemplatesJson);
|
$serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
|
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson.PHP_EOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function process_file($file)
|
private function process_file($file)
|
||||||
@@ -78,7 +78,7 @@ class ServicesGenerate extends Command
|
|||||||
if ($logo->count() > 0) {
|
if ($logo->count() > 0) {
|
||||||
$logo = str($logo[0])->after('# logo:')->trim()->value();
|
$logo = str($logo[0])->after('# logo:')->trim()->value();
|
||||||
} else {
|
} else {
|
||||||
$logo = 'svgs/unknown.svg';
|
$logo = 'svgs/coolify.png';
|
||||||
}
|
}
|
||||||
$minversion = collect(preg_grep('/^# minversion:/', explode("\n", $content)))->values();
|
$minversion = collect(preg_grep('/^# minversion:/', explode("\n", $content)))->values();
|
||||||
if ($minversion->count() > 0) {
|
if ($minversion->count() > 0) {
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ use App\Jobs\PullTemplatesFromCDN;
|
|||||||
use App\Jobs\ScheduledTaskJob;
|
use App\Jobs\ScheduledTaskJob;
|
||||||
use App\Jobs\ServerCheckJob;
|
use App\Jobs\ServerCheckJob;
|
||||||
use App\Jobs\UpdateCoolifyJob;
|
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;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
class Kernel extends ConsoleKernel
|
class Kernel extends ConsoleKernel
|
||||||
{
|
{
|
||||||
@@ -28,7 +28,7 @@ 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();
|
$settings = instanceSettings();
|
||||||
|
|
||||||
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
|
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||||
|
|
||||||
@@ -43,6 +43,8 @@ class Kernel extends ConsoleKernel
|
|||||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||||
|
|
||||||
$schedule->command('telescope:prune')->daily();
|
$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();
|
||||||
@@ -64,7 +66,7 @@ class Kernel extends ConsoleKernel
|
|||||||
|
|
||||||
private function pull_images($schedule)
|
private function pull_images($schedule)
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$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()) {
|
||||||
@@ -77,16 +79,16 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
})->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
})->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||||
}
|
}
|
||||||
$schedule->job(new PullHelperImageJob($server))
|
}
|
||||||
|
$schedule->job(new PullHelperImageJob)
|
||||||
->cron($settings->update_check_frequency)
|
->cron($settings->update_check_frequency)
|
||||||
->timezone($settings->instance_timezone)
|
->timezone($settings->instance_timezone)
|
||||||
->onOneServer();
|
->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private function schedule_updates($schedule)
|
private function schedule_updates($schedule)
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
|
|
||||||
$updateCheckFrequency = $settings->update_check_frequency;
|
$updateCheckFrequency = $settings->update_check_frequency;
|
||||||
$schedule->job(new CheckForUpdatesJob)
|
$schedule->job(new CheckForUpdatesJob)
|
||||||
@@ -113,7 +115,11 @@ class Kernel extends ConsoleKernel
|
|||||||
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
||||||
}
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
|
$last_sentinel_update = $server->sentinel_updated_at;
|
||||||
|
if (Carbon::parse($last_sentinel_update)->isBefore(now()->subMinutes(4))) {
|
||||||
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
||||||
|
}
|
||||||
|
// $schedule->job(new ServerStorageCheckJob($server))->everyMinute()->onOneServer();
|
||||||
$serverTimezone = $server->settings->server_timezone;
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
if ($server->settings->force_docker_cleanup) {
|
if ($server->settings->force_docker_cleanup) {
|
||||||
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
|
|||||||
14
app/Enums/ContainerStatusTypes.php
Normal file
14
app/Enums/ContainerStatusTypes.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum ContainerStatusTypes: string
|
||||||
|
{
|
||||||
|
case PAUSED = 'paused';
|
||||||
|
case RESTARTING = 'restarting';
|
||||||
|
case REMOVING = 'removing';
|
||||||
|
case RUNNING = 'running';
|
||||||
|
case DEAD = 'dead';
|
||||||
|
case CREATED = 'created';
|
||||||
|
case EXITED = 'exited';
|
||||||
|
}
|
||||||
8
app/Enums/StaticImageTypes.php
Normal file
8
app/Enums/StaticImageTypes.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum StaticImageTypes: string
|
||||||
|
{
|
||||||
|
case NGINX_ALPINE = 'nginx:alpine';
|
||||||
|
}
|
||||||
34
app/Events/CloudflareTunnelConfigured.php
Normal file
34
app/Events/CloudflareTunnelConfigured.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class CloudflareTunnelConfigured implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public $teamId;
|
||||||
|
|
||||||
|
public function __construct($teamId = null)
|
||||||
|
{
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
$teamId = auth()->user()->currentTeam()->id ?? null;
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
throw new \Exception('Team id is null');
|
||||||
|
}
|
||||||
|
$this->teamId = $teamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ class Handler extends ExceptionHandler
|
|||||||
if ($e instanceof RuntimeException) {
|
if ($e instanceof RuntimeException) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->settings = \App\Models\InstanceSettings::get();
|
$this->settings = instanceSettings();
|
||||||
if ($this->settings->do_not_track) {
|
if ($this->settings->do_not_track) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
186
app/Helpers/SshMultiplexingHelper.php
Normal file
186
app/Helpers/SshMultiplexingHelper.php
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
<?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()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sshConfig = self::serverSshConfiguration($server);
|
||||||
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||||
|
|
||||||
|
self::validateSshKey($sshKeyLocation);
|
||||||
|
|
||||||
|
$checkCommand = "ssh -O check -o ControlPath=$muxSocket ";
|
||||||
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$checkCommand .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
||||||
|
}
|
||||||
|
$checkCommand .= "{$server->user}@{$server->ip}";
|
||||||
|
$process = Process::run($checkCommand);
|
||||||
|
|
||||||
|
if ($process->exitCode() !== 0) {
|
||||||
|
self::establishNewMultiplexedConnection($server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function establishNewMultiplexedConnection(Server $server)
|
||||||
|
{
|
||||||
|
$sshConfig = self::serverSshConfiguration($server);
|
||||||
|
$sshKeyLocation = $sshConfig['sshKeyLocation'];
|
||||||
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
|
||||||
|
$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} ";
|
||||||
|
|
||||||
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$establishCommand .= ' -o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$establishCommand .= self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval);
|
||||||
|
$establishCommand .= "{$server->user}@{$server->ip}";
|
||||||
|
|
||||||
|
$establishProcess = Process::run($establishCommand);
|
||||||
|
|
||||||
|
if ($establishProcess->exitCode() !== 0) {
|
||||||
|
throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function removeMuxFile(Server $server)
|
||||||
|
{
|
||||||
|
$sshConfig = self::serverSshConfiguration($server);
|
||||||
|
$muxSocket = $sshConfig['muxFilename'];
|
||||||
|
|
||||||
|
$closeCommand = "ssh -O exit -o ControlPath=$muxSocket ";
|
||||||
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$closeCommand .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
||||||
|
}
|
||||||
|
$closeCommand .= "{$server->user}@{$server->ip}";
|
||||||
|
Process::run($closeCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||||
|
|
||||||
|
$scp_command = "timeout $timeout scp ";
|
||||||
|
if ($server->isIpv6()) {
|
||||||
|
$scp_command .= '-6 ';
|
||||||
|
}
|
||||||
|
if (self::isMultiplexingEnabled()) {
|
||||||
|
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||||
|
self::ensureMultiplexedConnection($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$scp_command .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$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');
|
||||||
|
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||||
|
|
||||||
|
$ssh_command = "timeout $timeout ssh ";
|
||||||
|
|
||||||
|
if (self::isMultiplexingEnabled()) {
|
||||||
|
$ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||||
|
self::ensureMultiplexedConnection($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$ssh_command .= "-o ProxyCommand='cloudflared access ssh --hostname %h' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));
|
||||||
|
|
||||||
|
$delimiter = Hash::make($command);
|
||||||
|
$delimiter = base64_encode($delimiter);
|
||||||
|
$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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -132,6 +132,7 @@ class ApplicationsController extends Controller
|
|||||||
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
||||||
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
||||||
'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
|
'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
|
||||||
|
'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'],
|
||||||
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
||||||
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
||||||
'start_command' => ['type' => 'string', 'description' => 'The start command.'],
|
'start_command' => ['type' => 'string', 'description' => 'The start command.'],
|
||||||
@@ -177,6 +178,7 @@ class ApplicationsController extends Controller
|
|||||||
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
||||||
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
|
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
|
||||||
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
||||||
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
]),
|
]),
|
||||||
@@ -235,6 +237,7 @@ class ApplicationsController extends Controller
|
|||||||
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
||||||
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
||||||
'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
|
'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
|
||||||
|
'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'],
|
||||||
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
||||||
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
||||||
'start_command' => ['type' => 'string', 'description' => 'The start command.'],
|
'start_command' => ['type' => 'string', 'description' => 'The start command.'],
|
||||||
@@ -279,6 +282,7 @@ class ApplicationsController extends Controller
|
|||||||
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
||||||
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
|
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
|
||||||
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
||||||
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
]),
|
]),
|
||||||
@@ -337,6 +341,7 @@ class ApplicationsController extends Controller
|
|||||||
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
||||||
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
||||||
'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
|
'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
|
||||||
|
'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'],
|
||||||
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
||||||
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
||||||
'start_command' => ['type' => 'string', 'description' => 'The start command.'],
|
'start_command' => ['type' => 'string', 'description' => 'The start command.'],
|
||||||
@@ -381,6 +386,7 @@ class ApplicationsController extends Controller
|
|||||||
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
||||||
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
|
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
|
||||||
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
||||||
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
]),
|
]),
|
||||||
@@ -468,6 +474,7 @@ class ApplicationsController extends Controller
|
|||||||
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
|
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
|
||||||
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
||||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||||
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
]),
|
]),
|
||||||
@@ -552,6 +559,7 @@ class ApplicationsController extends Controller
|
|||||||
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
|
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
|
||||||
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
||||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||||
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
]),
|
]),
|
||||||
@@ -602,6 +610,7 @@ class ApplicationsController extends Controller
|
|||||||
'name' => ['type' => 'string', 'description' => 'The application name.'],
|
'name' => ['type' => 'string', 'description' => 'The application name.'],
|
||||||
'description' => ['type' => 'string', 'description' => 'The application description.'],
|
'description' => ['type' => 'string', 'description' => 'The application description.'],
|
||||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||||
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
]),
|
]),
|
||||||
@@ -627,7 +636,7 @@ class ApplicationsController extends Controller
|
|||||||
|
|
||||||
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', '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'];
|
$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', 'use_build_server', 'static_image'];
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
@@ -665,6 +674,8 @@ class ApplicationsController extends Controller
|
|||||||
$fqdn = $request->domains;
|
$fqdn = $request->domains;
|
||||||
$instantDeploy = $request->instant_deploy;
|
$instantDeploy = $request->instant_deploy;
|
||||||
$githubAppUuid = $request->github_app_uuid;
|
$githubAppUuid = $request->github_app_uuid;
|
||||||
|
$useBuildServer = $request->use_build_server;
|
||||||
|
$isStatic = $request->is_static;
|
||||||
|
|
||||||
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
|
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
|
||||||
if (! $project) {
|
if (! $project) {
|
||||||
@@ -693,8 +704,7 @@ class ApplicationsController extends Controller
|
|||||||
if ($request->build_pack === 'dockercompose') {
|
if ($request->build_pack === 'dockercompose') {
|
||||||
$request->offsetSet('ports_exposes', '80');
|
$request->offsetSet('ports_exposes', '80');
|
||||||
}
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
$validationRules = [
|
||||||
sharedDataApplications(),
|
|
||||||
'git_repository' => 'string|required',
|
'git_repository' => 'string|required',
|
||||||
'git_branch' => 'string|required',
|
'git_branch' => 'string|required',
|
||||||
'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
|
'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
|
||||||
@@ -702,19 +712,21 @@ class ApplicationsController extends Controller
|
|||||||
'docker_compose_location' => 'string',
|
'docker_compose_location' => 'string',
|
||||||
'docker_compose_raw' => 'string|nullable',
|
'docker_compose_raw' => 'string|nullable',
|
||||||
'docker_compose_domains' => 'array|nullable',
|
'docker_compose_domains' => 'array|nullable',
|
||||||
'docker_compose_custom_start_command' => 'string|nullable',
|
];
|
||||||
'docker_compose_custom_build_command' => 'string|nullable',
|
$validationRules = array_merge($validationRules, sharedDataApplications());
|
||||||
]);
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
'errors' => $validator->errors(),
|
'errors' => $validator->errors(),
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
$return = $this->validateDataApplications($request, $server);
|
$return = $this->validateDataApplications($request, $server);
|
||||||
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);
|
||||||
|
|
||||||
@@ -738,6 +750,14 @@ class ApplicationsController extends Controller
|
|||||||
$application->destination_type = $destination->getMorphClass();
|
$application->destination_type = $destination->getMorphClass();
|
||||||
$application->environment_id = $environment->id;
|
$application->environment_id = $environment->id;
|
||||||
$application->save();
|
$application->save();
|
||||||
|
if (isset($isStatic)) {
|
||||||
|
$application->settings->is_static = $isStatic;
|
||||||
|
$application->settings->save();
|
||||||
|
}
|
||||||
|
if (isset($useBuildServer)) {
|
||||||
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
|
$application->settings->save();
|
||||||
|
}
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
@@ -771,8 +791,7 @@ class ApplicationsController extends Controller
|
|||||||
if ($request->build_pack === 'dockercompose') {
|
if ($request->build_pack === 'dockercompose') {
|
||||||
$request->offsetSet('ports_exposes', '80');
|
$request->offsetSet('ports_exposes', '80');
|
||||||
}
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
$validationRules = [
|
||||||
sharedDataApplications(),
|
|
||||||
'git_repository' => 'string|required',
|
'git_repository' => 'string|required',
|
||||||
'git_branch' => 'string|required',
|
'git_branch' => 'string|required',
|
||||||
'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
|
'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
|
||||||
@@ -781,10 +800,10 @@ class ApplicationsController extends Controller
|
|||||||
'watch_paths' => 'string|nullable',
|
'watch_paths' => 'string|nullable',
|
||||||
'docker_compose_location' => 'string',
|
'docker_compose_location' => 'string',
|
||||||
'docker_compose_raw' => 'string|nullable',
|
'docker_compose_raw' => 'string|nullable',
|
||||||
'docker_compose_domains' => 'array|nullable',
|
];
|
||||||
'docker_compose_custom_start_command' => 'string|nullable',
|
$validationRules = array_merge($validationRules, sharedDataApplications());
|
||||||
'docker_compose_custom_build_command' => 'string|nullable',
|
|
||||||
]);
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
@@ -833,6 +852,10 @@ class ApplicationsController extends Controller
|
|||||||
$application->environment_id = $environment->id;
|
$application->environment_id = $environment->id;
|
||||||
$application->source_type = $githubApp->getMorphClass();
|
$application->source_type = $githubApp->getMorphClass();
|
||||||
$application->source_id = $githubApp->id;
|
$application->source_id = $githubApp->id;
|
||||||
|
if (isset($useBuildServer)) {
|
||||||
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
|
$application->settings->save();
|
||||||
|
}
|
||||||
$application->save();
|
$application->save();
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||||
@@ -867,8 +890,8 @@ class ApplicationsController extends Controller
|
|||||||
if ($request->build_pack === 'dockercompose') {
|
if ($request->build_pack === 'dockercompose') {
|
||||||
$request->offsetSet('ports_exposes', '80');
|
$request->offsetSet('ports_exposes', '80');
|
||||||
}
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
|
||||||
sharedDataApplications(),
|
$validationRules = [
|
||||||
'git_repository' => 'string|required',
|
'git_repository' => 'string|required',
|
||||||
'git_branch' => 'string|required',
|
'git_branch' => 'string|required',
|
||||||
'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
|
'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
|
||||||
@@ -877,10 +900,10 @@ class ApplicationsController extends Controller
|
|||||||
'watch_paths' => 'string|nullable',
|
'watch_paths' => 'string|nullable',
|
||||||
'docker_compose_location' => 'string',
|
'docker_compose_location' => 'string',
|
||||||
'docker_compose_raw' => 'string|nullable',
|
'docker_compose_raw' => 'string|nullable',
|
||||||
'docker_compose_domains' => 'array|nullable',
|
];
|
||||||
'docker_compose_custom_start_command' => 'string|nullable',
|
|
||||||
'docker_compose_custom_build_command' => 'string|nullable',
|
$validationRules = array_merge($validationRules, sharedDataApplications());
|
||||||
]);
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -925,6 +948,10 @@ class ApplicationsController extends Controller
|
|||||||
$application->destination_id = $destination->id;
|
$application->destination_id = $destination->id;
|
||||||
$application->destination_type = $destination->getMorphClass();
|
$application->destination_type = $destination->getMorphClass();
|
||||||
$application->environment_id = $environment->id;
|
$application->environment_id = $environment->id;
|
||||||
|
if (isset($useBuildServer)) {
|
||||||
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
|
$application->settings->save();
|
||||||
|
}
|
||||||
$application->save();
|
$application->save();
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||||
@@ -956,10 +983,13 @@ class ApplicationsController extends Controller
|
|||||||
if (! $request->has('name')) {
|
if (! $request->has('name')) {
|
||||||
$request->offsetSet('name', 'dockerfile-'.new Cuid2);
|
$request->offsetSet('name', 'dockerfile-'.new Cuid2);
|
||||||
}
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
|
||||||
sharedDataApplications(),
|
$validationRules = [
|
||||||
'dockerfile' => 'string|required',
|
'dockerfile' => 'string|required',
|
||||||
]);
|
];
|
||||||
|
$validationRules = array_merge($validationRules, sharedDataApplications());
|
||||||
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
@@ -1004,6 +1034,10 @@ class ApplicationsController extends Controller
|
|||||||
$application->destination_id = $destination->id;
|
$application->destination_id = $destination->id;
|
||||||
$application->destination_type = $destination->getMorphClass();
|
$application->destination_type = $destination->getMorphClass();
|
||||||
$application->environment_id = $environment->id;
|
$application->environment_id = $environment->id;
|
||||||
|
if (isset($useBuildServer)) {
|
||||||
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
|
$application->settings->save();
|
||||||
|
}
|
||||||
|
|
||||||
$application->git_repository = 'coollabsio/coolify';
|
$application->git_repository = 'coollabsio/coolify';
|
||||||
$application->git_branch = 'main';
|
$application->git_branch = 'main';
|
||||||
@@ -1034,12 +1068,14 @@ class ApplicationsController extends Controller
|
|||||||
if (! $request->has('name')) {
|
if (! $request->has('name')) {
|
||||||
$request->offsetSet('name', 'docker-image-'.new Cuid2);
|
$request->offsetSet('name', 'docker-image-'.new Cuid2);
|
||||||
}
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
$validationRules = [
|
||||||
sharedDataApplications(),
|
|
||||||
'docker_registry_image_name' => 'string|required',
|
'docker_registry_image_name' => 'string|required',
|
||||||
'docker_registry_image_tag' => 'string',
|
'docker_registry_image_tag' => 'string',
|
||||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
|
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
|
||||||
]);
|
];
|
||||||
|
$validationRules = array_merge($validationRules, sharedDataApplications());
|
||||||
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
@@ -1062,6 +1098,10 @@ class ApplicationsController extends Controller
|
|||||||
$application->destination_id = $destination->id;
|
$application->destination_id = $destination->id;
|
||||||
$application->destination_type = $destination->getMorphClass();
|
$application->destination_type = $destination->getMorphClass();
|
||||||
$application->environment_id = $environment->id;
|
$application->environment_id = $environment->id;
|
||||||
|
if (isset($useBuildServer)) {
|
||||||
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
|
$application->settings->save();
|
||||||
|
}
|
||||||
|
|
||||||
$application->git_repository = 'coollabsio/coolify';
|
$application->git_repository = 'coollabsio/coolify';
|
||||||
$application->git_branch = 'main';
|
$application->git_branch = 'main';
|
||||||
@@ -1108,10 +1148,12 @@ class ApplicationsController extends Controller
|
|||||||
if (! $request->has('name')) {
|
if (! $request->has('name')) {
|
||||||
$request->offsetSet('name', 'service'.new Cuid2);
|
$request->offsetSet('name', 'service'.new Cuid2);
|
||||||
}
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
$validationRules = [
|
||||||
sharedDataApplications(),
|
|
||||||
'docker_compose_raw' => 'string|required',
|
'docker_compose_raw' => 'string|required',
|
||||||
]);
|
];
|
||||||
|
$validationRules = array_merge($validationRules, sharedDataApplications());
|
||||||
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
@@ -1259,16 +1301,10 @@ class ApplicationsController extends Controller
|
|||||||
format: 'uuid',
|
format: 'uuid',
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
new OA\Parameter(
|
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
name: 'cleanup',
|
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
in: 'query',
|
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
description: 'Delete configurations and volumes.',
|
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
required: false,
|
|
||||||
schema: new OA\Schema(
|
|
||||||
type: 'boolean',
|
|
||||||
default: true,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
@@ -1316,10 +1352,14 @@ class ApplicationsController extends Controller
|
|||||||
'message' => 'Application not found',
|
'message' => 'Application not found',
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteResourceJob::dispatch(
|
DeleteResourceJob::dispatch(
|
||||||
resource: $application,
|
resource: $application,
|
||||||
deleteConfigurations: $cleanup,
|
deleteConfigurations: $request->query->get('delete_configurations', true),
|
||||||
deleteVolumes: $cleanup);
|
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||||
|
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||||
|
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||||
|
);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Application deletion request queued.',
|
'message' => 'Application deletion request queued.',
|
||||||
@@ -1404,6 +1444,7 @@ class ApplicationsController extends Controller
|
|||||||
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
||||||
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
|
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
|
||||||
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
||||||
|
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
]),
|
]),
|
||||||
@@ -1460,10 +1501,9 @@ class ApplicationsController extends Controller
|
|||||||
], 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', 'instant_deploy'];
|
$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', 'use_build_server'];
|
||||||
|
|
||||||
$validator = customApiValidator($request->all(), [
|
$validationRules = [
|
||||||
sharedDataApplications(),
|
|
||||||
'name' => 'string|max:255',
|
'name' => 'string|max:255',
|
||||||
'description' => 'string|nullable',
|
'description' => 'string|nullable',
|
||||||
'static_image' => 'string',
|
'static_image' => 'string',
|
||||||
@@ -1473,7 +1513,9 @@ class ApplicationsController extends Controller
|
|||||||
'docker_compose_domains' => 'array|nullable',
|
'docker_compose_domains' => 'array|nullable',
|
||||||
'docker_compose_custom_start_command' => 'string|nullable',
|
'docker_compose_custom_start_command' => 'string|nullable',
|
||||||
'docker_compose_custom_build_command' => 'string|nullable',
|
'docker_compose_custom_build_command' => 'string|nullable',
|
||||||
]);
|
];
|
||||||
|
$validationRules = array_merge($validationRules, sharedDataApplications());
|
||||||
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
|
|
||||||
// Validate ports_exposes
|
// Validate ports_exposes
|
||||||
if ($request->has('ports_exposes')) {
|
if ($request->has('ports_exposes')) {
|
||||||
@@ -1538,6 +1580,13 @@ class ApplicationsController extends Controller
|
|||||||
}
|
}
|
||||||
$instantDeploy = $request->instant_deploy;
|
$instantDeploy = $request->instant_deploy;
|
||||||
|
|
||||||
|
$use_build_server = $request->use_build_server;
|
||||||
|
|
||||||
|
if (isset($use_build_server)) {
|
||||||
|
$application->settings->is_build_server_enabled = $use_build_server;
|
||||||
|
$application->settings->save();
|
||||||
|
}
|
||||||
|
|
||||||
removeUnnecessaryFieldsFromRequest($request);
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
|
|
||||||
$data = $request->all();
|
$data = $request->all();
|
||||||
@@ -2529,6 +2578,131 @@ class ApplicationsController extends Controller
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Execute Command',
|
||||||
|
description: "Execute a command on the application's current container.",
|
||||||
|
path: '/applications/{uuid}/execute',
|
||||||
|
operationId: 'execute-command-application',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Applications'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the application.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Command to execute.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'command' => ['type' => 'string', 'description' => 'Command to execute.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: "Execute a command on the application's current container.",
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Command executed.'],
|
||||||
|
'response' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
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 execute_command_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
// TODO: Need to review this from security perspective, to not allow arbitrary command execution
|
||||||
|
$allowedFields = ['command'];
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
if (! $application) {
|
||||||
|
return response()->json(['message' => 'Application not found.'], 404);
|
||||||
|
}
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'command' => 'string|required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
$container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail();
|
||||||
|
$status = getContainerStatus($application->destination->server, $container['Names']);
|
||||||
|
|
||||||
|
if ($status !== 'running') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Application is not running.',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$commands = collect([
|
||||||
|
executeInDocker($container['Names'], $request->command),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$res = instant_remote_process(command: $commands, server: $application->destination->server);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Command executed.',
|
||||||
|
'response' => $res,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
private function validateDataApplications(Request $request, Server $server)
|
private function validateDataApplications(Request $request, Server $server)
|
||||||
{
|
{
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
|
|||||||
@@ -1541,16 +1541,10 @@ class DatabasesController extends Controller
|
|||||||
format: 'uuid',
|
format: 'uuid',
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
new OA\Parameter(
|
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
name: 'cleanup',
|
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
in: 'query',
|
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
description: 'Delete configurations and volumes.',
|
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
required: false,
|
|
||||||
schema: new OA\Schema(
|
|
||||||
type: 'boolean',
|
|
||||||
default: true,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
@@ -1595,10 +1589,14 @@ class DatabasesController extends Controller
|
|||||||
if (! $database) {
|
if (! $database) {
|
||||||
return response()->json(['message' => 'Database not found.'], 404);
|
return response()->json(['message' => 'Database not found.'], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteResourceJob::dispatch(
|
DeleteResourceJob::dispatch(
|
||||||
resource: $database,
|
resource: $database,
|
||||||
deleteConfigurations: $cleanup,
|
deleteConfigurations: $request->query->get('delete_configurations', true),
|
||||||
deleteVolumes: $cleanup);
|
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||||
|
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||||
|
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||||
|
);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Database deletion request queued.',
|
'message' => 'Database deletion request queued.',
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class OtherController extends Controller
|
|||||||
if ($teamId !== '0') {
|
if ($teamId !== '0') {
|
||||||
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
|
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
|
||||||
}
|
}
|
||||||
$settings = \App\Models\InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$settings->update(['is_api_enabled' => true]);
|
$settings->update(['is_api_enabled' => true]);
|
||||||
|
|
||||||
return response()->json(['message' => 'API enabled.'], 200);
|
return response()->json(['message' => 'API enabled.'], 200);
|
||||||
@@ -138,7 +138,7 @@ class OtherController extends Controller
|
|||||||
if ($teamId !== '0') {
|
if ($teamId !== '0') {
|
||||||
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
|
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
|
||||||
}
|
}
|
||||||
$settings = \App\Models\InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$settings->update(['is_api_enabled' => false]);
|
$settings->update(['is_api_enabled' => false]);
|
||||||
|
|
||||||
return response()->json(['message' => 'API disabled.'], 200);
|
return response()->json(['message' => 'API disabled.'], 200);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Server\DeleteServer;
|
||||||
use App\Actions\Server\ValidateServer;
|
use App\Actions\Server\ValidateServer;
|
||||||
use App\Enums\ProxyStatus;
|
use App\Enums\ProxyStatus;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
@@ -23,7 +24,7 @@ class ServersController extends Controller
|
|||||||
return serializeApiResponse($settings);
|
return serializeApiResponse($settings);
|
||||||
}
|
}
|
||||||
$settings = $settings->makeHidden([
|
$settings = $settings->makeHidden([
|
||||||
'metrics_token',
|
'sentinel_token',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return serializeApiResponse($settings);
|
return serializeApiResponse($settings);
|
||||||
@@ -308,7 +309,7 @@ class ServersController extends Controller
|
|||||||
$projects = Project::where('team_id', $teamId)->get();
|
$projects = Project::where('team_id', $teamId)->get();
|
||||||
$domains = collect();
|
$domains = collect();
|
||||||
$applications = $projects->pluck('applications')->flatten();
|
$applications = $projects->pluck('applications')->flatten();
|
||||||
$settings = \App\Models\InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
if ($applications->count() > 0) {
|
if ($applications->count() > 0) {
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
$ip = $application->destination->server->ip;
|
$ip = $application->destination->server->ip;
|
||||||
@@ -726,6 +727,7 @@ class ServersController extends Controller
|
|||||||
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
|
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
|
||||||
}
|
}
|
||||||
$server->delete();
|
$server->delete();
|
||||||
|
DeleteServer::dispatch($server);
|
||||||
|
|
||||||
return response()->json(['message' => 'Server deleted.']);
|
return response()->json(['message' => 'Server deleted.']);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,6 +432,10 @@ class ServicesController extends Controller
|
|||||||
tags: ['Services'],
|
tags: ['Services'],
|
||||||
parameters: [
|
parameters: [
|
||||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
|
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
|
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
|
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||||
],
|
],
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
@@ -476,7 +480,14 @@ class ServicesController extends Controller
|
|||||||
if (! $service) {
|
if (! $service) {
|
||||||
return response()->json(['message' => 'Service not found.'], 404);
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
}
|
}
|
||||||
DeleteResourceJob::dispatch($service);
|
|
||||||
|
DeleteResourceJob::dispatch(
|
||||||
|
resource: $service,
|
||||||
|
deleteConfigurations: $request->query->get('delete_configurations', true),
|
||||||
|
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||||
|
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||||
|
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||||
|
);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Service deletion request queued.',
|
'message' => 'Service deletion request queued.',
|
||||||
@@ -516,7 +527,8 @@ class ServicesController extends Controller
|
|||||||
items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
|
items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -619,7 +631,8 @@ class ServicesController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -738,7 +751,8 @@ class ServicesController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -853,7 +867,8 @@ class ServicesController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -953,7 +968,8 @@ class ServicesController extends Controller
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -1025,9 +1041,11 @@ class ServicesController extends Controller
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'message' => ['type' => 'string', 'example' => 'Service starting request queued.'],
|
'message' => ['type' => 'string', 'example' => 'Service starting request queued.'],
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
),
|
),
|
||||||
]),
|
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -1101,9 +1119,11 @@ class ServicesController extends Controller
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'message' => ['type' => 'string', 'example' => 'Service stopping request queued.'],
|
'message' => ['type' => 'string', 'example' => 'Service stopping request queued.'],
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
),
|
),
|
||||||
]),
|
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
@@ -1177,9 +1197,11 @@ class ServicesController extends Controller
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'message' => ['type' => 'string', 'example' => 'Service restaring request queued.'],
|
'message' => ['type' => 'string', 'example' => 'Service restaring request queued.'],
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
),
|
),
|
||||||
]),
|
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
@@ -22,7 +21,7 @@ class OauthController extends Controller
|
|||||||
$oauthUser = get_socialite_provider($provider)->user();
|
$oauthUser = get_socialite_provider($provider)->user();
|
||||||
$user = User::whereEmail($oauthUser->email)->first();
|
$user = User::whereEmail($oauthUser->email)->first();
|
||||||
if (! $user) {
|
if (! $user) {
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
if (! $settings->is_registration_enabled) {
|
if (! $settings->is_registration_enabled) {
|
||||||
abort(403, 'Registration is disabled');
|
abort(403, 'Registration is disabled');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class ApiAllowed
|
|||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
$settings = \App\Models\InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
if ($settings->is_api_enabled === false) {
|
if ($settings->is_api_enabled === false) {
|
||||||
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
|
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use App\Models\ApplicationPreview;
|
|||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
use App\Models\GithubApp;
|
use App\Models\GithubApp;
|
||||||
use App\Models\GitlabApp;
|
use App\Models\GitlabApp;
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
@@ -27,6 +26,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
use Illuminate\Support\Sleep;
|
use Illuminate\Support\Sleep;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
@@ -210,7 +210,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
ray('New container name: ', $this->container_name)->green();
|
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
|
||||||
@@ -514,7 +513,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
'ignore_errors' => true,
|
'ignore_errors' => true,
|
||||||
], [
|
], [
|
||||||
"docker network connect {$networkId} coolify-proxy || true",
|
"docker network connect {$networkId} coolify-proxy >/dev/null 2>&1 || true",
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
'ignore_errors' => true,
|
'ignore_errors' => true,
|
||||||
]);
|
]);
|
||||||
@@ -919,10 +918,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -962,7 +961,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
|
||||||
if ($this->application->compose_parsing_version === '3') {
|
if ((int) $this->application->compose_parsing_version >= 3) {
|
||||||
$envs->push("COOLIFY_URL={$this->application->fqdn}");
|
$envs->push("COOLIFY_URL={$this->application->fqdn}");
|
||||||
} else {
|
} else {
|
||||||
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
|
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
|
||||||
@@ -970,7 +969,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
|
||||||
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
|
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
|
||||||
if ($this->application->compose_parsing_version === '3') {
|
if ((int) $this->application->compose_parsing_version >= 3) {
|
||||||
$envs->push("COOLIFY_FQDN={$url}");
|
$envs->push("COOLIFY_FQDN={$url}");
|
||||||
} else {
|
} else {
|
||||||
$envs->push("COOLIFY_URL={$url}");
|
$envs->push("COOLIFY_URL={$url}");
|
||||||
@@ -978,10 +977,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1334,7 +1333,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private function prepare_builder_image()
|
private function prepare_builder_image()
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$helperImage = config('coolify.helper_image');
|
$helperImage = config('coolify.helper_image');
|
||||||
$helperImage = "{$helperImage}:{$settings->helper_version}";
|
$helperImage = "{$helperImage}:{$settings->helper_version}";
|
||||||
// Get user home directory
|
// Get user home directory
|
||||||
@@ -1456,10 +1455,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
executeInDocker($this->deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'),
|
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}"),
|
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\" git ls-remote {$this->fullRepoUrl} {$local_branch}"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
'save' => 'git_commit_sha',
|
'save' => 'git_commit_sha',
|
||||||
],
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
@@ -2049,6 +2048,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
@@ -2068,6 +2071,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
@@ -2110,6 +2117,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
@@ -2129,6 +2140,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
@@ -2157,6 +2172,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
@@ -2176,6 +2195,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
@@ -2187,20 +2210,40 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
$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)
|
||||||
* @param int $timeout in seconds
|
|
||||||
*/
|
|
||||||
private function graceful_shutdown_container(string $containerName, int $timeout = 30)
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||||
|
|
||||||
|
$startTime = time();
|
||||||
|
while ($process->running()) {
|
||||||
|
if (time() - $startTime >= $timeout) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
|
["docker kill $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||||
["docker rm $containerName", 'hidden' => true, 'ignore_errors' => true]
|
|
||||||
);
|
);
|
||||||
} catch (\Exception $error) {
|
break;
|
||||||
// report error if needed
|
}
|
||||||
|
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(
|
$this->execute_remote_command(
|
||||||
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
|
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,15 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
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\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -22,7 +21,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if (isDev() || isCloud()) {
|
if (isDev() || isCloud()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
if ($response->successful()) {
|
if ($response->successful()) {
|
||||||
$versions = $response->json();
|
$versions = $response->json();
|
||||||
|
|||||||
@@ -21,11 +21,10 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S
|
|||||||
{
|
{
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
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\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class CleanupStaleMultiplexedConnections implements ShouldQueue
|
class CleanupStaleMultiplexedConnections implements ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -16,22 +18,65 @@ class CleanupStaleMultiplexedConnections implements ShouldQueue
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
Server::chunk(100, function ($servers) {
|
$this->cleanupStaleConnections();
|
||||||
foreach ($servers as $server) {
|
$this->cleanupNonExistentServerConnections();
|
||||||
$this->cleanupStaleConnection($server);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanupStaleConnection(Server $server)
|
private function cleanupStaleConnections()
|
||||||
{
|
{
|
||||||
$muxSocket = "/tmp/mux_{$server->id}";
|
$muxFiles = Storage::disk('ssh-mux')->files();
|
||||||
$checkCommand = "ssh -O check -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null";
|
|
||||||
|
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);
|
$checkProcess = Process::run($checkCommand);
|
||||||
|
|
||||||
if ($checkProcess->exitCode() !== 0) {
|
if ($checkProcess->exitCode() !== 0) {
|
||||||
$closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null";
|
$this->removeMultiplexFile($muxFile);
|
||||||
Process::run($closeCommand);
|
} 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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;
|
||||||
|
|
||||||
class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
@@ -25,16 +24,6 @@ class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->server->uuid))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->server->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
GetContainersStatus::run($this->server);
|
GetContainersStatus::run($this->server);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Actions\Database\StopDatabase;
|
|
||||||
use App\Events\BackupCreated;
|
use App\Events\BackupCreated;
|
||||||
use App\Models\S3Storage;
|
use App\Models\S3Storage;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
@@ -22,10 +21,8 @@ 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\Str;
|
use Illuminate\Support\Str;
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -64,8 +61,15 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
public function __construct($backup)
|
public function __construct($backup)
|
||||||
{
|
{
|
||||||
$this->backup = $backup;
|
$this->backup = $backup;
|
||||||
$this->team = Team::find($backup->team_id);
|
}
|
||||||
if (is_null($this->team)) {
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->team = Team::find($this->backup->team_id);
|
||||||
|
if (! $this->team) {
|
||||||
|
$this->backup->delete();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||||
@@ -77,28 +81,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->server = $this->database->destination->server;
|
$this->server = $this->database->destination->server;
|
||||||
$this->s3 = $this->backup->s3;
|
$this->s3 = $this->backup->s3;
|
||||||
}
|
}
|
||||||
|
if (is_null($this->server)) {
|
||||||
|
throw new \Exception('Server not found?!');
|
||||||
}
|
}
|
||||||
|
if (is_null($this->database)) {
|
||||||
public function middleware(): array
|
throw new \Exception('Database not found?!');
|
||||||
{
|
|
||||||
return [new WithoutOverlapping($this->backup->id)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->backup->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
// Check if team is exists
|
|
||||||
if (is_null($this->team)) {
|
|
||||||
$this->backup->update(['status' => 'failed']);
|
|
||||||
StopDatabase::run($this->database);
|
|
||||||
$this->database->delete();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BackupCreated::dispatch($this->team->id);
|
BackupCreated::dispatch($this->team->id);
|
||||||
@@ -249,7 +236,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
|
$this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
|
||||||
|
|
||||||
if ($this->database->name === 'coolify-db') {
|
if ($this->database->name === 'coolify-db') {
|
||||||
$databasesToBackup = ['coolify'];
|
$databasesToBackup = ['coolify'];
|
||||||
$this->directory_name = $this->container_name = 'coolify-db';
|
$this->directory_name = $this->container_name = 'coolify-db';
|
||||||
@@ -262,6 +248,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
try {
|
try {
|
||||||
if (str($databaseType)->contains('postgres')) {
|
if (str($databaseType)->contains('postgres')) {
|
||||||
$this->backup_file = "/pg-dump-$database-".Carbon::now()->timestamp.'.dmp';
|
$this->backup_file = "/pg-dump-$database-".Carbon::now()->timestamp.'.dmp';
|
||||||
|
if ($this->backup->dump_all) {
|
||||||
|
$this->backup_file = '/pg-dump-all-'.Carbon::now()->timestamp.'.gz';
|
||||||
|
}
|
||||||
$this->backup_location = $this->backup_dir.$this->backup_file;
|
$this->backup_location = $this->backup_dir.$this->backup_file;
|
||||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
'database_name' => $database,
|
'database_name' => $database,
|
||||||
@@ -290,6 +279,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->backup_standalone_mongodb($database);
|
$this->backup_standalone_mongodb($database);
|
||||||
} elseif (str($databaseType)->contains('mysql')) {
|
} elseif (str($databaseType)->contains('mysql')) {
|
||||||
$this->backup_file = "/mysql-dump-$database-".Carbon::now()->timestamp.'.dmp';
|
$this->backup_file = "/mysql-dump-$database-".Carbon::now()->timestamp.'.dmp';
|
||||||
|
if ($this->backup->dump_all) {
|
||||||
|
$this->backup_file = '/mysql-dump-all-'.Carbon::now()->timestamp.'.gz';
|
||||||
|
}
|
||||||
$this->backup_location = $this->backup_dir.$this->backup_file;
|
$this->backup_location = $this->backup_dir.$this->backup_file;
|
||||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
'database_name' => $database,
|
'database_name' => $database,
|
||||||
@@ -299,6 +291,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->backup_standalone_mysql($database);
|
$this->backup_standalone_mysql($database);
|
||||||
} elseif (str($databaseType)->contains('mariadb')) {
|
} elseif (str($databaseType)->contains('mariadb')) {
|
||||||
$this->backup_file = "/mariadb-dump-$database-".Carbon::now()->timestamp.'.dmp';
|
$this->backup_file = "/mariadb-dump-$database-".Carbon::now()->timestamp.'.dmp';
|
||||||
|
if ($this->backup->dump_all) {
|
||||||
|
$this->backup_file = '/mariadb-dump-all-'.Carbon::now()->timestamp.'.gz';
|
||||||
|
}
|
||||||
$this->backup_location = $this->backup_dir.$this->backup_file;
|
$this->backup_location = $this->backup_dir.$this->backup_file;
|
||||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
'database_name' => $database,
|
'database_name' => $database,
|
||||||
@@ -337,9 +332,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
|
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
|
if ($this->team) {
|
||||||
BackupCreated::dispatch($this->team->id);
|
BackupCreated::dispatch($this->team->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
||||||
{
|
{
|
||||||
@@ -396,7 +393,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->postgres_password) {
|
if ($this->postgres_password) {
|
||||||
$backupCommand .= " -e PGPASSWORD=$this->postgres_password";
|
$backupCommand .= " -e PGPASSWORD=$this->postgres_password";
|
||||||
}
|
}
|
||||||
|
if ($this->backup->dump_all) {
|
||||||
|
$backupCommand .= " $this->container_name pg_dumpall --username {$this->database->postgres_user} | gzip > $this->backup_location";
|
||||||
|
} else {
|
||||||
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
||||||
|
}
|
||||||
|
|
||||||
$commands[] = $backupCommand;
|
$commands[] = $backupCommand;
|
||||||
ray($commands);
|
ray($commands);
|
||||||
@@ -417,8 +418,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
|
if ($this->backup->dump_all) {
|
||||||
|
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress | gzip > $this->backup_location";
|
||||||
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
|
||||||
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 === '') {
|
||||||
@@ -436,7 +440,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
|
if ($this->backup->dump_all) {
|
||||||
|
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress > $this->backup_location";
|
||||||
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
|
||||||
|
}
|
||||||
ray($commands);
|
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);
|
||||||
@@ -481,7 +489,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
private function upload_to_s3(): void
|
private function upload_to_s3(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
ray($this->backup_location);
|
|
||||||
if (is_null($this->s3)) {
|
if (is_null($this->s3)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -491,20 +498,81 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$bucket = $this->s3->bucket;
|
$bucket = $this->s3->bucket;
|
||||||
$endpoint = $this->s3->endpoint;
|
$endpoint = $this->s3->endpoint;
|
||||||
$this->s3->testConnection(shouldSave: true);
|
$this->s3->testConnection(shouldSave: true);
|
||||||
$configName = new Cuid2;
|
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||||
|
$network = $this->database->service->destination->network;
|
||||||
|
} else {
|
||||||
|
$network = $this->database->destination->network;
|
||||||
|
}
|
||||||
|
|
||||||
$s3_copy_dir = str($this->backup_location)->replace(backup_dir(), '/var/www/html/storage/app/backups/');
|
$this->ensureHelperImageAvailable();
|
||||||
$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}/'";
|
$fullImageName = $this->getFullImageName();
|
||||||
|
|
||||||
|
if (isDev()) {
|
||||||
|
if ($this->database->name === 'coolify-db') {
|
||||||
|
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-'.$this->server->ip.$this->backup_file;
|
||||||
|
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
|
||||||
|
} else {
|
||||||
|
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name.$this->backup_file;
|
||||||
|
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
|
||||||
|
}
|
||||||
|
if ($this->s3->isHetzner()) {
|
||||||
|
$endpointWithoutBucket = 'https://'.str($endpoint)->after('https://')->after('.')->value();
|
||||||
|
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc alias set --path=off --api=S3v4 temporary {$endpointWithoutBucket} $key $secret";
|
||||||
|
} else {
|
||||||
|
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
|
||||||
|
}
|
||||||
|
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
||||||
instant_remote_process($commands, $this->server);
|
instant_remote_process($commands, $this->server);
|
||||||
|
|
||||||
$this->add_to_backup_output('Uploaded to S3.');
|
$this->add_to_backup_output('Uploaded to S3.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->add_to_backup_output($e->getMessage());
|
$this->add_to_backup_output($e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
$removeConfigCommands[] = "docker exec coolify bash -c 'mc config remove {$configName}'";
|
$command = "docker rm -f backup-of-{$this->backup->uuid}";
|
||||||
$removeConfigCommands[] = "docker exec coolify bash -c 'mc alias rm {$configName}'";
|
instant_remote_process([$command], $this->server);
|
||||||
instant_remote_process($removeConfigCommands, $this->server, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
$helperImage = config('coolify.helper_image');
|
||||||
|
$latestVersion = $settings->helper_version;
|
||||||
|
|
||||||
|
return "{$helperImage}:{$latestVersion}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
|
||||||
use App\Models\Team;
|
|
||||||
use App\Notifications\Database\DailyBackup;
|
|
||||||
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 DatabaseBackupStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public $tries = 1;
|
|
||||||
|
|
||||||
public function __construct() {}
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
// $teams = Team::all();
|
|
||||||
// foreach ($teams as $team) {
|
|
||||||
// $scheduled_backups = $team->scheduledDatabaseBackups()->get();
|
|
||||||
// if ($scheduled_backups->isEmpty()) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// foreach ($scheduled_backups as $scheduled_backup) {
|
|
||||||
// $last_days_backups = $scheduled_backup->get_last_days_backup_status();
|
|
||||||
// if ($last_days_backups->isEmpty()) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// $failed = $last_days_backups->where('status', 'failed');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $scheduled_backups = ScheduledDatabaseBackup::all();
|
|
||||||
// $databases = collect();
|
|
||||||
// $teams = collect();
|
|
||||||
// foreach ($scheduled_backups as $scheduled_backup) {
|
|
||||||
// $last_days_backups = $scheduled_backup->get_last_days_backup_status();
|
|
||||||
// if ($last_days_backups->isEmpty()) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// $failed = $last_days_backups->where('status', 'failed');
|
|
||||||
// $database = $scheduled_backup->database;
|
|
||||||
// $team = $database->team();
|
|
||||||
// $teams->put($team->id, $team);
|
|
||||||
// $databases->put("{$team->id}:{$database->name}", [
|
|
||||||
// 'failed_count' => $failed->count(),
|
|
||||||
// ]);
|
|
||||||
// }
|
|
||||||
// foreach ($databases as $name => $database) {
|
|
||||||
// [$team_id, $name] = explode(':', $name);
|
|
||||||
// $team = $teams->get($team_id);
|
|
||||||
// $team?->notify(new DailyBackup($databases));
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ namespace App\Jobs;
|
|||||||
|
|
||||||
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 = true,
|
||||||
public bool $deleteVolumes = false) {}
|
public bool $deleteVolumes = true,
|
||||||
|
public bool $dockerCleanup = true,
|
||||||
|
public bool $deleteConnectedNetworks = true
|
||||||
|
) {}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
@@ -51,11 +55,11 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
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 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
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::dispatch($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::dispatch($server, true);
|
||||||
|
}
|
||||||
Artisan::queue('cleanup:stucked-resources');
|
Artisan::queue('cleanup:stucked-resources');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
@@ -24,17 +23,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public ?string $usageBefore = null;
|
public ?string $usageBefore = null;
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server, public bool $manualCleanup = false) {}
|
||||||
|
|
||||||
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
|
||||||
{
|
{
|
||||||
@@ -42,8 +31,9 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
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);
|
if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) {
|
||||||
|
Log::info('DockerCleanupJob '.($this->manualCleanup ? 'manual' : 'force').' cleanup on '.$this->server->name);
|
||||||
CleanupDocker::run(server: $this->server);
|
CleanupDocker::run(server: $this->server);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ 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;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
@@ -25,16 +24,6 @@ class GithubAppPermissionJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public GithubApp $github_app) {}
|
public function __construct(public GithubApp $github_app) {}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->github_app->uuid))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->github_app->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
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;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
@@ -19,17 +17,7 @@ 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
|
||||||
{
|
{
|
||||||
@@ -37,13 +25,13 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
if ($response->successful()) {
|
if ($response->successful()) {
|
||||||
$versions = $response->json();
|
$versions = $response->json();
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$latest_version = data_get($versions, 'coolify.helper.version');
|
$latest_version = data_get($versions, 'coolify.helper.version');
|
||||||
$current_version = $settings->helper_version;
|
$current_version = $settings->helper_version;
|
||||||
if (version_compare($latest_version, $current_version, '>')) {
|
if (version_compare($latest_version, $current_version, '>')) {
|
||||||
// New version available
|
// New version available
|
||||||
$helperImage = config('coolify.helper_image');
|
// $helperImage = config('coolify.helper_image');
|
||||||
instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
|
// instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
|
||||||
$settings->update(['helper_version' => $latest_version]);
|
$settings->update(['helper_version' => $latest_version]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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;
|
||||||
|
|
||||||
class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
|
class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
@@ -18,16 +17,6 @@ class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $timeout = 1000;
|
public $timeout = 1000;
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->server->uuid))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): string
|
|
||||||
{
|
|
||||||
return $this->server->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
|
|||||||
407
app/Jobs/PushServerUpdateJob.php
Normal file
407
app/Jobs/PushServerUpdateJob.php
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Actions\Server\InstallLogDrain;
|
||||||
|
use App\Actions\Shared\ComplexStatusCheck;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
class PushServerUpdateJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $tries = 1;
|
||||||
|
|
||||||
|
public $timeout = 30;
|
||||||
|
|
||||||
|
public Collection $containers;
|
||||||
|
|
||||||
|
public Collection $applications;
|
||||||
|
|
||||||
|
public Collection $previews;
|
||||||
|
|
||||||
|
public Collection $databases;
|
||||||
|
|
||||||
|
public Collection $services;
|
||||||
|
|
||||||
|
public Collection $allApplicationIds;
|
||||||
|
|
||||||
|
public Collection $allDatabaseUuids;
|
||||||
|
|
||||||
|
public Collection $allTcpProxyUuids;
|
||||||
|
|
||||||
|
public Collection $allServiceApplicationIds;
|
||||||
|
|
||||||
|
public Collection $allApplicationPreviewsIds;
|
||||||
|
|
||||||
|
public Collection $allServiceDatabaseIds;
|
||||||
|
|
||||||
|
public Collection $allApplicationsWithAdditionalServers;
|
||||||
|
|
||||||
|
public Collection $foundApplicationIds;
|
||||||
|
|
||||||
|
public Collection $foundDatabaseUuids;
|
||||||
|
|
||||||
|
public Collection $foundServiceApplicationIds;
|
||||||
|
|
||||||
|
public Collection $foundServiceDatabaseIds;
|
||||||
|
|
||||||
|
public Collection $foundApplicationPreviewsIds;
|
||||||
|
|
||||||
|
public bool $foundProxy = false;
|
||||||
|
|
||||||
|
public bool $foundLogDrainContainer = false;
|
||||||
|
|
||||||
|
public function backoff(): int
|
||||||
|
{
|
||||||
|
return isDev() ? 1 : 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(public Server $server, public $data)
|
||||||
|
{
|
||||||
|
$this->containers = collect();
|
||||||
|
$this->foundApplicationIds = collect();
|
||||||
|
$this->foundDatabaseUuids = collect();
|
||||||
|
$this->foundServiceApplicationIds = collect();
|
||||||
|
$this->foundApplicationPreviewsIds = collect();
|
||||||
|
$this->foundServiceDatabaseIds = collect();
|
||||||
|
$this->allApplicationIds = collect();
|
||||||
|
$this->allDatabaseUuids = collect();
|
||||||
|
$this->allTcpProxyUuids = collect();
|
||||||
|
$this->allServiceApplicationIds = collect();
|
||||||
|
$this->allServiceDatabaseIds = collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (! $this->data) {
|
||||||
|
throw new \Exception('No data provided');
|
||||||
|
}
|
||||||
|
$data = collect($this->data);
|
||||||
|
|
||||||
|
$this->serverStatus();
|
||||||
|
|
||||||
|
$this->server->sentinelHeartbeat();
|
||||||
|
|
||||||
|
$this->containers = collect(data_get($data, 'containers'));
|
||||||
|
if ($this->containers->isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->applications = $this->server->applications();
|
||||||
|
$this->databases = $this->server->databases();
|
||||||
|
$this->previews = $this->server->previews();
|
||||||
|
$this->services = $this->server->services()->get();
|
||||||
|
$this->allApplicationIds = $this->applications->filter(function ($application) {
|
||||||
|
return $application->additional_servers->count() === 0;
|
||||||
|
})->pluck('id');
|
||||||
|
$this->allApplicationsWithAdditionalServers = $this->applications->filter(function ($application) {
|
||||||
|
return $application->additional_servers->count() > 0;
|
||||||
|
});
|
||||||
|
$this->allApplicationPreviewsIds = $this->previews->pluck('id');
|
||||||
|
$this->allDatabaseUuids = $this->databases->pluck('uuid');
|
||||||
|
$this->allTcpProxyUuids = $this->databases->where('is_public', true)->pluck('uuid');
|
||||||
|
$this->services->each(function ($service) {
|
||||||
|
$service->applications()->pluck('id')->each(function ($applicationId) {
|
||||||
|
$this->allServiceApplicationIds->push($applicationId);
|
||||||
|
});
|
||||||
|
$service->databases()->pluck('id')->each(function ($databaseId) {
|
||||||
|
$this->allServiceDatabaseIds->push($databaseId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ray('allServiceApplicationIds', ['allServiceApplicationIds' => $this->allServiceApplicationIds]);
|
||||||
|
|
||||||
|
foreach ($this->containers as $container) {
|
||||||
|
$containerStatus = data_get($container, 'state', 'exited');
|
||||||
|
$containerHealth = data_get($container, 'health_status', 'unhealthy');
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
|
$labels = collect(data_get($container, 'labels'));
|
||||||
|
$coolify_managed = $labels->has('coolify.managed');
|
||||||
|
if ($coolify_managed) {
|
||||||
|
$name = data_get($container, 'name');
|
||||||
|
if ($name === 'coolify-log-drain' && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundLogDrainContainer = true;
|
||||||
|
}
|
||||||
|
if ($labels->has('coolify.applicationId')) {
|
||||||
|
$applicationId = $labels->get('coolify.applicationId');
|
||||||
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId', '0');
|
||||||
|
try {
|
||||||
|
if ($pullRequestId === '0') {
|
||||||
|
if ($this->allApplicationIds->contains($applicationId) && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundApplicationIds->push($applicationId);
|
||||||
|
}
|
||||||
|
$this->updateApplicationStatus($applicationId, $containerStatus);
|
||||||
|
} else {
|
||||||
|
if ($this->allApplicationPreviewsIds->contains($applicationId) && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundApplicationPreviewsIds->push($applicationId);
|
||||||
|
}
|
||||||
|
$this->updateApplicationPreviewStatus($applicationId, $containerStatus);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray()->error($e);
|
||||||
|
}
|
||||||
|
} elseif ($labels->has('coolify.serviceId')) {
|
||||||
|
$serviceId = $labels->get('coolify.serviceId');
|
||||||
|
$subType = $labels->get('coolify.service.subType');
|
||||||
|
$subId = $labels->get('coolify.service.subId');
|
||||||
|
if ($subType === 'application' && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundServiceApplicationIds->push($subId);
|
||||||
|
$this->updateServiceSubStatus($serviceId, $subType, $subId, $containerStatus);
|
||||||
|
} elseif ($subType === 'database' && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundServiceDatabaseIds->push($subId);
|
||||||
|
$this->updateServiceSubStatus($serviceId, $subType, $subId, $containerStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$uuid = $labels->get('com.docker.compose.service');
|
||||||
|
$type = $labels->get('coolify.type');
|
||||||
|
if ($name === 'coolify-proxy' && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundProxy = true;
|
||||||
|
} elseif ($type === 'service' && $this->isRunning($containerStatus)) {
|
||||||
|
ray("Service: $uuid, $containerStatus");
|
||||||
|
} else {
|
||||||
|
if ($this->allDatabaseUuids->contains($uuid) && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundDatabaseUuids->push($uuid);
|
||||||
|
if ($this->allTcpProxyUuids->contains($uuid) && $this->isRunning($containerStatus)) {
|
||||||
|
$this->updateDatabaseStatus($uuid, $containerStatus, tcpProxy: true);
|
||||||
|
} else {
|
||||||
|
$this->updateDatabaseStatus($uuid, $containerStatus, tcpProxy: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->updateProxyStatus();
|
||||||
|
|
||||||
|
$this->updateNotFoundApplicationStatus();
|
||||||
|
$this->updateNotFoundApplicationPreviewStatus();
|
||||||
|
$this->updateNotFoundDatabaseStatus();
|
||||||
|
$this->updateNotFoundServiceStatus();
|
||||||
|
|
||||||
|
$this->updateAdditionalServersStatus();
|
||||||
|
|
||||||
|
$this->checkLogDrainContainer();
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function serverStatus()
|
||||||
|
{
|
||||||
|
if ($this->server->isFunctional() === false) {
|
||||||
|
throw new \Exception('Server is not ready.');
|
||||||
|
}
|
||||||
|
if ($this->server->status() === false) {
|
||||||
|
throw new \Exception('Server is not reachable.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateApplicationStatus(string $applicationId, string $containerStatus)
|
||||||
|
{
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if (! $application) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$application->status = $containerStatus;
|
||||||
|
$application->save();
|
||||||
|
ray('Application updated', ['application_id' => $applicationId, 'status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateApplicationPreviewStatus(string $applicationId, string $containerStatus)
|
||||||
|
{
|
||||||
|
$application = $this->previews->where('id', $applicationId)->first();
|
||||||
|
if (! $application) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$application->status = $containerStatus;
|
||||||
|
$application->save();
|
||||||
|
ray('Application preview updated', ['application_id' => $applicationId, 'status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateNotFoundApplicationStatus()
|
||||||
|
{
|
||||||
|
$notFoundApplicationIds = $this->allApplicationIds->diff($this->foundApplicationIds);
|
||||||
|
if ($notFoundApplicationIds->isNotEmpty()) {
|
||||||
|
ray('Not found application ids', ['application_ids' => $notFoundApplicationIds]);
|
||||||
|
$notFoundApplicationIds->each(function ($applicationId) {
|
||||||
|
ray('Updating application status', ['application_id' => $applicationId, 'status' => 'exited']);
|
||||||
|
$application = Application::find($applicationId);
|
||||||
|
if ($application) {
|
||||||
|
$application->status = 'exited';
|
||||||
|
$application->save();
|
||||||
|
ray('Application status updated', ['application_id' => $applicationId, 'status' => 'exited']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateNotFoundApplicationPreviewStatus()
|
||||||
|
{
|
||||||
|
$notFoundApplicationPreviewsIds = $this->allApplicationPreviewsIds->diff($this->foundApplicationPreviewsIds);
|
||||||
|
if ($notFoundApplicationPreviewsIds->isNotEmpty()) {
|
||||||
|
ray('Not found application previews ids', ['application_previews_ids' => $notFoundApplicationPreviewsIds]);
|
||||||
|
$notFoundApplicationPreviewsIds->each(function ($applicationPreviewId) {
|
||||||
|
ray('Updating application preview status', ['application_preview_id' => $applicationPreviewId, 'status' => 'exited']);
|
||||||
|
$applicationPreview = ApplicationPreview::find($applicationPreviewId);
|
||||||
|
if ($applicationPreview) {
|
||||||
|
$applicationPreview->status = 'exited';
|
||||||
|
$applicationPreview->save();
|
||||||
|
ray('Application preview status updated', ['application_preview_id' => $applicationPreviewId, 'status' => 'exited']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateProxyStatus()
|
||||||
|
{
|
||||||
|
// If proxy is not found, start it
|
||||||
|
if ($this->server->isProxyShouldRun()) {
|
||||||
|
if ($this->foundProxy === false) {
|
||||||
|
try {
|
||||||
|
if (CheckProxy::run($this->server)) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateDatabaseStatus(string $databaseUuid, string $containerStatus, bool $tcpProxy = false)
|
||||||
|
{
|
||||||
|
$database = $this->databases->where('uuid', $databaseUuid)->first();
|
||||||
|
if (! $database) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$database->status = $containerStatus;
|
||||||
|
$database->save();
|
||||||
|
ray('Database status updated', ['database_uuid' => $databaseUuid, 'status' => $containerStatus]);
|
||||||
|
if ($this->isRunning($containerStatus) && $tcpProxy) {
|
||||||
|
$tcpProxyContainerFound = $this->containers->filter(function ($value, $key) use ($databaseUuid) {
|
||||||
|
return data_get($value, 'name') === "$databaseUuid-proxy" && data_get($value, 'state') === 'running';
|
||||||
|
})->first();
|
||||||
|
if (! $tcpProxyContainerFound) {
|
||||||
|
ray('Starting TCP proxy for database', ['database_uuid' => $databaseUuid]);
|
||||||
|
StartDatabaseProxy::dispatch($database);
|
||||||
|
} else {
|
||||||
|
ray('TCP proxy for database found in containers', ['database_uuid' => $databaseUuid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateNotFoundDatabaseStatus()
|
||||||
|
{
|
||||||
|
$notFoundDatabaseUuids = $this->allDatabaseUuids->diff($this->foundDatabaseUuids);
|
||||||
|
if ($notFoundDatabaseUuids->isNotEmpty()) {
|
||||||
|
ray('Not found database uuids', ['database_uuids' => $notFoundDatabaseUuids]);
|
||||||
|
$notFoundDatabaseUuids->each(function ($databaseUuid) {
|
||||||
|
ray('Updating database status', ['database_uuid' => $databaseUuid, 'status' => 'exited']);
|
||||||
|
$database = $this->databases->where('uuid', $databaseUuid)->first();
|
||||||
|
if ($database) {
|
||||||
|
$database->status = 'exited';
|
||||||
|
$database->save();
|
||||||
|
ray('Database status updated', ['database_uuid' => $databaseUuid, 'status' => 'exited']);
|
||||||
|
ray('Database is public', ['database_uuid' => $databaseUuid, 'is_public' => $database->is_public]);
|
||||||
|
if ($database->is_public) {
|
||||||
|
ray('Stopping TCP proxy for database', ['database_uuid' => $databaseUuid]);
|
||||||
|
StopDatabaseProxy::dispatch($database);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateServiceSubStatus(string $serviceId, string $subType, string $subId, string $containerStatus)
|
||||||
|
{
|
||||||
|
$service = $this->services->where('id', $serviceId)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($subType === 'application') {
|
||||||
|
$application = $service->applications()->where('id', $subId)->first();
|
||||||
|
$application->status = $containerStatus;
|
||||||
|
$application->save();
|
||||||
|
ray('Service application updated', ['service_id' => $serviceId, 'sub_type' => $subType, 'sub_id' => $subId, 'status' => $containerStatus]);
|
||||||
|
} elseif ($subType === 'database') {
|
||||||
|
$database = $service->databases()->where('id', $subId)->first();
|
||||||
|
$database->status = $containerStatus;
|
||||||
|
$database->save();
|
||||||
|
ray('Service database updated', ['service_id' => $serviceId, 'sub_type' => $subType, 'sub_id' => $subId, 'status' => $containerStatus]);
|
||||||
|
} else {
|
||||||
|
ray()->warning('Unknown sub type', ['service_id' => $serviceId, 'sub_type' => $subType, 'sub_id' => $subId, 'status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateNotFoundServiceStatus()
|
||||||
|
{
|
||||||
|
$notFoundServiceApplicationIds = $this->allServiceApplicationIds->diff($this->foundServiceApplicationIds);
|
||||||
|
$notFoundServiceDatabaseIds = $this->allServiceDatabaseIds->diff($this->foundServiceDatabaseIds);
|
||||||
|
if ($notFoundServiceApplicationIds->isNotEmpty()) {
|
||||||
|
ray('Not found service application ids', ['service_application_ids' => $notFoundServiceApplicationIds]);
|
||||||
|
$notFoundServiceApplicationIds->each(function ($serviceApplicationId) {
|
||||||
|
ray('Updating service application status', ['service_application_id' => $serviceApplicationId, 'status' => 'exited']);
|
||||||
|
$application = ServiceApplication::find($serviceApplicationId);
|
||||||
|
if ($application) {
|
||||||
|
$application->status = 'exited';
|
||||||
|
$application->save();
|
||||||
|
ray('Service application status updated', ['service_application_id' => $serviceApplicationId, 'status' => 'exited']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($notFoundServiceDatabaseIds->isNotEmpty()) {
|
||||||
|
ray('Not found service database ids', ['service_database_ids' => $notFoundServiceDatabaseIds]);
|
||||||
|
$notFoundServiceDatabaseIds->each(function ($serviceDatabaseId) {
|
||||||
|
ray('Updating service database status', ['service_database_id' => $serviceDatabaseId, 'status' => 'exited']);
|
||||||
|
$database = ServiceDatabase::find($serviceDatabaseId);
|
||||||
|
if ($database) {
|
||||||
|
$database->status = 'exited';
|
||||||
|
$database->save();
|
||||||
|
ray('Service database status updated', ['service_database_id' => $serviceDatabaseId, 'status' => 'exited']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateAdditionalServersStatus()
|
||||||
|
{
|
||||||
|
$this->allApplicationsWithAdditionalServers->each(function ($application) {
|
||||||
|
ray('Updating additional servers status for application', ['application_id' => $application->id]);
|
||||||
|
ComplexStatusCheck::run($application);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isRunning(string $containerStatus)
|
||||||
|
{
|
||||||
|
return str($containerStatus)->contains('running');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkLogDrainContainer()
|
||||||
|
{
|
||||||
|
if ($this->server->isLogDrainEnabled() && $this->foundLogDrainContainer === false) {
|
||||||
|
InstallLogDrain::dispatch($this->server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ use Illuminate\Bus\Queueable;
|
|||||||
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;
|
||||||
|
|
||||||
class ScheduledTaskJob implements ShouldQueue
|
class ScheduledTaskJob implements ShouldQueue
|
||||||
@@ -56,24 +55,17 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
{
|
{
|
||||||
if ($this->resource instanceof Application) {
|
if ($this->resource instanceof Application) {
|
||||||
$timezone = $this->resource->destination->server->settings->server_timezone;
|
$timezone = $this->resource->destination->server->settings->server_timezone;
|
||||||
|
|
||||||
return $timezone;
|
return $timezone;
|
||||||
} elseif ($this->resource instanceof Service) {
|
} elseif ($this->resource instanceof Service) {
|
||||||
$timezone = $this->resource->server->settings->server_timezone;
|
$timezone = $this->resource->server->settings->server_timezone;
|
||||||
|
|
||||||
return $timezone;
|
return $timezone;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'UTC';
|
return 'UTC';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [new WithoutOverlapping($this->task->id)];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->task->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -94,12 +86,12 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
} elseif ($this->resource->type() == 'service') {
|
} elseif ($this->resource->type() == 'service') {
|
||||||
$this->resource->applications()->get()->each(function ($application) {
|
$this->resource->applications()->get()->each(function ($application) {
|
||||||
if (str(data_get($application, 'status'))->contains('running')) {
|
if (str(data_get($application, 'status'))->contains('running')) {
|
||||||
$this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid');
|
$this->containers[] = data_get($application, 'name').'-'.data_get($this->resource, 'uuid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$this->resource->databases()->get()->each(function ($database) {
|
$this->resource->databases()->get()->each(function ($database) {
|
||||||
if (str(data_get($database, 'status'))->contains('running')) {
|
if (str(data_get($database, 'status'))->contains('running')) {
|
||||||
$this->containers[] = data_get($database, 'name') . '-' . data_get($this->resource, 'uuid');
|
$this->containers[] = data_get($database, 'name').'-'.data_get($this->resource, 'uuid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -112,8 +104,8 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->containers as $containerName) {
|
foreach ($this->containers as $containerName) {
|
||||||
if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) {
|
if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container.'-'.$this->resource->uuid)) {
|
||||||
$cmd = "sh -c '" . str_replace("'", "'\''", $this->task->command) . "'";
|
$cmd = "sh -c '".str_replace("'", "'\''", $this->task->command)."'";
|
||||||
$exec = "docker exec {$containerName} {$cmd}";
|
$exec = "docker exec {$containerName} {$cmd}";
|
||||||
$this->task_output = instant_remote_process([$exec], $this->server, true);
|
$this->task_output = instant_remote_process([$exec], $this->server, true);
|
||||||
$this->task_log->update([
|
$this->task_log->update([
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ 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\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $tries = 3;
|
public $tries = 1;
|
||||||
|
|
||||||
public $timeout = 60;
|
public $timeout = 60;
|
||||||
|
|
||||||
@@ -45,16 +44,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
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()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -80,8 +69,10 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return 'No containers found.';
|
return 'No containers found.';
|
||||||
}
|
}
|
||||||
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
||||||
|
if ($this->server->isLogDrainEnabled()) {
|
||||||
$this->checkLogDrainContainer();
|
$this->checkLogDrainContainer();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
@@ -93,7 +84,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private function serverStatus()
|
private function serverStatus()
|
||||||
{
|
{
|
||||||
['uptime' => $uptime] = $this->server->validateConnection();
|
['uptime' => $uptime] = $this->server->validateConnection(false);
|
||||||
if ($uptime) {
|
if ($uptime) {
|
||||||
if ($this->server->unreachable_notification_sent === true) {
|
if ($this->server->unreachable_notification_sent === true) {
|
||||||
$this->server->update(['unreachable_notification_sent' => false]);
|
$this->server->update(['unreachable_notification_sent' => false]);
|
||||||
@@ -126,9 +117,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private function checkLogDrainContainer()
|
private function checkLogDrainContainer()
|
||||||
{
|
{
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
||||||
return data_get($value, 'Name') === '/coolify-log-drain';
|
return data_get($value, 'Name') === '/coolify-log-drain';
|
||||||
})->first();
|
})->first();
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ 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;
|
||||||
|
|
||||||
class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
|
class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
@@ -26,16 +25,6 @@ class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public Team $team) {}
|
public function __construct(public Team $team) {}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->team->uuid))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->team->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ 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;
|
||||||
|
|
||||||
class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
@@ -26,16 +25,6 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function middleware(): array
|
|
||||||
{
|
|
||||||
return [(new WithoutOverlapping($this->server->uuid))];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uniqueId(): int
|
|
||||||
{
|
|
||||||
return $this->server->uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (! $this->server->isServerReady($this->tries)) {
|
if (! $this->server->isServerReady($this->tries)) {
|
||||||
|
|||||||
59
app/Jobs/ServerStorageCheckJob.php
Normal file
59
app/Jobs/ServerStorageCheckJob.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $tries = 1;
|
||||||
|
|
||||||
|
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 handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (! $this->server->isFunctional()) {
|
||||||
|
ray('Server is not ready.');
|
||||||
|
|
||||||
|
return 'Server is not ready.';
|
||||||
|
}
|
||||||
|
$team = $this->server->team;
|
||||||
|
$percentage = $this->server->storageCheck();
|
||||||
|
if ($percentage > 1) {
|
||||||
|
ray('Server storage is at '.$percentage.'%');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Actions\Server\UpdateCoolify;
|
use App\Actions\Server\UpdateCoolify;
|
||||||
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;
|
||||||
@@ -23,7 +22,7 @@ class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
CheckForUpdatesJob::dispatchSync();
|
CheckForUpdatesJob::dispatchSync();
|
||||||
$settings = InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
if (! $settings->new_version_available) {
|
if (! $settings->new_version_available) {
|
||||||
Log::info('No new version available. Skipping update.');
|
Log::info('No new version available. Skipping update.');
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
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') {
|
||||||
@@ -175,7 +175,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
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->updateServerDetails();
|
||||||
$this->currentState = 'validate-server';
|
$this->currentState = 'validate-server';
|
||||||
}
|
}
|
||||||
@@ -231,17 +231,24 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
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([
|
|
||||||
|
try {
|
||||||
|
$privateKey = PrivateKey::createAndStore([
|
||||||
'name' => $this->privateKeyName,
|
'name' => $this->privateKeyName,
|
||||||
'description' => $this->privateKeyDescription,
|
'description' => $this->privateKeyDescription,
|
||||||
'private_key' => $this->privateKey,
|
'private_key' => $this->privateKey,
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
]);
|
]);
|
||||||
$this->createdPrivateKey->save();
|
|
||||||
|
$this->createdPrivateKey = $privateKey;
|
||||||
$this->currentState = 'create-server';
|
$this->currentState = 'create-server';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->addError('privateKey', 'Failed to save private key: '.$e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveServer()
|
public function saveServer()
|
||||||
|
|||||||
@@ -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,8 +30,7 @@ class Dashboard extends Component
|
|||||||
|
|
||||||
public function cleanup_queue()
|
public function cleanup_queue()
|
||||||
{
|
{
|
||||||
$this->dispatch('success', 'Cleanup started.');
|
Artisan::queue('cleanup:deployment-queue', [
|
||||||
Artisan::queue('cleanup:application-deployment-queue', [
|
|
||||||
'--team-id' => currentTeam()->id,
|
'--team-id' => currentTeam()->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class Form extends Component
|
|||||||
}
|
}
|
||||||
$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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class Show extends Component
|
|||||||
return ! $alreadyAddedNetworks->contains('network', $network['Name']);
|
return ! $alreadyAddedNetworks->contains('network', $network['Name']);
|
||||||
});
|
});
|
||||||
if ($this->networks->count() === 0) {
|
if ($this->networks->count() === 0) {
|
||||||
$this->dispatch('success', 'No new networks found.');
|
$this->dispatch('success', 'No new destinations found on this server.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class Help extends Component
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
$mail->subject("[HELP]: {$this->subject}");
|
$mail->subject("[HELP]: {$this->subject}");
|
||||||
$settings = \App\Models\InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
$type = set_transanctional_email_settings($settings);
|
$type = set_transanctional_email_settings($settings);
|
||||||
if (! $type) {
|
if (! $type) {
|
||||||
$url = 'https://app.coolify.io/api/feedback';
|
$url = 'https://app.coolify.io/api/feedback';
|
||||||
@@ -61,6 +61,7 @@ class Help extends Component
|
|||||||
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
|
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
|
||||||
}
|
}
|
||||||
$this->dispatch('success', 'Feedback sent.', 'We will get in touch with you as soon as possible.');
|
$this->dispatch('success', 'Feedback sent.', 'We will get in touch with you as soon as possible.');
|
||||||
|
$this->reset('description', 'subject');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,28 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class NavbarDeleteTeam extends Component
|
class NavbarDeleteTeam extends Component
|
||||||
{
|
{
|
||||||
public function delete()
|
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 = currentTeam();
|
||||||
$currentTeam->delete();
|
$currentTeam->delete();
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ class Email extends Component
|
|||||||
|
|
||||||
public function copyFromInstanceSettings()
|
public function copyFromInstanceSettings()
|
||||||
{
|
{
|
||||||
$settings = \App\Models\InstanceSettings::get();
|
$settings = instanceSettings();
|
||||||
if ($settings->smtp_enabled) {
|
if ($settings->smtp_enabled) {
|
||||||
$team = currentTeam();
|
$team = currentTeam();
|
||||||
$team->update([
|
$team->update([
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user