Merge branch 'next' into fix/improved-responses-on-git-error
This commit is contained in:
@@ -22,3 +22,6 @@ yarn-error.log
|
|||||||
/_data
|
/_data
|
||||||
.rnd
|
.rnd
|
||||||
/.ssh
|
/.ssh
|
||||||
|
.ignition.json
|
||||||
|
.env.dusk.local
|
||||||
|
docker/coolify-realtime/node_modules
|
||||||
|
|||||||
@@ -1,16 +1,34 @@
|
|||||||
APP_NAME=Coolify-localhost
|
# Coolify Configuration
|
||||||
APP_ID=development
|
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
|
APP_NAME="Coolify Development"
|
||||||
|
APP_ID=development
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_DEBUG=true
|
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
APP_PORT=8000
|
APP_PORT=8000
|
||||||
MUX_ENABLED=false
|
APP_DEBUG=true
|
||||||
|
SSH_MUX_ENABLED=true
|
||||||
|
|
||||||
|
# PostgreSQL Database Configuration
|
||||||
|
DB_DATABASE=coolify
|
||||||
|
DB_USERNAME=coolify
|
||||||
|
DB_PASSWORD=password
|
||||||
|
DB_HOST=host.docker.internal
|
||||||
|
DB_PORT=5432
|
||||||
|
|
||||||
|
# Ray Configuration
|
||||||
|
# Set to true to enable Ray
|
||||||
|
RAY_ENABLED=false
|
||||||
|
# Set custom ray port
|
||||||
|
# RAY_PORT=
|
||||||
|
|
||||||
|
# Enable Laravel Telescope for debugging
|
||||||
|
TELESCOPE_ENABLED=false
|
||||||
|
|
||||||
|
# Selenium Driver URL for Dusk
|
||||||
DUSK_DRIVER_URL=http://selenium:4444
|
DUSK_DRIVER_URL=http://selenium:4444
|
||||||
|
|
||||||
## For Andras only
|
# Special Keys for Andras
|
||||||
# To purge cache
|
# For cache purging
|
||||||
BUNNY_API_KEY=
|
BUNNY_API_KEY=
|
||||||
# To upload assets
|
# For asset uploads
|
||||||
BUNNY_STORAGE_API_KEY=
|
BUNNY_STORAGE_API_KEY=
|
||||||
|
|||||||
15
.env.dusk.ci
Normal file
15
.env.dusk.ci
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
APP_ENV=production
|
||||||
|
APP_NAME="Coolify Staging"
|
||||||
|
APP_ID=development
|
||||||
|
APP_KEY=
|
||||||
|
APP_URL=http://localhost
|
||||||
|
APP_PORT=8000
|
||||||
|
SSH_MUX_ENABLED=true
|
||||||
|
|
||||||
|
# PostgreSQL Database Configuration
|
||||||
|
DB_DATABASE=coolify
|
||||||
|
DB_USERNAME=coolify
|
||||||
|
DB_PASSWORD=password
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5432
|
||||||
|
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
|
# Coolify Configuration
|
||||||
APP_ID=
|
APP_ID=
|
||||||
APP_NAME=Coolify
|
APP_NAME=Coolify
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
|
|
||||||
|
# PostgreSQL Database Configuration
|
||||||
|
DB_USERNAME=coolify
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
|
|
||||||
|
# Redis Configuration
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
|
# Pusher Configuration
|
||||||
PUSHER_APP_ID=
|
PUSHER_APP_ID=
|
||||||
PUSHER_APP_KEY=
|
PUSHER_APP_KEY=
|
||||||
PUSHER_APP_SECRET=
|
PUSHER_APP_SECRET=
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ APP_ID=coolify-windows-docker-desktop
|
|||||||
APP_NAME=Coolify
|
APP_NAME=Coolify
|
||||||
APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=
|
APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=
|
||||||
|
|
||||||
|
DB_USERNAME=coolify
|
||||||
DB_PASSWORD=coolify
|
DB_PASSWORD=coolify
|
||||||
REDIS_PASSWORD=coolify
|
REDIS_PASSWORD=coolify
|
||||||
|
|
||||||
|
|||||||
65
.github/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
|
||||||
37
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
37
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
@@ -1,37 +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
|
|
||||||
20
.github/ISSUE_TEMPLATE/config.yml
vendored
20
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,18 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
|
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: 🤔 Community Support (Chat)
|
- name: 🤔 Questions and Community Support
|
||||||
url: https://coollabs.io/discord
|
url: https://coollabs.io/discord
|
||||||
about: Reach out to us on Discord.
|
about: If you have any questions, reach out to us on Discord inside the "#support" channel.
|
||||||
- name: 🙋♂️ Feature Requests
|
|
||||||
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
|
- name: 💡 Feature Request
|
||||||
about: All feature requests will be discussed here.
|
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests
|
||||||
|
about: Suggest a new feature for Coolify.
|
||||||
|
|
||||||
|
- name: ⚙️ Service Request
|
||||||
|
url: https://github.com/coollabsio/coolify/discussions/categories/service-requests
|
||||||
|
about: Request a new service integration for Coolify.
|
||||||
|
|
||||||
|
- name: 🔧 Improvements
|
||||||
|
url: https://github.com/coollabsio/coolify/discussions/categories/improvements
|
||||||
|
about: Suggest improvements to existing features for Coolify.
|
||||||
|
|||||||
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 #
|
||||||
|
|||||||
65
.github/workflows/browser-tests.yml
vendored
Normal file
65
.github/workflows/browser-tests.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
name: Dusk
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "not-existing" ]
|
||||||
|
jobs:
|
||||||
|
dusk:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
env:
|
||||||
|
REDIS_HOST: localhost
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up PostgreSQL
|
||||||
|
run: |
|
||||||
|
sudo systemctl start postgresql
|
||||||
|
sudo -u postgres psql -c "CREATE DATABASE coolify;"
|
||||||
|
sudo -u postgres psql -c "CREATE USER coolify WITH PASSWORD 'password';"
|
||||||
|
sudo -u postgres psql -c "ALTER ROLE coolify SET client_encoding TO 'utf8';"
|
||||||
|
sudo -u postgres psql -c "ALTER ROLE coolify SET default_transaction_isolation TO 'read committed';"
|
||||||
|
sudo -u postgres psql -c "ALTER ROLE coolify SET timezone TO 'UTC';"
|
||||||
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE coolify TO coolify;"
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.2'
|
||||||
|
- name: Copy .env
|
||||||
|
run: cp .env.dusk.ci .env
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: composer install --no-progress --prefer-dist --optimize-autoloader
|
||||||
|
- name: Generate key
|
||||||
|
run: php artisan key:generate
|
||||||
|
- name: Install Chrome binaries
|
||||||
|
run: php artisan dusk:chrome-driver --detect
|
||||||
|
- name: Start Chrome Driver
|
||||||
|
run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=4444 &
|
||||||
|
- name: Build assets
|
||||||
|
run: npm install && npm run build
|
||||||
|
- name: Run Laravel Server
|
||||||
|
run: php artisan serve --no-reload &
|
||||||
|
- name: Execute tests
|
||||||
|
run: php artisan dusk
|
||||||
|
- name: Upload Screenshots
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: screenshots
|
||||||
|
path: tests/Browser/screenshots
|
||||||
|
- name: Upload Console Logs
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: console
|
||||||
|
path: tests/Browser/console
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
106
.github/workflows/coolify-helper-next.yml
vendored
106
.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,21 +20,38 @@ 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: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build 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 }}: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: |
|
||||||
|
coolify.managed=true
|
||||||
aarch64:
|
aarch64:
|
||||||
runs-on: [ self-hosted, arm64 ]
|
runs-on: [ self-hosted, arm64 ]
|
||||||
permissions:
|
permissions:
|
||||||
@@ -41,21 +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: 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: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build 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 }}: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: |
|
||||||
|
coolify.managed=true
|
||||||
|
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -63,22 +99,44 @@ 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: 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: Get Version
|
||||||
|
id: version
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
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 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
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
|
|
||||||
|
|||||||
106
.github/workflows/coolify-helper.yml
vendored
106
.github/workflows/coolify-helper.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Coolify Helper Image (v4)
|
name: Coolify Helper Image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -8,7 +8,8 @@ on:
|
|||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
DOCKER_REGISTRY: docker.io
|
||||||
IMAGE_NAME: "coollabsio/coolify-helper"
|
IMAGE_NAME: "coollabsio/coolify-helper"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -19,21 +20,38 @@ 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: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build 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 }}:latest
|
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:
|
aarch64:
|
||||||
runs-on: [ self-hosted, arm64 ]
|
runs-on: [ self-hosted, arm64 ]
|
||||||
permissions:
|
permissions:
|
||||||
@@ -41,21 +59,38 @@ 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: Get Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build 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 }}:latest-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: |
|
||||||
|
coolify.managed=true
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -63,22 +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: 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: Get Version
|
||||||
|
id: version
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
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 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
|
- 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 }}
|
||||||
|
|
||||||
|
|||||||
139
.github/workflows/coolify-production-build.yml
vendored
Normal file
139
.github/workflows/coolify-production-build.yml
vendored
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
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/*
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 }}
|
||||||
147
.github/workflows/coolify-realtime-next.yml
vendored
Normal file
147
.github/workflows/coolify-realtime-next.yml
vendored
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
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/package-lock.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 }}
|
||||||
147
.github/workflows/coolify-realtime.yml
vendored
Normal file
147
.github/workflows/coolify-realtime.yml
vendored
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
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/package-lock.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 }}
|
||||||
125
.github/workflows/coolify-staging-build.yml
vendored
Normal file
125
.github/workflows/coolify-staging-build.yml
vendored
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
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/*
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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.yml
vendored
25
.github/workflows/fix-php-code-style-issues.yml
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
|
|
||||||
87
.github/workflows/production-build.yml
vendored
87
.github/workflows/production-build.yml
vendored
@@ -1,87 +0,0 @@
|
|||||||
name: Production Build (v4)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["main"]
|
|
||||||
paths-ignore:
|
|
||||||
- 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 }}
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -32,3 +32,6 @@ _ide_helper_models.php
|
|||||||
.rnd
|
.rnd
|
||||||
/.ssh
|
/.ssh
|
||||||
scripts/load-test/*
|
scripts/load-test/*
|
||||||
|
.ignition.json
|
||||||
|
.env.dusk.local
|
||||||
|
docker/coolify-realtime/node_modules
|
||||||
|
|||||||
243
CONTRIBUTING.md
Normal file
243
CONTRIBUTING.md
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
# Contributing to Coolify
|
||||||
|
|
||||||
|
> "First, thanks for considering contributing to my project. It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
|
||||||
|
|
||||||
|
You can ask for guidance anytime on our [Discord server](https://coollabs.io/discord) in the `#contribute` channel.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Setup Development Environment](#1-setup-development-environment)
|
||||||
|
2. [Verify Installation](#2-verify-installation-optional)
|
||||||
|
3. [Fork and Setup Local Repository](#3-fork-and-setup-local-repository)
|
||||||
|
4. [Set up Environment Variables](#4-set-up-environment-variables)
|
||||||
|
5. [Start Coolify](#5-start-coolify)
|
||||||
|
6. [Start Development](#6-start-development)
|
||||||
|
7. [Create a Pull Request](#7-create-a-pull-request)
|
||||||
|
8. [Development Notes](#development-notes)
|
||||||
|
9. [Resetting Development Environment](#resetting-development-environment)
|
||||||
|
10. [Additional Contribution Guidelines](#additional-contribution-guidelines)
|
||||||
|
|
||||||
|
## 1. Setup Development Environment
|
||||||
|
|
||||||
|
Follow the steps below for your operating system:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Windows</strong></summary>
|
||||||
|
|
||||||
|
1. Install `docker-ce`, Docker Desktop (or similar):
|
||||||
|
- Docker CE (recommended):
|
||||||
|
- Install Windows Subsystem for Linux v2 (WSL2) by following this guide: [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install?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/?ref=coolify)
|
||||||
|
- Make sure to choose the appropriate Linux distribution (e.g., Ubuntu) when following the Docker installation guide
|
||||||
|
- Install Docker Desktop (easier):
|
||||||
|
- Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/?ref=coolify)
|
||||||
|
- Ensure WSL2 backend is enabled in Docker Desktop settings
|
||||||
|
|
||||||
|
2. Install Spin:
|
||||||
|
- Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2?ref=coolify)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>MacOS</strong></summary>
|
||||||
|
|
||||||
|
1. Install Orbstack, Docker Desktop (or similar):
|
||||||
|
- Orbstack (recommended, as it is a faster and lighter alternative to Docker Desktop):
|
||||||
|
- Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation?ref=coolify)
|
||||||
|
- Docker Desktop:
|
||||||
|
- Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/?ref=coolify)
|
||||||
|
|
||||||
|
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?ref=coolify)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Linux</strong></summary>
|
||||||
|
|
||||||
|
1. Install Docker Engine, Docker Desktop (or similar):
|
||||||
|
- Docker Engine (recommended, as there is no VM overhead):
|
||||||
|
- Follow the official [Docker Engine installation guide](https://docs.docker.com/engine/install/?ref=coolify) for your Linux distribution
|
||||||
|
- Docker Desktop:
|
||||||
|
- If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/?ref=coolify)
|
||||||
|
|
||||||
|
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?ref=coolify)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 2. Verify Installation (Optional)
|
||||||
|
|
||||||
|
After installing Docker (or Orbstack) and Spin, verify the installation:
|
||||||
|
|
||||||
|
1. Open a terminal or command prompt
|
||||||
|
2. Run the following commands:
|
||||||
|
```bash
|
||||||
|
docker --version
|
||||||
|
spin --version
|
||||||
|
```
|
||||||
|
You should see version information for both Docker and Spin.
|
||||||
|
|
||||||
|
## 3. Fork and Setup Local Repository
|
||||||
|
|
||||||
|
1. Fork the [Coolify](https://github.com/coollabsio/coolify) repository to your GitHub account.
|
||||||
|
|
||||||
|
2. Install a code editor on your machine (choose one):
|
||||||
|
|
||||||
|
| Editor | Platform | Download Link |
|
||||||
|
|--------|----------|---------------|
|
||||||
|
| Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download?ref=coolify) |
|
||||||
|
| Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/?ref=coolify) |
|
||||||
|
| Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download?ref=coolify) |
|
||||||
|
|
||||||
|
3. Clone the Coolify Repository from your fork to your local machine
|
||||||
|
- Use `git clone` in the command line, or
|
||||||
|
- Use GitHub Desktop (recommended):
|
||||||
|
- Download and install from [https://desktop.github.com/](https://desktop.github.com/?ref=coolify)
|
||||||
|
- Open GitHub Desktop and login with your GitHub account
|
||||||
|
- Click on `File` -> `Clone Repository` select `github.com` as the repository location, then select your forked Coolify repository, choose the local path and then click `Clone`
|
||||||
|
|
||||||
|
4. Open the cloned Coolify Repository in your chosen code editor.
|
||||||
|
|
||||||
|
## 4. Set up Environment Variables
|
||||||
|
|
||||||
|
1. In the Code Editor, locate the `.env.development.example` file in the root directory of your local Coolify repository.
|
||||||
|
2. Duplicate the `.env.development.example` file and rename the copy to `.env`.
|
||||||
|
3. Open the new `.env` file and review its contents. Adjust any environment variables as needed for your development setup.
|
||||||
|
4. If you encounter errors during database migrations, update the database connection settings in your `.env` file. Use the IP address or hostname of your PostgreSQL database container. You can find this information by running `docker ps` after executing `spin up`.
|
||||||
|
5. Save the changes to your `.env` file.
|
||||||
|
|
||||||
|
## 5. Start Coolify
|
||||||
|
|
||||||
|
1. Open a terminal in the local Coolify directory.
|
||||||
|
2. Run the following command in the terminal (leave that terminal open):
|
||||||
|
```bash
|
||||||
|
spin up
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> You may see some errors, but don't worry; this is expected.
|
||||||
|
|
||||||
|
3. If you encounter permission errors, especially on macOS, use:
|
||||||
|
```bash
|
||||||
|
sudo spin up
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If you change environment variables afterwards or anything seems broken, press Ctrl + C to stop the process and run `spin up` again.
|
||||||
|
|
||||||
|
## 6. Start Development
|
||||||
|
|
||||||
|
1. Access your Coolify instance:
|
||||||
|
- URL: `http://localhost:8000`
|
||||||
|
- Login: `test@example.com`
|
||||||
|
- Password: `password`
|
||||||
|
|
||||||
|
2. Additional development tools:
|
||||||
|
| Tool | URL | Note |
|
||||||
|
|------|-----|------|
|
||||||
|
| Laravel Horizon (scheduler) | `http://localhost:8000/horizon` | Only accessible when logged in as root user |
|
||||||
|
| Mailpit (email catcher) | `http://localhost:8025` | |
|
||||||
|
| Telescope (debugging tool) | `http://localhost:8000/telescope` | Disabled by default |
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> To enable Telescope, add the following to your `.env` file:
|
||||||
|
> ```env
|
||||||
|
> TELESCOPE_ENABLED=true
|
||||||
|
> ```
|
||||||
|
|
||||||
|
## 7. 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:
|
||||||
|
|
||||||
|
1. **Database Migrations**: After switching branches or making changes to the database structure, always run migrations:
|
||||||
|
```bash
|
||||||
|
docker exec -it coolify php artisan migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Resetting Development Setup**: To reset your development setup to a clean database with default values:
|
||||||
|
```bash
|
||||||
|
docker exec -it coolify php artisan migrate:fresh --seed
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Troubleshooting**: If you encounter unexpected behavior, ensure your database is up-to-date with the latest migrations and if possible reset the development setup to eliminate any environment-specific issues.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches.
|
||||||
|
|
||||||
|
## Resetting Development Environment
|
||||||
|
|
||||||
|
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`):
|
||||||
|
|
||||||
|
1. Stop all running containers `ctrl + c`.
|
||||||
|
|
||||||
|
2. Remove all Coolify containers:
|
||||||
|
```bash
|
||||||
|
docker rm coolify coolify-db coolify-redis coolify-realtime coolify-testing-host coolify-minio coolify-vite-1 coolify-mail
|
||||||
|
```
|
||||||
|
|
||||||
|
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]
|
||||||
|
> 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.
|
||||||
|
|
||||||
|
## Additional Contribution Guidelines
|
||||||
|
|
||||||
|
### Contributing a New Service
|
||||||
|
|
||||||
|
To add a new service to Coolify, please refer to our documentation:
|
||||||
|
[Adding a New Service](https://coolify.io/docs/knowledge-base/contribute/service)
|
||||||
|
|
||||||
|
### Contributing to Documentation
|
||||||
|
|
||||||
|
To contribute to the Coolify documentation, please refer to this guide:
|
||||||
|
[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/CONTRIBUTING.md)
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
> "First, thanks for considering to contribute to my project.
|
|
||||||
It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
|
|
||||||
|
|
||||||
You can ask for guidance anytime on our
|
|
||||||
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
|
||||||
|
|
||||||
## Code Contribution
|
|
||||||
|
|
||||||
### 1) Setup your development environment
|
|
||||||
|
|
||||||
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
|
|
||||||
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
|
|
||||||
|
|
||||||
### 2) Set your environment variables
|
|
||||||
|
|
||||||
- Copy [.env.development.example](./.env.development.example) to .env.
|
|
||||||
|
|
||||||
## 3) Start & setup Coolify
|
|
||||||
|
|
||||||
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
|
|
||||||
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
|
|
||||||
|
|
||||||
### 4) Start development
|
|
||||||
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
|
||||||
|
|
||||||
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
|
||||||
|
|
||||||
Mails are caught by Mailpit: `localhost:8025`
|
|
||||||
|
|
||||||
## New Service Contribution
|
|
||||||
Check out the docs [here](https://coolify.io/docs/knowledge-base/add-a-service).
|
|
||||||
|
|
||||||
79
README.md
79
README.md
@@ -1,17 +1,19 @@
|
|||||||
|

|
||||||
|
|
||||||
[](https://console.algora.io/org/coollabsio/bounties/new)
|
[](https://console.algora.io/org/coollabsio/bounties/new)
|
||||||
[](https://console.algora.io/org/coollabsio/bounties?status=open)
|
|
||||||
[](https://console.algora.io/org/coollabsio/bounties?status=completed)
|
|
||||||
# About the Project
|
# About the Project
|
||||||
|
|
||||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
||||||
|
|
||||||
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
|
It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else.
|
||||||
|
|
||||||
Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
|
Imagine having the ease of a cloud but with your own servers. That is **Coolify**.
|
||||||
|
|
||||||
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
|
No vendor lock-in, which means that all the configurations for your applications/databases/etc are saved to your server. So, if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You lose the automations and all the magic. 🪄️
|
||||||
|
|
||||||
For more information, take a look at our landing page [here](https://coolify.io).
|
For more information, take a look at our landing page at [coolify.io](https://coolify.io).
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
@@ -22,36 +24,56 @@ You can find the installation script source [here](./scripts/install.sh).
|
|||||||
|
|
||||||
# Support
|
# Support
|
||||||
|
|
||||||
Contact us [here](https://coolify.io/docs/contact).
|
Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
|
||||||
|
|
||||||
# Donations
|
# Donations
|
||||||
To stay completely free, open-source, no feature behind paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the future development of the project.
|
To stay completely free and open-source, with no feature behind the paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the project's future development.
|
||||||
|
|
||||||
https://coolify.io/sponsorships
|
[coolify.io/sponsorships](https://coolify.io/sponsorships)
|
||||||
|
|
||||||
Thank you so much!
|
Thank you so much!
|
||||||
|
|
||||||
Special thanks to our biggest sponsors!
|
Special thanks to our biggest sponsors!
|
||||||
|
|
||||||
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
|
### Special Sponsors
|
||||||
<a href="http://htznr.li/CoolifyXHetzner" target="_blank"><img src="./other/logos/hetzner.jpg" alt="hetzner logo" width="200"/></a>
|
|
||||||
<a href="https://logto.io/?ref=coolify" target="_blank"><img src="./other/logos/logto.webp" alt="logto logo" width="200"/></a>
|

|
||||||
<a href="https://bc.direct/?utm_source=coolify.io" target="_blank"><img src="./other/logos/bc.png" alt="bc direct logo" width="200"/></a>
|
|
||||||
<a href="https://www.quantcdn.io/?utm_source=coolify.io" target="_blank"><img src="./other/logos/quant.svg" alt="quantcdn logo" width="200"/></a>
|
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
|
||||||
<a href="https://arcjet.com/?utm_source=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a>
|
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
|
||||||
|
* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities.
|
||||||
|
* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies.
|
||||||
|
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution.
|
||||||
|
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks.
|
||||||
|
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
|
||||||
|
* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management.
|
||||||
|
* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions.
|
||||||
|
* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies.
|
||||||
|
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets.
|
||||||
|
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers.
|
||||||
|
* [Latitude](https://latitude.sh/?ref=coolify.io) - A cloud computing platform offering bare metal servers and cloud instances for developers and businesses.
|
||||||
|
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities.
|
||||||
|
* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries.
|
||||||
|
* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools.
|
||||||
|
* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services.
|
||||||
|
* [Ubicloud](https://ubicloud.com/?ref=coolify.io) - An open-source alternative to hyperscale cloud providers, offering high-performance cloud computing services.
|
||||||
|
* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses.
|
||||||
|
* [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly.
|
||||||
|
* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
||||||
|
|
||||||
|
|
||||||
## Github Sponsors ($40+)
|
## Github Sponsors ($40+)
|
||||||
<a href="https://serpapi.com/?utm_source=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
||||||
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
<a href="https://typebot.io/?ref=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
||||||
<a href="https://www.runpod.io/?utm_source=coolify.io">
|
<a href="https://www.runpod.io/?ref=coolify.io">
|
||||||
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
|
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
|
||||||
<a href="https://lightspeed.run/?utm_source=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
<a href="https://lightspeed.run/?ref=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
||||||
<a href="https://www.flint.sh/en/home?utm_source=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
|
<a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
|
||||||
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
<a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
||||||
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
<a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
||||||
<a href="https://x.com/mrsmith9ja?utm_source=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
|
<a href="https://codext.link/coolify-io?ref=coolify.io"><img src="./other/logos/codext.jpg" width="60px" alt="Codext" /></a>
|
||||||
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
|
<a href="https://x.com/mrsmith9ja?ref=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
|
||||||
|
<a href="https://www.uxwizz.com/?ref=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
|
||||||
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
|
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
|
||||||
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>
|
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>
|
||||||
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
|
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
|
||||||
@@ -61,8 +83,11 @@ Special thanks to our biggest sponsors!
|
|||||||
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
|
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
|
||||||
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
|
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
|
||||||
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
|
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
|
||||||
|
<a href="https://www.breakcold.com/?utm_source=coolify.io"><img src="https://github.com/breakcold.png" width="60px" alt="Breakcold" /></a>
|
||||||
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
|
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
|
||||||
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
||||||
|
<a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a>
|
||||||
|
<a href="https://x.com/adithsuhas17?utm_source=coolify.io"><img src="https://github.com/adith-suhas-sv.png" width="60px" alt="Adith Suhas" /></a>
|
||||||
|
|
||||||
## Organizations
|
## Organizations
|
||||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||||
@@ -83,9 +108,9 @@ Special thanks to our biggest sponsors!
|
|||||||
|
|
||||||
# Cloud
|
# Cloud
|
||||||
|
|
||||||
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
If you do not want to self-host Coolify, there is a paid cloud version available: [app.coolify.io](https://app.coolify.io)
|
||||||
|
|
||||||
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
For more information & pricing, take a look at our landing page [coolify.io](https://coolify.io).
|
||||||
|
|
||||||
## Why should I use the Cloud version?
|
## Why should I use the Cloud version?
|
||||||
The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month.
|
The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month.
|
||||||
@@ -109,7 +134,7 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://www.producthunt.com/posts/coolify?ref=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
|
|||||||
130
RELEASE.md
Normal file
130
RELEASE.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Coolify Release Guide
|
||||||
|
|
||||||
|
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
1. **Development on `next` or Feature Branches**
|
||||||
|
- Improvements, fixes, and new features are developed on the `next` branch or separate feature branches.
|
||||||
|
|
||||||
|
2. **Merging to `main`**
|
||||||
|
- Once ready, changes are merged from the `next` branch into the `main` branch.
|
||||||
|
|
||||||
|
3. **Building the Release**
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
4. **Creating a GitHub Release**
|
||||||
|
- A new GitHub release is manually created with details of the changes made in the version.
|
||||||
|
|
||||||
|
5. **Updating 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)
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> 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
|
||||||
|
|
||||||
|
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]
|
||||||
|
> 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
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Updating to unreleased versions is not recommended and may cause issues. Use at your own risk!
|
||||||
|
|
||||||
|
To update your Coolify instance to a specific (unreleased) version, use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s <version>
|
||||||
|
```
|
||||||
|
Replace `<version>` with the version you want to update to (for example `4.0.0-beta.332`).
|
||||||
16
app/Actions/Application/GenerateConfig.php
Normal file
16
app/Actions/Application/GenerateConfig.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?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)
|
||||||
|
{
|
||||||
|
return $application->generateConfig(is_json: $is_json);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
app/Actions/Application/IsHorizonQueueEmpty.php
Normal file
37
app/Actions/Application/IsHorizonQueueEmpty.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use Laravel\Horizon\Contracts\JobRepository;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class IsHorizonQueueEmpty
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$hostname = gethostname();
|
||||||
|
$recent = app(JobRepository::class)->getRecent();
|
||||||
|
if ($recent) {
|
||||||
|
$running = $recent->filter(function ($job) use ($hostname) {
|
||||||
|
$payload = json_decode($job->payload);
|
||||||
|
$tags = data_get($payload, 'tags');
|
||||||
|
|
||||||
|
return $job->status != 'completed' &&
|
||||||
|
$job->status != 'failed' &&
|
||||||
|
isset($tags) &&
|
||||||
|
is_array($tags) &&
|
||||||
|
in_array('server:'.$hostname, $tags);
|
||||||
|
});
|
||||||
|
if ($running->count() > 0) {
|
||||||
|
echo 'false';
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo 'true';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/Actions/Application/LoadComposeFile.php
Normal file
16
app/Actions/Application/LoadComposeFile.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class LoadComposeFile
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Application $application)
|
||||||
|
{
|
||||||
|
$application->loadComposeFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,35 +10,32 @@ class StopApplication
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Application $application)
|
public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true)
|
||||||
{
|
{
|
||||||
if ($application->destination->server->isSwarm()) {
|
try {
|
||||||
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
$server = $application->destination->server;
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
if ($application->build_pack === 'dockercompose') {
|
||||||
});
|
$application->delete_connected_networks($application->uuid);
|
||||||
foreach ($servers as $server) {
|
|
||||||
if (! $server->isFunctional()) {
|
|
||||||
return 'Server is not functional';
|
|
||||||
}
|
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
|
||||||
if ($containers->count() > 0) {
|
|
||||||
foreach ($containers as $container) {
|
|
||||||
$containerName = data_get($container, 'Names');
|
|
||||||
if ($containerName) {
|
|
||||||
instant_remote_process(
|
|
||||||
["docker rm -f {$containerName}"],
|
|
||||||
$server
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::dispatch($server, true);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ class StopApplicationOneServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
ray($e->getMessage());
|
|
||||||
|
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Actions\CoolifyTask;
|
namespace App\Actions\CoolifyTask;
|
||||||
|
|
||||||
use App\Data\CoolifyTaskArgs;
|
use App\Data\CoolifyTaskArgs;
|
||||||
|
use App\Enums\ActivityTypes;
|
||||||
use App\Jobs\CoolifyTask;
|
use App\Jobs\CoolifyTask;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
@@ -40,8 +41,17 @@ class PrepareCoolifyTask
|
|||||||
|
|
||||||
public function __invoke(): Activity
|
public function __invoke(): Activity
|
||||||
{
|
{
|
||||||
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish, call_event_data: $this->remoteProcessArgs->call_event_data);
|
$job = new CoolifyTask(
|
||||||
|
activity: $this->activity,
|
||||||
|
ignore_errors: $this->remoteProcessArgs->ignore_errors,
|
||||||
|
call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish,
|
||||||
|
call_event_data: $this->remoteProcessArgs->call_event_data,
|
||||||
|
);
|
||||||
|
if ($this->remoteProcessArgs->type === ActivityTypes::COMMAND->value) {
|
||||||
|
dispatch($job)->onQueue('high');
|
||||||
|
} else {
|
||||||
dispatch($job);
|
dispatch($job);
|
||||||
|
}
|
||||||
$this->activity->refresh();
|
$this->activity->refresh();
|
||||||
|
|
||||||
return $this->activity;
|
return $this->activity;
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ 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;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
@@ -38,8 +40,7 @@ class RunRemoteProcess
|
|||||||
*/
|
*/
|
||||||
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null, $call_event_data = null)
|
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null, $call_event_data = null)
|
||||||
{
|
{
|
||||||
|
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value && $activity->getExtraProperty('type') !== ActivityTypes::COMMAND->value) {
|
||||||
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
|
|
||||||
throw new \RuntimeException('Incompatible Activity to run a remote command.');
|
throw new \RuntimeException('Incompatible Activity to run a remote command.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +125,7 @@ class RunRemoteProcess
|
|||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e);
|
Log::error('Error calling event: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|||||||
29
app/Actions/Database/RestartDatabase.php
Normal file
29
app/Actions/Database/RestartDatabase.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RestartDatabase
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
StopDatabase::run($database);
|
||||||
|
|
||||||
|
return StartDatabase::run($database);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneClickhouse;
|
use App\Models\StandaloneClickhouse;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@@ -52,6 +51,8 @@ class StartClickhouse
|
|||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => [
|
||||||
'coolify.managed' => 'true',
|
'coolify.managed' => 'true',
|
||||||
|
'coolify.type' => 'database',
|
||||||
|
'coolify.databaseId' => $this->database->id,
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => "clickhouse-client --password {$this->database->clickhouse_admin_password} --query 'SELECT 1'",
|
'test' => "clickhouse-client --password {$this->database->clickhouse_admin_password} --query 'SELECT 1'",
|
||||||
@@ -80,14 +81,7 @@ class StartClickhouse
|
|||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -103,6 +97,11 @@ class StartClickhouse
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -155,14 +154,16 @@ class StartClickhouse
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
|
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
|
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
57
app/Actions/Database/StartDatabase.php
Normal file
57
app/Actions/Database/StartDatabase.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartDatabase
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
switch ($database->getMorphClass()) {
|
||||||
|
case \App\Models\StandalonePostgresql::class:
|
||||||
|
$activity = StartPostgresql::run($database);
|
||||||
|
break;
|
||||||
|
case \App\Models\StandaloneRedis::class:
|
||||||
|
$activity = StartRedis::run($database);
|
||||||
|
break;
|
||||||
|
case \App\Models\StandaloneMongodb::class:
|
||||||
|
$activity = StartMongodb::run($database);
|
||||||
|
break;
|
||||||
|
case \App\Models\StandaloneMysql::class:
|
||||||
|
$activity = StartMysql::run($database);
|
||||||
|
break;
|
||||||
|
case \App\Models\StandaloneMariadb::class:
|
||||||
|
$activity = StartMariadb::run($database);
|
||||||
|
break;
|
||||||
|
case \App\Models\StandaloneKeydb::class:
|
||||||
|
$activity = StartKeydb::run($database);
|
||||||
|
break;
|
||||||
|
case \App\Models\StandaloneDragonfly::class:
|
||||||
|
$activity = StartDragonfly::run($database);
|
||||||
|
break;
|
||||||
|
case \App\Models\StandaloneClickhouse::class:
|
||||||
|
$activity = StartClickhouse::run($database);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($database->is_public && $database->public_port) {
|
||||||
|
StartDatabaseProxy::dispatch($database)->onQueue('high');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $activity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ class StartDatabaseProxy
|
|||||||
$server = data_get($database, 'destination.server');
|
$server = data_get($database, 'destination.server');
|
||||||
$containerName = data_get($database, 'uuid');
|
$containerName = data_get($database, 'uuid');
|
||||||
$proxyContainerName = "{$database->uuid}-proxy";
|
$proxyContainerName = "{$database->uuid}-proxy";
|
||||||
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
if ($database->getMorphClass() === \App\Models\ServiceDatabase::class) {
|
||||||
$databaseType = $database->databaseType();
|
$databaseType = $database->databaseType();
|
||||||
// $connectPredefined = data_get($database, 'service.connect_to_docker_network');
|
// $connectPredefined = data_get($database, 'service.connect_to_docker_network');
|
||||||
$network = $database->service->uuid;
|
$network = $database->service->uuid;
|
||||||
@@ -34,54 +34,54 @@ class StartDatabaseProxy
|
|||||||
$proxyContainerName = "{$database->service->uuid}-proxy";
|
$proxyContainerName = "{$database->service->uuid}-proxy";
|
||||||
switch ($databaseType) {
|
switch ($databaseType) {
|
||||||
case 'standalone-mariadb':
|
case 'standalone-mariadb':
|
||||||
$type = 'App\Models\StandaloneMariadb';
|
$type = \App\Models\StandaloneMariadb::class;
|
||||||
$containerName = "mariadb-{$database->service->uuid}";
|
$containerName = "mariadb-{$database->service->uuid}";
|
||||||
break;
|
break;
|
||||||
case 'standalone-mongodb':
|
case 'standalone-mongodb':
|
||||||
$type = 'App\Models\StandaloneMongodb';
|
$type = \App\Models\StandaloneMongodb::class;
|
||||||
$containerName = "mongodb-{$database->service->uuid}";
|
$containerName = "mongodb-{$database->service->uuid}";
|
||||||
break;
|
break;
|
||||||
case 'standalone-mysql':
|
case 'standalone-mysql':
|
||||||
$type = 'App\Models\StandaloneMysql';
|
$type = \App\Models\StandaloneMysql::class;
|
||||||
$containerName = "mysql-{$database->service->uuid}";
|
$containerName = "mysql-{$database->service->uuid}";
|
||||||
break;
|
break;
|
||||||
case 'standalone-postgresql':
|
case 'standalone-postgresql':
|
||||||
$type = 'App\Models\StandalonePostgresql';
|
$type = \App\Models\StandalonePostgresql::class;
|
||||||
$containerName = "postgresql-{$database->service->uuid}";
|
$containerName = "postgresql-{$database->service->uuid}";
|
||||||
break;
|
break;
|
||||||
case 'standalone-redis':
|
case 'standalone-redis':
|
||||||
$type = 'App\Models\StandaloneRedis';
|
$type = \App\Models\StandaloneRedis::class;
|
||||||
$containerName = "redis-{$database->service->uuid}";
|
$containerName = "redis-{$database->service->uuid}";
|
||||||
break;
|
break;
|
||||||
case 'standalone-keydb':
|
case 'standalone-keydb':
|
||||||
$type = 'App\Models\StandaloneKeydb';
|
$type = \App\Models\StandaloneKeydb::class;
|
||||||
$containerName = "keydb-{$database->service->uuid}";
|
$containerName = "keydb-{$database->service->uuid}";
|
||||||
break;
|
break;
|
||||||
case 'standalone-dragonfly':
|
case 'standalone-dragonfly':
|
||||||
$type = 'App\Models\StandaloneDragonfly';
|
$type = \App\Models\StandaloneDragonfly::class;
|
||||||
$containerName = "dragonfly-{$database->service->uuid}";
|
$containerName = "dragonfly-{$database->service->uuid}";
|
||||||
break;
|
break;
|
||||||
case 'standalone-clickhouse':
|
case 'standalone-clickhouse':
|
||||||
$type = 'App\Models\StandaloneClickhouse';
|
$type = \App\Models\StandaloneClickhouse::class;
|
||||||
$containerName = "clickhouse-{$database->service->uuid}";
|
$containerName = "clickhouse-{$database->service->uuid}";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($type === 'App\Models\StandaloneRedis') {
|
if ($type === \App\Models\StandaloneRedis::class) {
|
||||||
$internalPort = 6379;
|
$internalPort = 6379;
|
||||||
} elseif ($type === 'App\Models\StandalonePostgresql') {
|
} elseif ($type === \App\Models\StandalonePostgresql::class) {
|
||||||
$internalPort = 5432;
|
$internalPort = 5432;
|
||||||
} elseif ($type === 'App\Models\StandaloneMongodb') {
|
} elseif ($type === \App\Models\StandaloneMongodb::class) {
|
||||||
$internalPort = 27017;
|
$internalPort = 27017;
|
||||||
} elseif ($type === 'App\Models\StandaloneMysql') {
|
} elseif ($type === \App\Models\StandaloneMysql::class) {
|
||||||
$internalPort = 3306;
|
$internalPort = 3306;
|
||||||
} elseif ($type === 'App\Models\StandaloneMariadb') {
|
} elseif ($type === \App\Models\StandaloneMariadb::class) {
|
||||||
$internalPort = 3306;
|
$internalPort = 3306;
|
||||||
} elseif ($type === 'App\Models\StandaloneKeydb') {
|
} elseif ($type === \App\Models\StandaloneKeydb::class) {
|
||||||
$internalPort = 6379;
|
$internalPort = 6379;
|
||||||
} elseif ($type === 'App\Models\StandaloneDragonfly') {
|
} elseif ($type === \App\Models\StandaloneDragonfly::class) {
|
||||||
$internalPort = 6379;
|
$internalPort = 6379;
|
||||||
} elseif ($type === 'App\Models\StandaloneClickhouse') {
|
} elseif ($type === \App\Models\StandaloneClickhouse::class) {
|
||||||
$internalPort = 9000;
|
$internalPort = 9000;
|
||||||
}
|
}
|
||||||
$configuration_dir = database_proxy_dir($database->uuid);
|
$configuration_dir = database_proxy_dir($database->uuid);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneDragonfly;
|
use App\Models\StandaloneDragonfly;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@@ -47,11 +46,10 @@ class StartDragonfly
|
|||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network,
|
$this->database->destination->network,
|
||||||
],
|
],
|
||||||
'ulimits' => [
|
|
||||||
'memlock' => '-1',
|
|
||||||
],
|
|
||||||
'labels' => [
|
'labels' => [
|
||||||
'coolify.managed' => 'true',
|
'coolify.managed' => 'true',
|
||||||
|
'coolify.type' => 'database',
|
||||||
|
'coolify.databaseId' => $this->database->id,
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => "redis-cli -a {$this->database->dragonfly_password} ping",
|
'test' => "redis-cli -a {$this->database->dragonfly_password} ping",
|
||||||
@@ -80,14 +78,7 @@ class StartDragonfly
|
|||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -103,6 +94,11 @@ class StartDragonfly
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -155,7 +151,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::of($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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Actions\Database;
|
|||||||
|
|
||||||
use App\Models\StandaloneKeydb;
|
use App\Models\StandaloneKeydb;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@@ -51,6 +50,8 @@ class StartKeydb
|
|||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => [
|
||||||
'coolify.managed' => 'true',
|
'coolify.managed' => 'true',
|
||||||
|
'coolify.type' => 'database',
|
||||||
|
'coolify.databaseId' => $this->database->id,
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => "keydb-cli --pass {$this->database->keydb_password} ping",
|
'test' => "keydb-cli --pass {$this->database->keydb_password} ping",
|
||||||
@@ -79,14 +80,7 @@ class StartKeydb
|
|||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -111,6 +105,10 @@ class StartKeydb
|
|||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes";
|
$docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -163,10 +161,12 @@ class StartKeydb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneMariadb;
|
use App\Models\StandaloneMariadb;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@@ -46,6 +45,8 @@ class StartMariadb
|
|||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => [
|
||||||
'coolify.managed' => 'true',
|
'coolify.managed' => 'true',
|
||||||
|
'coolify.type' => 'database',
|
||||||
|
'coolify.databaseId' => $this->database->id,
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'],
|
'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'],
|
||||||
@@ -74,14 +75,7 @@ class StartMariadb
|
|||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -105,6 +99,11 @@ class StartMariadb
|
|||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -157,21 +156,23 @@ class StartMariadb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($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::of($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::of($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::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneMongodb;
|
use App\Models\StandaloneMongodb;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@@ -26,6 +25,10 @@ class StartMongodb
|
|||||||
$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;
|
||||||
|
|
||||||
|
if (isDev()) {
|
||||||
|
$this->configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name;
|
||||||
|
}
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
@@ -50,6 +53,8 @@ class StartMongodb
|
|||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => [
|
||||||
'coolify.managed' => 'true',
|
'coolify.managed' => 'true',
|
||||||
|
'coolify.type' => 'database',
|
||||||
|
'coolify.databaseId' => $this->database->id,
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
@@ -82,14 +87,7 @@ class StartMongodb
|
|||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -122,6 +120,10 @@ class StartMongodb
|
|||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -174,18 +176,20 @@ class StartMongodb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($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::of($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::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandaloneMysql;
|
use App\Models\StandaloneMysql;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@@ -46,6 +45,8 @@ class StartMysql
|
|||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => [
|
||||||
'coolify.managed' => 'true',
|
'coolify.managed' => 'true',
|
||||||
|
'coolify.type' => 'database',
|
||||||
|
'coolify.databaseId' => $this->database->id,
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"],
|
'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"],
|
||||||
@@ -74,14 +75,7 @@ class StartMysql
|
|||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -105,6 +99,11 @@ class StartMysql
|
|||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -157,21 +156,23 @@ class StartMysql
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($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::of($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::of($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::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@@ -50,6 +49,8 @@ class StartPostgresql
|
|||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => [
|
||||||
'coolify.managed' => 'true',
|
'coolify.managed' => 'true',
|
||||||
|
'coolify.type' => 'database',
|
||||||
|
'coolify.databaseId' => $this->database->id,
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
@@ -81,14 +82,7 @@ class StartPostgresql
|
|||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -127,6 +121,10 @@ class StartPostgresql
|
|||||||
'config_file=/etc/postgresql/postgresql.conf',
|
'config_file=/etc/postgresql/postgresql.conf',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -179,21 +177,23 @@ class StartPostgresql
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PGUSER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('PGUSER'))->isEmpty()) {
|
||||||
$environment_variables->push("PGUSER={$this->database->postgres_user}");
|
$environment_variables->push("PGUSER={$this->database->postgres_user}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
||||||
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Actions\Database;
|
|||||||
|
|
||||||
use App\Models\StandaloneRedis;
|
use App\Models\StandaloneRedis;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@@ -22,8 +21,6 @@ class StartRedis
|
|||||||
{
|
{
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
@@ -38,6 +35,8 @@ class StartRedis
|
|||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->add_custom_redis();
|
$this->add_custom_redis();
|
||||||
|
|
||||||
|
$startCommand = $this->buildStartCommand();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
@@ -51,6 +50,8 @@ class StartRedis
|
|||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => [
|
||||||
'coolify.managed' => 'true',
|
'coolify.managed' => 'true',
|
||||||
|
'coolify.type' => 'database',
|
||||||
|
'coolify.databaseId' => $this->database->id,
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
@@ -83,14 +84,7 @@ class StartRedis
|
|||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration();
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
|
||||||
'fluentd-async' => 'true',
|
|
||||||
'fluentd-sub-second-precision' => 'true',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (count($this->database->ports_mappings_array) > 0) {
|
if (count($this->database->ports_mappings_array) > 0) {
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||||
@@ -113,8 +107,12 @@ class StartRedis
|
|||||||
'target' => '/usr/local/etc/redis/redis.conf',
|
'target' => '/usr/local/etc/redis/redis.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -163,17 +161,54 @@ class StartRedis
|
|||||||
private function generate_environment_variables()
|
private function generate_environment_variables()
|
||||||
{
|
{
|
||||||
$environment_variables = collect();
|
$environment_variables = collect();
|
||||||
|
|
||||||
foreach ($this->database->runtime_environment_variables as $env) {
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
|
if ($env->is_shared) {
|
||||||
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
|
|
||||||
|
if ($env->key === 'REDIS_PASSWORD') {
|
||||||
|
$this->database->update(['redis_password' => $env->real_value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($env->key === 'REDIS_USERNAME') {
|
||||||
|
$this->database->update(['redis_username' => $env->real_value]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($env->key === 'REDIS_PASSWORD') {
|
||||||
|
$env->update(['value' => $this->database->redis_password]);
|
||||||
|
} elseif ($env->key === 'REDIS_USERNAME') {
|
||||||
|
$env->update(['value' => $this->database->redis_username]);
|
||||||
|
}
|
||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildStartCommand(): string
|
||||||
|
{
|
||||||
|
$hasRedisConf = ! is_null($this->database->redis_conf) && ! empty($this->database->redis_conf);
|
||||||
|
$redisConfPath = '/usr/local/etc/redis/redis.conf';
|
||||||
|
|
||||||
|
if ($hasRedisConf) {
|
||||||
|
$confContent = $this->database->redis_conf;
|
||||||
|
$hasRequirePass = str_contains($confContent, 'requirepass');
|
||||||
|
|
||||||
|
if ($hasRequirePass) {
|
||||||
|
$command = "redis-server $redisConfPath";
|
||||||
|
} else {
|
||||||
|
$command = "redis-server $redisConfPath --requirepass {$this->database->redis_password}";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$command = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $command;
|
||||||
|
}
|
||||||
|
|
||||||
private function add_custom_redis()
|
private function add_custom_redis()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) {
|
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) {
|
||||||
|
|||||||
@@ -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,26 +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(
|
|
||||||
["docker rm -f {$database->uuid}"],
|
$this->stopContainer($database, $database->uuid, 300);
|
||||||
$server
|
if (! $isDeleteOperation) {
|
||||||
);
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::dispatch($server, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($database->is_public) {
|
if ($database->is_public) {
|
||||||
StopDatabaseProxy::run($database);
|
StopDatabaseProxy::run($database);
|
||||||
}
|
}
|
||||||
// TODO: make notification for services
|
|
||||||
// $database->environment->project->team->notify(new StatusChanged($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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Events\DatabaseProxyStopped;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
use App\Models\StandaloneClickhouse;
|
use App\Models\StandaloneClickhouse;
|
||||||
use App\Models\StandaloneDragonfly;
|
use App\Models\StandaloneDragonfly;
|
||||||
@@ -21,12 +22,16 @@ class StopDatabaseProxy
|
|||||||
{
|
{
|
||||||
$server = data_get($database, 'destination.server');
|
$server = data_get($database, 'destination.server');
|
||||||
$uuid = $database->uuid;
|
$uuid = $database->uuid;
|
||||||
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
if ($database->getMorphClass() === \App\Models\ServiceDatabase::class) {
|
||||||
$uuid = $database->service->uuid;
|
$uuid = $database->service->uuid;
|
||||||
$server = data_get($database, 'service.server');
|
$server = data_get($database, 'service.server');
|
||||||
}
|
}
|
||||||
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
||||||
|
|
||||||
$database->is_public = false;
|
$database->is_public = false;
|
||||||
$database->save();
|
$database->save();
|
||||||
|
|
||||||
|
DatabaseProxyStopped::dispatch();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,13 @@
|
|||||||
namespace App\Actions\Docker;
|
namespace App\Actions\Docker;
|
||||||
|
|
||||||
use App\Actions\Database\StartDatabaseProxy;
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
use App\Actions\Proxy\CheckProxy;
|
|
||||||
use App\Actions\Proxy\StartProxy;
|
|
||||||
use App\Actions\Shared\ComplexStatusCheck;
|
use App\Actions\Shared\ComplexStatusCheck;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
use App\Notifications\Container\ContainerRestarted;
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
use App\Notifications\Container\ContainerStopped;
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class GetContainersStatus
|
class GetContainersStatus
|
||||||
@@ -20,16 +18,19 @@ class GetContainersStatus
|
|||||||
|
|
||||||
public $applications;
|
public $applications;
|
||||||
|
|
||||||
|
public ?Collection $containers;
|
||||||
|
|
||||||
|
public ?Collection $containerReplicates;
|
||||||
|
|
||||||
public $server;
|
public $server;
|
||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server, ?Collection $containers = null, ?Collection $containerReplicates = null)
|
||||||
{
|
{
|
||||||
// if (isDev()) {
|
$this->containers = $containers;
|
||||||
// $server = Server::find(0);
|
$this->containerReplicates = $containerReplicates;
|
||||||
// }
|
|
||||||
$this->server = $server;
|
$this->server = $server;
|
||||||
if (! $this->server->isFunctional()) {
|
if (! $this->server->isFunctional()) {
|
||||||
return 'Server is not ready.';
|
return 'Server is not functional.';
|
||||||
}
|
}
|
||||||
$this->applications = $this->server->applications();
|
$this->applications = $this->server->applications();
|
||||||
$skip_these_applications = collect([]);
|
$skip_these_applications = collect([]);
|
||||||
@@ -45,343 +46,18 @@ class GetContainersStatus
|
|||||||
$this->applications = $this->applications->filter(function ($value, $key) use ($skip_these_applications) {
|
$this->applications = $this->applications->filter(function ($value, $key) use ($skip_these_applications) {
|
||||||
return ! $skip_these_applications->pluck('id')->contains($value->id);
|
return ! $skip_these_applications->pluck('id')->contains($value->id);
|
||||||
});
|
});
|
||||||
$this->old_way();
|
if ($this->containers === null) {
|
||||||
// if ($this->server->isSwarm()) {
|
['containers' => $this->containers, 'containerReplicates' => $this->containerReplicates] = $this->server->getContainers();
|
||||||
// $this->old_way();
|
|
||||||
// } else {
|
|
||||||
// if (!$this->server->is_metrics_enabled) {
|
|
||||||
// $this->old_way();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// $sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this->server, false);
|
|
||||||
// $sentinel_found = json_decode($sentinel_found, true);
|
|
||||||
// $status = data_get($sentinel_found, '0.State.Status', 'exited');
|
|
||||||
// if ($status === 'running') {
|
|
||||||
// ray('Checking with Sentinel');
|
|
||||||
// $this->sentinel();
|
|
||||||
// } else {
|
|
||||||
// ray('Checking the Old way');
|
|
||||||
// $this->old_way();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sentinel()
|
if (is_null($this->containers)) {
|
||||||
{
|
|
||||||
try {
|
|
||||||
$containers = $this->server->getContainers();
|
|
||||||
if ($containers->count() === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$databases = $this->server->databases();
|
|
||||||
$services = $this->server->services()->get();
|
|
||||||
$previews = $this->server->previews();
|
|
||||||
$foundApplications = [];
|
|
||||||
$foundApplicationPreviews = [];
|
|
||||||
$foundDatabases = [];
|
|
||||||
$foundServices = [];
|
|
||||||
|
|
||||||
foreach ($containers as $container) {
|
|
||||||
$labels = Arr::undot(data_get($container, 'labels'));
|
|
||||||
$containerStatus = data_get($container, 'state');
|
|
||||||
$containerHealth = data_get($container, 'health_status', 'unhealthy');
|
|
||||||
$containerStatus = "$containerStatus ($containerHealth)";
|
|
||||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
|
||||||
if ($applicationId) {
|
|
||||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
|
||||||
if ($pullRequestId) {
|
|
||||||
if (str($applicationId)->contains('-')) {
|
|
||||||
$applicationId = str($applicationId)->before('-');
|
|
||||||
}
|
|
||||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
|
||||||
if ($preview) {
|
|
||||||
$foundApplicationPreviews[] = $preview->id;
|
|
||||||
$statusFromDb = $preview->status;
|
|
||||||
if ($statusFromDb !== $containerStatus) {
|
|
||||||
$preview->update(['status' => $containerStatus]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//Notify user that this container should not be there.
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$application = $this->applications->where('id', $applicationId)->first();
|
|
||||||
if ($application) {
|
|
||||||
$foundApplications[] = $application->id;
|
|
||||||
$statusFromDb = $application->status;
|
|
||||||
if ($statusFromDb !== $containerStatus) {
|
|
||||||
$application->update(['status' => $containerStatus]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//Notify user that this container should not be there.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$uuid = data_get($labels, 'com.docker.compose.service');
|
|
||||||
$type = data_get($labels, 'coolify.type');
|
|
||||||
if ($uuid) {
|
|
||||||
if ($type === 'service') {
|
|
||||||
$database_id = data_get($labels, 'coolify.service.subId');
|
|
||||||
if ($database_id) {
|
|
||||||
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
|
||||||
if ($service_db) {
|
|
||||||
$uuid = $service_db->service->uuid;
|
|
||||||
$isPublic = data_get($service_db, 'is_public');
|
|
||||||
if ($isPublic) {
|
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
|
||||||
if ($this->server->isSwarm()) {
|
|
||||||
// TODO: fix this with sentinel
|
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
|
||||||
} else {
|
|
||||||
return data_get($value, 'name') === "$uuid-proxy";
|
|
||||||
}
|
|
||||||
})->first();
|
|
||||||
if (! $foundTcpProxy) {
|
|
||||||
StartDatabaseProxy::run($service_db);
|
|
||||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$database = $databases->where('uuid', $uuid)->first();
|
|
||||||
if ($database) {
|
|
||||||
$isPublic = data_get($database, 'is_public');
|
|
||||||
$foundDatabases[] = $database->id;
|
|
||||||
$statusFromDb = $database->status;
|
|
||||||
if ($statusFromDb !== $containerStatus) {
|
|
||||||
$database->update(['status' => $containerStatus]);
|
|
||||||
}
|
|
||||||
if ($isPublic) {
|
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
|
||||||
if ($this->server->isSwarm()) {
|
|
||||||
// TODO: fix this with sentinel
|
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
|
||||||
} else {
|
|
||||||
return data_get($value, 'name') === "$uuid-proxy";
|
|
||||||
}
|
|
||||||
})->first();
|
|
||||||
if (! $foundTcpProxy) {
|
|
||||||
StartDatabaseProxy::run($database);
|
|
||||||
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Notify user that this container should not be there.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data_get($container, 'name') === 'coolify-db') {
|
|
||||||
$foundDatabases[] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
|
||||||
if ($serviceLabelId) {
|
|
||||||
$subType = data_get($labels, 'coolify.service.subType');
|
|
||||||
$subId = data_get($labels, 'coolify.service.subId');
|
|
||||||
$service = $services->where('id', $serviceLabelId)->first();
|
|
||||||
if (! $service) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($subType === 'application') {
|
|
||||||
$service = $service->applications()->where('id', $subId)->first();
|
|
||||||
} else {
|
|
||||||
$service = $service->databases()->where('id', $subId)->first();
|
|
||||||
}
|
|
||||||
if ($service) {
|
|
||||||
$foundServices[] = "$service->id-$service->name";
|
|
||||||
$statusFromDb = $service->status;
|
|
||||||
if ($statusFromDb !== $containerStatus) {
|
|
||||||
// ray('Updating status: ' . $containerStatus);
|
|
||||||
$service->update(['status' => $containerStatus]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$exitedServices = collect([]);
|
|
||||||
foreach ($services as $service) {
|
|
||||||
$apps = $service->applications()->get();
|
|
||||||
$dbs = $service->databases()->get();
|
|
||||||
foreach ($apps as $app) {
|
|
||||||
if (in_array("$app->id-$app->name", $foundServices)) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
$exitedServices->push($app);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($dbs as $db) {
|
|
||||||
if (in_array("$db->id-$db->name", $foundServices)) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
$exitedServices->push($db);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$exitedServices = $exitedServices->unique('id');
|
|
||||||
foreach ($exitedServices as $exitedService) {
|
|
||||||
if (str($exitedService->status)->startsWith('exited')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$name = data_get($exitedService, 'name');
|
|
||||||
$fqdn = data_get($exitedService, 'fqdn');
|
|
||||||
if ($name) {
|
|
||||||
if ($fqdn) {
|
|
||||||
$containerName = "$name, available at $fqdn";
|
|
||||||
} else {
|
|
||||||
$containerName = $name;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ($fqdn) {
|
|
||||||
$containerName = $fqdn;
|
|
||||||
} else {
|
|
||||||
$containerName = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$projectUuid = data_get($service, 'environment.project.uuid');
|
|
||||||
$serviceUuid = data_get($service, 'uuid');
|
|
||||||
$environmentName = data_get($service, 'environment.name');
|
|
||||||
|
|
||||||
if ($projectUuid && $serviceUuid && $environmentName) {
|
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
|
|
||||||
} else {
|
|
||||||
$url = null;
|
|
||||||
}
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
|
||||||
$exitedService->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
|
||||||
foreach ($notRunningApplications as $applicationId) {
|
|
||||||
$application = $this->applications->where('id', $applicationId)->first();
|
|
||||||
if (str($application->status)->startsWith('exited')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$application->update(['status' => 'exited']);
|
|
||||||
|
|
||||||
$name = data_get($application, 'name');
|
|
||||||
$fqdn = data_get($application, 'fqdn');
|
|
||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
|
||||||
|
|
||||||
$projectUuid = data_get($application, 'environment.project.uuid');
|
|
||||||
$applicationUuid = data_get($application, 'uuid');
|
|
||||||
$environment = data_get($application, 'environment.name');
|
|
||||||
|
|
||||||
if ($projectUuid && $applicationUuid && $environment) {
|
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
|
|
||||||
} else {
|
|
||||||
$url = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
|
||||||
}
|
|
||||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
|
||||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
|
||||||
$preview = $previews->where('id', $previewId)->first();
|
|
||||||
if (str($preview->status)->startsWith('exited')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$preview->update(['status' => 'exited']);
|
|
||||||
|
|
||||||
$name = data_get($preview, 'name');
|
|
||||||
$fqdn = data_get($preview, 'fqdn');
|
|
||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
|
||||||
|
|
||||||
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
|
||||||
$environmentName = data_get($preview, 'application.environment.name');
|
|
||||||
$applicationUuid = data_get($preview, 'application.uuid');
|
|
||||||
|
|
||||||
if ($projectUuid && $applicationUuid && $environmentName) {
|
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
|
|
||||||
} else {
|
|
||||||
$url = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
|
||||||
}
|
|
||||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
|
||||||
foreach ($notRunningDatabases as $database) {
|
|
||||||
$database = $databases->where('id', $database)->first();
|
|
||||||
if (str($database->status)->startsWith('exited')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$database->update(['status' => 'exited']);
|
|
||||||
|
|
||||||
$name = data_get($database, 'name');
|
|
||||||
$fqdn = data_get($database, 'fqdn');
|
|
||||||
|
|
||||||
$containerName = $name;
|
|
||||||
|
|
||||||
$projectUuid = data_get($database, 'environment.project.uuid');
|
|
||||||
$environmentName = data_get($database, 'environment.name');
|
|
||||||
$databaseUuid = data_get($database, 'uuid');
|
|
||||||
|
|
||||||
if ($projectUuid && $databaseUuid && $environmentName) {
|
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
|
|
||||||
} else {
|
|
||||||
$url = null;
|
|
||||||
}
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if proxy is running
|
|
||||||
$this->server->proxyType();
|
|
||||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
|
||||||
if ($this->server->isSwarm()) {
|
|
||||||
// TODO: fix this with sentinel
|
|
||||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
|
||||||
} else {
|
|
||||||
return data_get($value, 'name') === 'coolify-proxy';
|
|
||||||
}
|
|
||||||
})->first();
|
|
||||||
if (! $foundProxyContainer) {
|
|
||||||
try {
|
|
||||||
$shouldStart = CheckProxy::run($this->server);
|
|
||||||
if ($shouldStart) {
|
|
||||||
StartProxy::run($this->server, false);
|
|
||||||
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
ray($e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->server->proxy->status = data_get($foundProxyContainer, 'state');
|
|
||||||
$this->server->save();
|
|
||||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
|
||||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
|
||||||
ray($e->getMessage());
|
|
||||||
|
|
||||||
return handleError($e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function old_way()
|
|
||||||
{
|
|
||||||
if ($this->server->isSwarm()) {
|
|
||||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
|
||||||
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
|
||||||
} else {
|
|
||||||
// Precheck for containers
|
|
||||||
$containers = instant_remote_process(['docker container ls -q'], $this->server, false);
|
|
||||||
if (! $containers) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
|
||||||
$containerReplicates = null;
|
|
||||||
}
|
|
||||||
if (is_null($containers)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$containers = format_docker_command_output_to_json($containers);
|
if ($this->containerReplicates) {
|
||||||
if ($containerReplicates) {
|
foreach ($this->containerReplicates as $containerReplica) {
|
||||||
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
|
||||||
foreach ($containerReplicates as $containerReplica) {
|
|
||||||
$name = data_get($containerReplica, 'Name');
|
$name = data_get($containerReplica, 'Name');
|
||||||
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
$this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
|
||||||
if (data_get($container, 'Spec.Name') === $name) {
|
if (data_get($container, 'Spec.Name') === $name) {
|
||||||
$replicas = data_get($containerReplica, 'Replicas');
|
$replicas = data_get($containerReplica, 'Replicas');
|
||||||
$running = str($replicas)->explode('/')[0];
|
$running = str($replicas)->explode('/')[0];
|
||||||
@@ -407,7 +83,7 @@ class GetContainersStatus
|
|||||||
$foundDatabases = [];
|
$foundDatabases = [];
|
||||||
$foundServices = [];
|
$foundServices = [];
|
||||||
|
|
||||||
foreach ($containers as $container) {
|
foreach ($this->containers as $container) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
$labels = data_get($container, 'Spec.Labels');
|
$labels = data_get($container, 'Spec.Labels');
|
||||||
$uuid = data_get($labels, 'coolify.name');
|
$uuid = data_get($labels, 'coolify.name');
|
||||||
@@ -431,6 +107,8 @@ class GetContainersStatus
|
|||||||
$statusFromDb = $preview->status;
|
$statusFromDb = $preview->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
$preview->update(['status' => $containerStatus]);
|
$preview->update(['status' => $containerStatus]);
|
||||||
|
} else {
|
||||||
|
$preview->update(['last_online_at' => now()]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//Notify user that this container should not be there.
|
//Notify user that this container should not be there.
|
||||||
@@ -442,6 +120,8 @@ class GetContainersStatus
|
|||||||
$statusFromDb = $application->status;
|
$statusFromDb = $application->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
$application->update(['status' => $containerStatus]);
|
$application->update(['status' => $containerStatus]);
|
||||||
|
} else {
|
||||||
|
$application->update(['last_online_at' => now()]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//Notify user that this container should not be there.
|
//Notify user that this container should not be there.
|
||||||
@@ -461,7 +141,7 @@ class GetContainersStatus
|
|||||||
if ($uuid) {
|
if ($uuid) {
|
||||||
$isPublic = data_get($service_db, 'is_public');
|
$isPublic = data_get($service_db, 'is_public');
|
||||||
if ($isPublic) {
|
if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
} else {
|
||||||
@@ -484,9 +164,12 @@ class GetContainersStatus
|
|||||||
$statusFromDb = $database->status;
|
$statusFromDb = $database->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
$database->update(['status' => $containerStatus]);
|
$database->update(['status' => $containerStatus]);
|
||||||
|
} else {
|
||||||
|
$database->update(['last_online_at' => now()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isPublic) {
|
if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
} else {
|
||||||
@@ -495,7 +178,7 @@ class GetContainersStatus
|
|||||||
})->first();
|
})->first();
|
||||||
if (! $foundTcpProxy) {
|
if (! $foundTcpProxy) {
|
||||||
StartDatabaseProxy::run($database);
|
StartDatabaseProxy::run($database);
|
||||||
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -526,6 +209,8 @@ class GetContainersStatus
|
|||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
// ray('Updating status: ' . $containerStatus);
|
// ray('Updating status: ' . $containerStatus);
|
||||||
$service->update(['status' => $containerStatus]);
|
$service->update(['status' => $containerStatus]);
|
||||||
|
} else {
|
||||||
|
$service->update(['last_online_at' => now()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -549,7 +234,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;
|
||||||
@@ -656,31 +341,5 @@ 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
|
|
||||||
$this->server->proxyType();
|
|
||||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
|
||||||
if ($this->server->isSwarm()) {
|
|
||||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
|
||||||
} else {
|
|
||||||
return data_get($value, 'Name') === '/coolify-proxy';
|
|
||||||
}
|
|
||||||
})->first();
|
|
||||||
if (! $foundProxyContainer) {
|
|
||||||
try {
|
|
||||||
$shouldStart = CheckProxy::run($this->server);
|
|
||||||
if ($shouldStart) {
|
|
||||||
StartProxy::run($this->server, false);
|
|
||||||
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
ray($e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
|
||||||
$this->server->save();
|
|
||||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
|
||||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,15 @@
|
|||||||
|
|
||||||
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;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
use Laravel\Fortify\Contracts\CreatesNewUsers;
|
use Laravel\Fortify\Contracts\CreatesNewUsers;
|
||||||
|
|
||||||
class CreateNewUser implements CreatesNewUsers
|
class CreateNewUser implements CreatesNewUsers
|
||||||
{
|
{
|
||||||
use PasswordValidationRules;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and create a newly registered user.
|
* Validate and create a newly registered user.
|
||||||
*
|
*
|
||||||
@@ -20,7 +18,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);
|
||||||
}
|
}
|
||||||
@@ -33,7 +31,7 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
'max:255',
|
'max:255',
|
||||||
Rule::unique(User::class),
|
Rule::unique(User::class),
|
||||||
],
|
],
|
||||||
'password' => $this->passwordRules(),
|
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||||
])->validate();
|
])->validate();
|
||||||
|
|
||||||
if (User::count() == 0) {
|
if (User::count() == 0) {
|
||||||
@@ -42,19 +40,19 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
$user = User::create([
|
$user = User::create([
|
||||||
'id' => 0,
|
'id' => 0,
|
||||||
'name' => $input['name'],
|
'name' => $input['name'],
|
||||||
'email' => $input['email'],
|
'email' => strtolower($input['email']),
|
||||||
'password' => Hash::make($input['password']),
|
'password' => Hash::make($input['password']),
|
||||||
]);
|
]);
|
||||||
$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 {
|
||||||
$user = User::create([
|
$user = User::create([
|
||||||
'name' => $input['name'],
|
'name' => $input['name'],
|
||||||
'email' => $input['email'],
|
'email' => strtolower($input['email']),
|
||||||
'password' => Hash::make($input['password']),
|
'password' => Hash::make($input['password']),
|
||||||
]);
|
]);
|
||||||
$team = $user->teams()->first();
|
$team = $user->teams()->first();
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Actions\Fortify;
|
|
||||||
|
|
||||||
use Laravel\Fortify\Rules\Password;
|
|
||||||
|
|
||||||
trait PasswordValidationRules
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the validation rules used to validate passwords.
|
|
||||||
*
|
|
||||||
* @return array<int, \Illuminate\Contracts\Validation\Rule|array|string>
|
|
||||||
*/
|
|
||||||
protected function passwordRules(): array
|
|
||||||
{
|
|
||||||
return ['required', 'string', new Password, 'confirmed'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,12 +5,11 @@ namespace App\Actions\Fortify;
|
|||||||
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;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
||||||
|
|
||||||
class ResetUserPassword implements ResetsUserPasswords
|
class ResetUserPassword implements ResetsUserPasswords
|
||||||
{
|
{
|
||||||
use PasswordValidationRules;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and reset the user's forgotten password.
|
* Validate and reset the user's forgotten password.
|
||||||
*
|
*
|
||||||
@@ -19,7 +18,7 @@ class ResetUserPassword implements ResetsUserPasswords
|
|||||||
public function reset(User $user, array $input): void
|
public function reset(User $user, array $input): void
|
||||||
{
|
{
|
||||||
Validator::make($input, [
|
Validator::make($input, [
|
||||||
'password' => $this->passwordRules(),
|
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||||
])->validate();
|
])->validate();
|
||||||
|
|
||||||
$user->forceFill([
|
$user->forceFill([
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ namespace App\Actions\Fortify;
|
|||||||
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;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
|
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
|
||||||
|
|
||||||
class UpdateUserPassword implements UpdatesUserPasswords
|
class UpdateUserPassword implements UpdatesUserPasswords
|
||||||
{
|
{
|
||||||
use PasswordValidationRules;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and update the user's password.
|
* Validate and update the user's password.
|
||||||
*
|
*
|
||||||
@@ -20,7 +19,7 @@ class UpdateUserPassword implements UpdatesUserPasswords
|
|||||||
{
|
{
|
||||||
Validator::make($input, [
|
Validator::make($input, [
|
||||||
'current_password' => ['required', 'string', 'current_password:web'],
|
'current_password' => ['required', 'string', 'current_password:web'],
|
||||||
'password' => $this->passwordRules(),
|
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||||
], [
|
], [
|
||||||
'current_password.current_password' => __('The provided password does not match your current password.'),
|
'current_password.current_password' => __('The provided password does not match your current password.'),
|
||||||
])->validateWithBag('updatePassword');
|
])->validateWithBag('updatePassword');
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -26,8 +25,6 @@ class CheckResaleLicense
|
|||||||
// }
|
// }
|
||||||
$base_url = config('coolify.license_url');
|
$base_url = config('coolify.license_url');
|
||||||
$instance_id = config('app.id');
|
$instance_id = config('app.id');
|
||||||
|
|
||||||
ray("Checking license key against $base_url/lemon/validate");
|
|
||||||
$data = Http::withHeaders([
|
$data = Http::withHeaders([
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
])->get("$base_url/lemon/validate", [
|
])->get("$base_url/lemon/validate", [
|
||||||
@@ -35,7 +32,6 @@ class CheckResaleLicense
|
|||||||
'instance_id' => $instance_id,
|
'instance_id' => $instance_id,
|
||||||
])->json();
|
])->json();
|
||||||
if (data_get($data, 'valid') === true && data_get($data, 'license_key.status') === 'active') {
|
if (data_get($data, 'valid') === true && data_get($data, 'license_key.status') === 'active') {
|
||||||
ray('Valid & active license key');
|
|
||||||
$settings->update([
|
$settings->update([
|
||||||
'is_resale_license_active' => true,
|
'is_resale_license_active' => true,
|
||||||
]);
|
]);
|
||||||
@@ -49,7 +45,6 @@ class CheckResaleLicense
|
|||||||
'instance_id' => $instance_id,
|
'instance_id' => $instance_id,
|
||||||
])->json();
|
])->json();
|
||||||
if (data_get($data, 'activated') === true) {
|
if (data_get($data, 'activated') === true) {
|
||||||
ray('Activated license key');
|
|
||||||
$settings->update([
|
$settings->update([
|
||||||
'is_resale_license_active' => true,
|
'is_resale_license_active' => true,
|
||||||
]);
|
]);
|
||||||
@@ -61,7 +56,6 @@ class CheckResaleLicense
|
|||||||
}
|
}
|
||||||
throw new \Exception('Cannot activate license key.');
|
throw new \Exception('Cannot activate license key.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e);
|
|
||||||
$settings->update([
|
$settings->update([
|
||||||
'resale_license' => null,
|
'resale_license' => null,
|
||||||
'is_resale_license_active' => false,
|
'is_resale_license_active' => false,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class CheckConfiguration
|
class CheckConfiguration
|
||||||
@@ -22,9 +21,8 @@ class CheckConfiguration
|
|||||||
"cat $proxy_path/docker-compose.yml",
|
"cat $proxy_path/docker-compose.yml",
|
||||||
];
|
];
|
||||||
$proxy_configuration = instant_remote_process($payload, $server, false);
|
$proxy_configuration = instant_remote_process($payload, $server, false);
|
||||||
|
|
||||||
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
||||||
$proxy_configuration = Str::of(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,18 @@
|
|||||||
|
|
||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
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 +30,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 +66,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) {
|
||||||
|
Log::error('Error checking proxy: '.$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;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class SaveConfiguration
|
class SaveConfiguration
|
||||||
@@ -18,7 +17,7 @@ class SaveConfiguration
|
|||||||
$proxy_path = $server->proxyPath();
|
$proxy_path = $server->proxyPath();
|
||||||
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
||||||
|
|
||||||
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_saved_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$server->save();
|
$server->save();
|
||||||
|
|
||||||
return instant_remote_process([
|
return instant_remote_process([
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Actions\Proxy;
|
|||||||
|
|
||||||
use App\Events\ProxyStarted;
|
use App\Events\ProxyStarted;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
@@ -12,11 +11,10 @@ class StartProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, bool $async = true): string|Activity
|
public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
|
||||||
{
|
{
|
||||||
try {
|
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) {
|
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
@@ -27,7 +25,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::of($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([
|
||||||
@@ -36,7 +34,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';
|
||||||
@@ -47,19 +45,20 @@ 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($async) {
|
if ($async) {
|
||||||
$activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
|
return remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
|
||||||
|
|
||||||
return $activity;
|
|
||||||
} else {
|
} else {
|
||||||
instant_remote_process($commands, $server);
|
instant_remote_process($commands, $server);
|
||||||
$server->proxy->set('status', 'running');
|
$server->proxy->set('status', 'running');
|
||||||
@@ -69,9 +68,5 @@ class StartProxy
|
|||||||
|
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
|
||||||
ray($e);
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,31 @@ class CleanupDocker
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, bool $force = true)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
if ($force) {
|
$settings = instanceSettings();
|
||||||
instant_remote_process(['docker image prune -af'], $server, false);
|
$helperImageVersion = data_get($settings, 'helper_version');
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
$helperImage = config('coolify.helper_image');
|
||||||
instant_remote_process(['docker builder prune -af'], $server, false);
|
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
||||||
} else {
|
|
||||||
instant_remote_process(['docker image prune -f'], $server, false);
|
$commands = [
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
'docker container prune -f --filter "label=coolify.managed=true" --filter "label!=coolify.proxy=true"',
|
||||||
instant_remote_process(['docker builder prune -f'], $server, false);
|
'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) {
|
||||||
|
instant_remote_process([$command], $server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -39,9 +40,12 @@ class ConfigureCloudflared
|
|||||||
]);
|
]);
|
||||||
instant_remote_process($commands, $server);
|
instant_remote_process($commands, $server);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $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',
|
||||||
]);
|
]);
|
||||||
|
|||||||
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,12 +12,11 @@ class InstallDocker
|
|||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
|
$dockerVersion = config('constants.docker_install_version');
|
||||||
$supported_os_type = $server->validateOS();
|
$supported_os_type = $server->validateOS();
|
||||||
if (! $supported_os_type) {
|
if (! $supported_os_type) {
|
||||||
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);
|
|
||||||
$dockerVersion = '24.0';
|
|
||||||
$config = base64_encode('{
|
$config = base64_encode('{
|
||||||
"log-driver": "json-file",
|
"log-driver": "json-file",
|
||||||
"log-opts": {
|
"log-opts": {
|
||||||
|
|||||||
41
app/Actions/Server/ResourcesCheck.php
Normal file
41
app/Actions/Server/ResourcesCheck.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ResourcesCheck
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$seconds = 60;
|
||||||
|
try {
|
||||||
|
Application::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
ServiceApplication::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
ServiceDatabase::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandalonePostgresql::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneRedis::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneMongodb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneMysql::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneMariadb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneKeydb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneDragonfly::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneClickhouse::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/Actions/Server/RestartContainer.php
Normal file
16
app/Actions/Server/RestartContainer.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RestartContainer
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server, string $containerName)
|
||||||
|
{
|
||||||
|
$server->restartContainer($containerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/Actions/Server/RunCommand.php
Normal file
17
app/Actions/Server/RunCommand.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Enums\ActivityTypes;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RunCommand
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server, $command)
|
||||||
|
{
|
||||||
|
return remote_process(command: [$command], server: $server, ignore_errors: true, type: ActivityTypes::COMMAND->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
269
app/Actions/Server/ServerCheck.php
Normal file
269
app/Actions/Server/ServerCheck.php
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Jobs\CheckAndStartSentinelJob;
|
||||||
|
use App\Jobs\ServerStorageCheckJob;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ServerCheck
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public Server $server;
|
||||||
|
|
||||||
|
public bool $isSentinel = false;
|
||||||
|
|
||||||
|
public $containers;
|
||||||
|
|
||||||
|
public $databases;
|
||||||
|
|
||||||
|
public function handle(Server $server, $data = null)
|
||||||
|
{
|
||||||
|
$this->server = $server;
|
||||||
|
try {
|
||||||
|
if ($this->server->isFunctional() === false) {
|
||||||
|
return 'Server is not functional.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->server->isSwarmWorker() && ! $this->server->isBuildServer()) {
|
||||||
|
|
||||||
|
if (isset($data)) {
|
||||||
|
$data = collect($data);
|
||||||
|
|
||||||
|
$this->server->sentinelHeartbeat();
|
||||||
|
|
||||||
|
$this->containers = collect(data_get($data, 'containers'));
|
||||||
|
|
||||||
|
$filesystemUsageRoot = data_get($data, 'filesystem_usage_root.used_percentage');
|
||||||
|
ServerStorageCheckJob::dispatch($this->server, $filesystemUsageRoot);
|
||||||
|
|
||||||
|
$containerReplicates = null;
|
||||||
|
$this->isSentinel = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
|
||||||
|
// ServerStorageCheckJob::dispatch($this->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($this->containers)) {
|
||||||
|
return 'No containers found.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($containerReplicates)) {
|
||||||
|
foreach ($containerReplicates as $containerReplica) {
|
||||||
|
$name = data_get($containerReplica, 'Name');
|
||||||
|
$this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
|
||||||
|
if (data_get($container, 'Spec.Name') === $name) {
|
||||||
|
$replicas = data_get($containerReplica, 'Replicas');
|
||||||
|
$running = str($replicas)->explode('/')[0];
|
||||||
|
$total = str($replicas)->explode('/')[1];
|
||||||
|
if ($running === $total) {
|
||||||
|
data_set($container, 'State.Status', 'running');
|
||||||
|
data_set($container, 'State.Health.Status', 'healthy');
|
||||||
|
} else {
|
||||||
|
data_set($container, 'State.Status', 'starting');
|
||||||
|
data_set($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $container;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->checkContainers();
|
||||||
|
|
||||||
|
if ($this->server->isSentinelEnabled() && $this->isSentinel === false) {
|
||||||
|
CheckAndStartSentinelJob::dispatch($this->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->server->isLogDrainEnabled()) {
|
||||||
|
$this->checkLogDrainContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->server->proxySet() && ! $this->server->proxy->force_stop) {
|
||||||
|
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundProxyContainer) {
|
||||||
|
try {
|
||||||
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkLogDrainContainer()
|
||||||
|
{
|
||||||
|
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
||||||
|
return data_get($value, 'Name') === '/coolify-log-drain';
|
||||||
|
})->first();
|
||||||
|
if ($foundLogDrainContainer) {
|
||||||
|
$status = data_get($foundLogDrainContainer, 'State.Status');
|
||||||
|
if ($status !== 'running') {
|
||||||
|
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkContainers()
|
||||||
|
{
|
||||||
|
foreach ($this->containers as $container) {
|
||||||
|
if ($this->isSentinel) {
|
||||||
|
$labels = Arr::undot(data_get($container, 'labels'));
|
||||||
|
} else {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$labels = Arr::undot(data_get($container, 'Spec.Labels'));
|
||||||
|
} else {
|
||||||
|
$labels = Arr::undot(data_get($container, 'Config.Labels'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
$managed = data_get($labels, 'coolify.managed');
|
||||||
|
if (! $managed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$uuid = data_get($labels, 'coolify.name');
|
||||||
|
if (! $uuid) {
|
||||||
|
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isSentinel) {
|
||||||
|
$containerStatus = data_get($container, 'state');
|
||||||
|
$containerHealth = data_get($container, 'health_status');
|
||||||
|
} else {
|
||||||
|
$containerStatus = data_get($container, 'State.Status');
|
||||||
|
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
}
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
|
|
||||||
|
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
|
$serviceId = data_get($labels, 'coolify.serviceId');
|
||||||
|
$databaseId = data_get($labels, 'coolify.databaseId');
|
||||||
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
|
|
||||||
|
if ($applicationId) {
|
||||||
|
// Application
|
||||||
|
if ($pullRequestId != 0) {
|
||||||
|
if (str($applicationId)->contains('-')) {
|
||||||
|
$applicationId = str($applicationId)->before('-');
|
||||||
|
}
|
||||||
|
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
|
if ($preview) {
|
||||||
|
$preview->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$application = Application::where('id', $applicationId)->first();
|
||||||
|
if ($application) {
|
||||||
|
$application->update([
|
||||||
|
'status' => $containerStatus,
|
||||||
|
'last_online_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif (isset($serviceId)) {
|
||||||
|
// Service
|
||||||
|
$subType = data_get($labels, 'coolify.service.subType');
|
||||||
|
$subId = data_get($labels, 'coolify.service.subId');
|
||||||
|
$service = Service::where('id', $serviceId)->first();
|
||||||
|
if (! $service) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($subType === 'application') {
|
||||||
|
$service = ServiceApplication::where('id', $subId)->first();
|
||||||
|
} else {
|
||||||
|
$service = ServiceDatabase::where('id', $subId)->first();
|
||||||
|
}
|
||||||
|
if ($service) {
|
||||||
|
$service->update([
|
||||||
|
'status' => $containerStatus,
|
||||||
|
'last_online_at' => now(),
|
||||||
|
]);
|
||||||
|
if ($subType === 'database') {
|
||||||
|
$isPublic = data_get($service, 'is_public');
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->isSentinel) {
|
||||||
|
return data_get($value, 'name') === $uuid.'-proxy';
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Database
|
||||||
|
if (is_null($this->databases)) {
|
||||||
|
$this->databases = $this->server->databases();
|
||||||
|
}
|
||||||
|
$database = $this->databases->where('uuid', $uuid)->first();
|
||||||
|
if ($database) {
|
||||||
|
$database->update([
|
||||||
|
'status' => $containerStatus,
|
||||||
|
'last_online_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$isPublic = data_get($database, 'is_public');
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->isSentinel) {
|
||||||
|
return data_get($value, 'name') === $uuid.'-proxy';
|
||||||
|
} else {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($database);
|
||||||
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace App\Actions\Server;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class InstallLogDrain
|
class StartLogDrain
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
@@ -13,23 +13,22 @@ class InstallLogDrain
|
|||||||
{
|
{
|
||||||
if ($server->settings->is_logdrain_newrelic_enabled) {
|
if ($server->settings->is_logdrain_newrelic_enabled) {
|
||||||
$type = 'newrelic';
|
$type = 'newrelic';
|
||||||
|
StopLogDrain::run($server);
|
||||||
} elseif ($server->settings->is_logdrain_highlight_enabled) {
|
} elseif ($server->settings->is_logdrain_highlight_enabled) {
|
||||||
$type = 'highlight';
|
$type = 'highlight';
|
||||||
|
StopLogDrain::run($server);
|
||||||
} elseif ($server->settings->is_logdrain_axiom_enabled) {
|
} elseif ($server->settings->is_logdrain_axiom_enabled) {
|
||||||
$type = 'axiom';
|
$type = 'axiom';
|
||||||
|
StopLogDrain::run($server);
|
||||||
} elseif ($server->settings->is_logdrain_custom_enabled) {
|
} elseif ($server->settings->is_logdrain_custom_enabled) {
|
||||||
$type = 'custom';
|
$type = 'custom';
|
||||||
|
StopLogDrain::run($server);
|
||||||
} else {
|
} else {
|
||||||
$type = 'none';
|
$type = 'none';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ($type === 'none') {
|
if ($type === 'none') {
|
||||||
$command = [
|
return 'No log drain is enabled.';
|
||||||
"echo 'Stopping old Fluent Bit'",
|
|
||||||
'docker rm -f coolify-log-drain || true',
|
|
||||||
];
|
|
||||||
|
|
||||||
return instant_remote_process($command, $server);
|
|
||||||
} elseif ($type === 'newrelic') {
|
} elseif ($type === 'newrelic') {
|
||||||
if (! $server->settings->is_logdrain_newrelic_enabled) {
|
if (! $server->settings->is_logdrain_newrelic_enabled) {
|
||||||
throw new \Exception('New Relic log drain is not enabled.');
|
throw new \Exception('New Relic log drain is not enabled.');
|
||||||
@@ -52,7 +51,11 @@ class InstallLogDrain
|
|||||||
[FILTER]
|
[FILTER]
|
||||||
Name modify
|
Name modify
|
||||||
Match *
|
Match *
|
||||||
Set server_name {$server->name}
|
Set coolify.server_name {$server->name}
|
||||||
|
Rename COOLIFY_APP_NAME coolify.app_name
|
||||||
|
Rename COOLIFY_PROJECT_NAME coolify.project_name
|
||||||
|
Rename COOLIFY_SERVER_IP coolify.server_ip
|
||||||
|
Rename COOLIFY_ENVIRONMENT_NAME coolify.environment_name
|
||||||
[OUTPUT]
|
[OUTPUT]
|
||||||
Name nrlogs
|
Name nrlogs
|
||||||
Match *
|
Match *
|
||||||
@@ -103,7 +106,11 @@ class InstallLogDrain
|
|||||||
[FILTER]
|
[FILTER]
|
||||||
Name modify
|
Name modify
|
||||||
Match *
|
Match *
|
||||||
Set server_name {$server->name}
|
Set coolify.server_name {$server->name}
|
||||||
|
Rename COOLIFY_APP_NAME coolify.app_name
|
||||||
|
Rename COOLIFY_PROJECT_NAME coolify.project_name
|
||||||
|
Rename COOLIFY_SERVER_IP coolify.server_ip
|
||||||
|
Rename COOLIFY_ENVIRONMENT_NAME coolify.environment_name
|
||||||
[OUTPUT]
|
[OUTPUT]
|
||||||
Name http
|
Name http
|
||||||
Match *
|
Match *
|
||||||
@@ -148,6 +155,8 @@ services:
|
|||||||
- ./parsers.conf:/parsers.conf
|
- ./parsers.conf:/parsers.conf
|
||||||
ports:
|
ports:
|
||||||
- 127.0.0.1:24224:24224
|
- 127.0.0.1:24224:24224
|
||||||
|
labels:
|
||||||
|
- coolify.managed=true
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
');
|
');
|
||||||
$readme = base64_encode('# New Relic Log Drain
|
$readme = base64_encode('# New Relic Log Drain
|
||||||
@@ -199,10 +208,8 @@ Files:
|
|||||||
throw new \Exception('Unknown log drain type.');
|
throw new \Exception('Unknown log drain type.');
|
||||||
}
|
}
|
||||||
$restart_command = [
|
$restart_command = [
|
||||||
"echo 'Stopping old Fluent Bit'",
|
|
||||||
"cd $config_path && docker compose down --remove-orphans || true",
|
|
||||||
"echo 'Starting Fluent Bit'",
|
"echo 'Starting Fluent Bit'",
|
||||||
"cd $config_path && docker compose up -d --remove-orphans",
|
"cd $config_path && docker compose up -d",
|
||||||
];
|
];
|
||||||
$command = array_merge($command, $add_envs_command, $restart_command);
|
$command = array_merge($command, $add_envs_command, $restart_command);
|
||||||
|
|
||||||
@@ -9,15 +9,57 @@ class StartSentinel
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, $version = 'latest', bool $restart = false)
|
public function handle(Server $server, bool $restart = false, ?string $latestVersion = null)
|
||||||
{
|
{
|
||||||
|
if ($server->isSwarm() || $server->isBuildServer()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ($restart) {
|
if ($restart) {
|
||||||
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
|
StopSentinel::run($server);
|
||||||
}
|
}
|
||||||
|
$version = $latestVersion ?? get_latest_sentinel_version();
|
||||||
|
$metricsHistory = data_get($server, 'settings.sentinel_metrics_history_days');
|
||||||
|
$refreshRate = data_get($server, 'settings.sentinel_metrics_refresh_rate_seconds');
|
||||||
|
$pushInterval = data_get($server, 'settings.sentinel_push_interval_seconds');
|
||||||
|
$token = data_get($server, 'settings.sentinel_token');
|
||||||
|
$endpoint = data_get($server, 'settings.sentinel_custom_url');
|
||||||
|
$debug = data_get($server, 'settings.is_sentinel_debug_enabled');
|
||||||
|
$mountDir = '/data/coolify/sentinel';
|
||||||
|
$image = "ghcr.io/coollabsio/sentinel:$version";
|
||||||
|
if (! $endpoint) {
|
||||||
|
throw new \Exception('You should set FQDN in Instance Settings.');
|
||||||
|
}
|
||||||
|
$environments = [
|
||||||
|
'TOKEN' => $token,
|
||||||
|
'DEBUG' => $debug ? 'true' : 'false',
|
||||||
|
'PUSH_ENDPOINT' => $endpoint,
|
||||||
|
'PUSH_INTERVAL_SECONDS' => $pushInterval,
|
||||||
|
'COLLECTOR_ENABLED' => $server->isMetricsEnabled() ? 'true' : 'false',
|
||||||
|
'COLLECTOR_REFRESH_RATE_SECONDS' => $refreshRate,
|
||||||
|
'COLLECTOR_RETENTION_PERIOD_DAYS' => $metricsHistory,
|
||||||
|
];
|
||||||
|
$labels = [
|
||||||
|
'coolify.managed' => 'true',
|
||||||
|
];
|
||||||
|
if (isDev()) {
|
||||||
|
// data_set($environments, 'DEBUG', 'true');
|
||||||
|
// $image = 'sentinel';
|
||||||
|
$mountDir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/sentinel';
|
||||||
|
}
|
||||||
|
$dockerEnvironments = '-e "'.implode('" -e "', array_map(fn ($key, $value) => "$key=$value", array_keys($environments), $environments)).'"';
|
||||||
|
$dockerLabels = implode(' ', array_map(fn ($key, $value) => "$key=$value", array_keys($labels), $labels));
|
||||||
|
$dockerCommand = "docker run -d $dockerEnvironments --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v $mountDir:/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 --label $dockerLabels $image";
|
||||||
|
|
||||||
instant_remote_process([
|
instant_remote_process([
|
||||||
"docker run --rm --pull always -d -e \"SCHEDULER=true\" --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 $mountDir",
|
||||||
'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
|
$dockerCommand,
|
||||||
], $server, false);
|
"chown -R 9999:root $mountDir",
|
||||||
|
"chmod -R 700 $mountDir",
|
||||||
|
], $server);
|
||||||
|
|
||||||
|
$server->settings->is_sentinel_enabled = true;
|
||||||
|
$server->settings->save();
|
||||||
|
$server->sentinelHeartbeat();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
app/Actions/Server/StopLogDrain.php
Normal file
20
app/Actions/Server/StopLogDrain.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StopLogDrain
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return instant_remote_process(['docker rm -f coolify-log-drain'], $server, false);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/Actions/Server/StopSentinel.php
Normal file
17
app/Actions/Server/StopSentinel.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StopSentinel
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
|
||||||
|
$server->sentinelHeartbeat(isReset: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
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 Illuminate\Support\Sleep;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class UpdateCoolify
|
class UpdateCoolify
|
||||||
@@ -18,14 +19,17 @@ class UpdateCoolify
|
|||||||
|
|
||||||
public function handle($manual_update = false)
|
public function handle($manual_update = false)
|
||||||
{
|
{
|
||||||
try {
|
if (isDev()) {
|
||||||
$settings = InstanceSettings::get();
|
Sleep::for(10)->seconds();
|
||||||
ray('Running InstanceAutoUpdateJob');
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$settings = instanceSettings();
|
||||||
$this->server = Server::find(0);
|
$this->server = Server::find(0);
|
||||||
if (! $this->server) {
|
if (! $this->server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CleanupDocker::run($this->server, false);
|
CleanupDocker::dispatch($this->server)->onQueue('high');
|
||||||
$this->latestVersion = get_latest_version_of_coolify();
|
$this->latestVersion = get_latest_version_of_coolify();
|
||||||
$this->currentVersion = config('version');
|
$this->currentVersion = config('version');
|
||||||
if (! $manual_update) {
|
if (! $manual_update) {
|
||||||
@@ -40,24 +44,19 @@ class UpdateCoolify
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->update();
|
$this->update();
|
||||||
} catch (\Throwable $e) {
|
$settings->new_version_available = false;
|
||||||
throw $e;
|
$settings->save();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function update()
|
private function update()
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
PullHelperImageJob::dispatch($this->server);
|
||||||
remote_process([
|
|
||||||
'sleep 10',
|
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
|
||||||
], $this->server);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
remote_process([
|
remote_process([
|
||||||
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
|
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
|
||||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion",
|
"bash /data/coolify/source/upgrade.sh $this->latestVersion",
|
||||||
], $this->server);
|
], $this->server);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
67
app/Actions/Server/ValidateServer.php
Normal file
67
app/Actions/Server/ValidateServer.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ValidateServer
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public ?string $uptime = null;
|
||||||
|
|
||||||
|
public ?string $error = null;
|
||||||
|
|
||||||
|
public ?string $supported_os_type = null;
|
||||||
|
|
||||||
|
public ?string $docker_installed = null;
|
||||||
|
|
||||||
|
public ?string $docker_compose_installed = null;
|
||||||
|
|
||||||
|
public ?string $docker_version = null;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => null,
|
||||||
|
]);
|
||||||
|
['uptime' => $this->uptime, 'error' => $error] = $server->validateConnection();
|
||||||
|
if (! $this->uptime) {
|
||||||
|
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
$this->supported_os_type = $server->validateOS();
|
||||||
|
if (! $this->supported_os_type) {
|
||||||
|
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->docker_installed = $server->validateDockerEngine();
|
||||||
|
$this->docker_compose_installed = $server->validateDockerCompose();
|
||||||
|
if (! $this->docker_installed || ! $this->docker_compose_installed) {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
$this->docker_version = $server->validateDockerEngineVersion();
|
||||||
|
|
||||||
|
if ($this->docker_version) {
|
||||||
|
return 'OK';
|
||||||
|
} else {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,18 +2,20 @@
|
|||||||
|
|
||||||
namespace App\Actions\Service;
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use App\Actions\Server\CleanupDocker;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class DeleteService
|
class DeleteService
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Service $service)
|
public function handle(Service $service, bool $deleteConfigurations, bool $deleteVolumes, bool $dockerCleanup, bool $deleteConnectedNetworks)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = data_get($service, 'server');
|
$server = data_get($service, 'server');
|
||||||
if ($server->isFunctional()) {
|
if ($deleteVolumes && $server->isFunctional()) {
|
||||||
$storagesToDelete = collect([]);
|
$storagesToDelete = collect([]);
|
||||||
|
|
||||||
$service->environment_variables()->delete();
|
$service->environment_variables()->delete();
|
||||||
@@ -33,13 +35,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 !== null && $result !== 0) {
|
||||||
|
Log::error('Error deleting volumes: '.$result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +68,11 @@ class DeleteService
|
|||||||
$task->delete();
|
$task->delete();
|
||||||
}
|
}
|
||||||
$service->tags()->detach();
|
$service->tags()->detach();
|
||||||
|
$service->forceDelete();
|
||||||
|
|
||||||
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::dispatch($server, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
app/Actions/Service/RestartService.php
Normal file
18
app/Actions/Service/RestartService.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RestartService
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Service $service)
|
||||||
|
{
|
||||||
|
StopService::run($service);
|
||||||
|
|
||||||
|
return StartService::run($service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,12 +12,13 @@ class StartService
|
|||||||
|
|
||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
ray('Starting service: '.$service->name);
|
|
||||||
$service->saveComposeConfigs();
|
$service->saveComposeConfigs();
|
||||||
$commands[] = 'cd '.$service->workdir();
|
$commands[] = 'cd '.$service->workdir();
|
||||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||||
|
if ($service->networks()->count() > 0) {
|
||||||
$commands[] = "echo 'Creating Docker network.'";
|
$commands[] = "echo 'Creating Docker network.'";
|
||||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||||
|
}
|
||||||
$commands[] = 'echo Starting service.';
|
$commands[] = 'echo Starting service.';
|
||||||
$commands[] = "echo 'Pulling images.'";
|
$commands[] = "echo 'Pulling images.'";
|
||||||
$commands[] = 'docker compose pull';
|
$commands[] = 'docker compose pull';
|
||||||
@@ -29,11 +30,10 @@ 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');
|
|
||||||
|
|
||||||
return $activity;
|
return 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,34 +10,25 @@ class StopService
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Service $service)
|
public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = $service->destination->server;
|
$server = $service->destination->server;
|
||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
ray('Stopping service: '.$service->name);
|
|
||||||
$applications = $service->applications()->get();
|
|
||||||
foreach ($applications as $application) {
|
|
||||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
|
||||||
$application->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
$dbs = $service->databases()->get();
|
|
||||||
foreach ($dbs as $db) {
|
|
||||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
|
||||||
$db->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
|
||||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
|
||||||
// TODO: make notification for databases
|
|
||||||
// $service->environment->project->team->notify(new StatusChanged($service));
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
echo $e->getMessage();
|
|
||||||
ray($e->getMessage());
|
|
||||||
|
|
||||||
|
$containersToStop = $service->getContainersToStop();
|
||||||
|
$service->stopContainers($containersToStop, $server);
|
||||||
|
|
||||||
|
if (! $isDeleteOperation) {
|
||||||
|
$service->delete_connected_networks($service->uuid);
|
||||||
|
if ($dockerCleanup) {
|
||||||
|
CleanupDocker::dispatch($server, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use Illuminate\Support\Facades\DB;
|
|||||||
|
|
||||||
class CleanupDatabase extends Command
|
class CleanupDatabase extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'cleanup:database {--yes}';
|
protected $signature = 'cleanup:database {--yes} {--keep-days=}';
|
||||||
|
|
||||||
protected $description = 'Cleanup database';
|
protected $description = 'Cleanup database';
|
||||||
|
|
||||||
@@ -18,7 +18,12 @@ class CleanupDatabase extends Command
|
|||||||
} else {
|
} else {
|
||||||
echo "Running database cleanup in dry-run mode...\n";
|
echo "Running database cleanup in dry-run mode...\n";
|
||||||
}
|
}
|
||||||
$keep_days = 60;
|
if (isCloud()) {
|
||||||
|
// Later on we can increase this to 180 days or dynamically set
|
||||||
|
$keep_days = $this->option('keep-days') ?? 60;
|
||||||
|
} else {
|
||||||
|
$keep_days = $this->option('keep-days') ?? 60;
|
||||||
|
}
|
||||||
echo "Keep days: $keep_days\n";
|
echo "Keep days: $keep_days\n";
|
||||||
// Cleanup failed jobs table
|
// Cleanup failed jobs table
|
||||||
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));
|
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));
|
||||||
@@ -59,6 +64,5 @@ class CleanupDatabase extends Command
|
|||||||
if ($this->option('yes')) {
|
if ($this->option('yes')) {
|
||||||
$webhooks->delete();
|
$webhooks->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
30
app/Console/Commands/CleanupRedis.php
Normal file
30
app/Console/Commands/CleanupRedis.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?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,8 +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\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;
|
||||||
@@ -25,14 +30,33 @@ class CleanupStuckedResources extends Command
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
ray('Running cleanup stucked resources.');
|
|
||||||
echo "Running cleanup stucked resources.\n";
|
echo "Running cleanup stucked resources.\n";
|
||||||
$this->cleanup_stucked_resources();
|
$this->cleanup_stucked_resources();
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -42,6 +66,17 @@ class CleanupStuckedResources extends Command
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
$applicationsPreviews = ApplicationPreview::get();
|
||||||
|
foreach ($applicationsPreviews as $applicationPreview) {
|
||||||
|
if (! data_get($applicationPreview, 'application')) {
|
||||||
|
echo "Deleting stuck application preview: {$applicationPreview->uuid}\n";
|
||||||
|
$applicationPreview->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
foreach ($postgresqls as $postgresql) {
|
foreach ($postgresqls as $postgresql) {
|
||||||
@@ -153,6 +188,18 @@ class CleanupStuckedResources extends Command
|
|||||||
echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n";
|
echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||||
|
foreach ($scheduled_backups as $scheduled_backup) {
|
||||||
|
if (! $scheduled_backup->server()) {
|
||||||
|
echo "Deleting stuck scheduledbackup: {$scheduled_backup->name}\n";
|
||||||
|
$scheduled_backup->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck scheduledbackups: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup any resources that are not attached to any environment or destination or server
|
// Cleanup any resources that are not attached to any environment or destination or server
|
||||||
try {
|
try {
|
||||||
$applications = Application::all();
|
$applications = Application::all();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class CleanupUnreachableServers extends Command
|
|||||||
if ($servers->count() > 0) {
|
if ($servers->count() > 0) {
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||||
send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
// send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
||||||
$server->update([
|
$server->update([
|
||||||
'ip' => '1.2.3.4',
|
'ip' => '1.2.3.4',
|
||||||
]);
|
]);
|
||||||
|
|||||||
49
app/Console/Commands/CloudCheckSubscription.php
Normal file
49
app/Console/Commands/CloudCheckSubscription.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CloudCheckSubscription extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'cloud:check-subscription';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Check Cloud subscriptions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
$activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->get();
|
||||||
|
foreach ($activeSubscribers as $team) {
|
||||||
|
$stripeSubscriptionId = $team->subscription->stripe_subscription_id;
|
||||||
|
$stripeInvoicePaid = $team->subscription->stripe_invoice_paid;
|
||||||
|
$stripeCustomerId = $team->subscription->stripe_customer_id;
|
||||||
|
if (! $stripeSubscriptionId) {
|
||||||
|
echo "Team {$team->id} has no subscription, but invoice status is: {$stripeInvoicePaid}\n";
|
||||||
|
echo "Link on Stripe: https://dashboard.stripe.com/customers/{$stripeCustomerId}\n";
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$subscription = $stripe->subscriptions->retrieve($stripeSubscriptionId);
|
||||||
|
if ($subscription->status === 'active') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
echo "Subscription {$stripeSubscriptionId} is not active ({$subscription->status})\n";
|
||||||
|
echo "Link on Stripe: https://dashboard.stripe.com/subscriptions/{$stripeSubscriptionId}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
app/Console/Commands/CloudCleanupSubscriptions.php
Normal file
98
app/Console/Commands/CloudCleanupSubscriptions.php
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CloudCleanupSubscriptions extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cloud:cleanup-subs';
|
||||||
|
|
||||||
|
protected $description = 'Cleanup subcriptions teams';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (! isCloud()) {
|
||||||
|
$this->error('This command can only be run on cloud');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->info('Cleaning up subcriptions teams');
|
||||||
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
|
||||||
|
$teams = Team::all()->filter(function ($team) {
|
||||||
|
return $team->id !== 0;
|
||||||
|
})->sortBy('id');
|
||||||
|
foreach ($teams as $team) {
|
||||||
|
if ($team) {
|
||||||
|
$this->info("Checking team {$team->id}");
|
||||||
|
}
|
||||||
|
if (! data_get($team, 'subscription')) {
|
||||||
|
$this->disableServers($team);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If the team has no subscription id and the invoice is paid, we need to reset the invoice paid status
|
||||||
|
if (! (data_get($team, 'subscription.stripe_subscription_id'))) {
|
||||||
|
$this->info("Resetting invoice paid status for team {$team->id} {$team->name}");
|
||||||
|
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
'stripe_subscription_id' => null,
|
||||||
|
]);
|
||||||
|
$this->disableServers($team);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$subscription = $stripe->subscriptions->retrieve(data_get($team, 'subscription.stripe_subscription_id'), []);
|
||||||
|
$status = data_get($subscription, 'status');
|
||||||
|
if ($status === 'active' || $status === 'past_due') {
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => true,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->info('Subscription status: '.$status);
|
||||||
|
$this->info('Subscription id: '.data_get($team, 'subscription.stripe_subscription_id'));
|
||||||
|
$confirm = $this->confirm('Do you want to cancel the subscription?', true);
|
||||||
|
if (! $confirm) {
|
||||||
|
$this->info("Skipping team {$team->id} {$team->name}");
|
||||||
|
} else {
|
||||||
|
$this->info("Cancelling subscription for team {$team->id} {$team->name}");
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
'stripe_subscription_id' => null,
|
||||||
|
]);
|
||||||
|
$this->disableServers($team);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error($e->getMessage());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function disableServers(Team $team)
|
||||||
|
{
|
||||||
|
foreach ($team->servers as $server) {
|
||||||
|
if ($server->settings->is_usable === true || $server->settings->is_reachable === true || $server->ip !== '1.2.3.4') {
|
||||||
|
$this->info("Disabling server {$server->id} {$server->name}");
|
||||||
|
$server->settings()->update([
|
||||||
|
'is_usable' => false,
|
||||||
|
'is_reachable' => false,
|
||||||
|
]);
|
||||||
|
$server->update([
|
||||||
|
'ip' => '1.2.3.4',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,17 +9,51 @@ use Illuminate\Support\Facades\Process;
|
|||||||
|
|
||||||
class Dev extends Command
|
class Dev extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'dev:init';
|
protected $signature = 'dev {--init} {--generate-openapi}';
|
||||||
|
|
||||||
protected $description = 'Init the app in dev mode';
|
protected $description = 'Helper commands for development.';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
|
{
|
||||||
|
if ($this->option('init')) {
|
||||||
|
$this->init();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->option('generate-openapi')) {
|
||||||
|
$this->generateOpenApi();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateOpenApi()
|
||||||
|
{
|
||||||
|
// Generate OpenAPI documentation
|
||||||
|
echo "Generating OpenAPI documentation.\n";
|
||||||
|
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
||||||
|
$error = $process->errorOutput();
|
||||||
|
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||||
|
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||||
|
echo $error;
|
||||||
|
echo $process->output();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
{
|
{
|
||||||
// Generate APP_KEY if not exists
|
// Generate APP_KEY if not exists
|
||||||
|
|
||||||
if (empty(env('APP_KEY'))) {
|
if (empty(env('APP_KEY'))) {
|
||||||
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) {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use App\Notifications\Application\DeploymentSuccess;
|
|||||||
use App\Notifications\Application\StatusChanged;
|
use App\Notifications\Application\StatusChanged;
|
||||||
use App\Notifications\Database\BackupFailed;
|
use App\Notifications\Database\BackupFailed;
|
||||||
use App\Notifications\Database\BackupSuccess;
|
use App\Notifications\Database\BackupSuccess;
|
||||||
use App\Notifications\Database\DailyBackup;
|
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
@@ -81,7 +80,7 @@ class Emails extends Command
|
|||||||
}
|
}
|
||||||
set_transanctional_email_settings();
|
set_transanctional_email_settings();
|
||||||
|
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->subject('Test Email');
|
$this->mail->subject('Test Email');
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'updates':
|
case 'updates':
|
||||||
@@ -107,7 +106,7 @@ class Emails extends Command
|
|||||||
$confirmed = confirm('Are you sure?');
|
$confirmed = confirm('Are you sure?');
|
||||||
if ($confirmed) {
|
if ($confirmed) {
|
||||||
foreach ($emails as $email) {
|
foreach ($emails as $email) {
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->subject('One-click Services, Docker Compose support');
|
$this->mail->subject('One-click Services, Docker Compose support');
|
||||||
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
||||||
'token' => encrypt($email),
|
'token' => encrypt($email),
|
||||||
@@ -118,31 +117,13 @@ class Emails extends Command
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'emails-test':
|
case 'emails-test':
|
||||||
$this->mail = (new Test())->toMail();
|
$this->mail = (new Test)->toMail();
|
||||||
$this->sendEmail();
|
|
||||||
break;
|
|
||||||
case 'database-backup-statuses-daily':
|
|
||||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
|
||||||
$databases = 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;
|
|
||||||
$databases->put($database->name, [
|
|
||||||
'failed_count' => $failed->count(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$this->mail = (new DailyBackup($databases))->toMail();
|
|
||||||
$this->sendEmail();
|
$this->sendEmail();
|
||||||
break;
|
break;
|
||||||
case 'application-deployment-success-daily':
|
case 'application-deployment-success-daily':
|
||||||
$applications = Application::all();
|
$applications = Application::all();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
$deployments = $application->get_last_days_deployments();
|
$deployments = $application->get_last_days_deployments();
|
||||||
ray($deployments);
|
|
||||||
if ($deployments->isEmpty()) {
|
if ($deployments->isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -224,7 +205,7 @@ class Emails extends Command
|
|||||||
// $this->sendEmail();
|
// $this->sendEmail();
|
||||||
// break;
|
// break;
|
||||||
case 'waitlist-invitation-link':
|
case 'waitlist-invitation-link':
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.waitlist-invitation', [
|
$this->mail->view('emails.waitlist-invitation', [
|
||||||
'loginLink' => 'https://coolify.io',
|
'loginLink' => 'https://coolify.io',
|
||||||
]);
|
]);
|
||||||
@@ -241,7 +222,7 @@ class Emails extends Command
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 'realusers-before-trial':
|
case 'realusers-before-trial':
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.before-trial-conversion');
|
$this->mail->view('emails.before-trial-conversion');
|
||||||
$this->mail->subject('Trial period has been added for all subscription plans.');
|
$this->mail->subject('Trial period has been added for all subscription plans.');
|
||||||
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
||||||
@@ -287,7 +268,7 @@ class Emails extends Command
|
|||||||
foreach ($admins as $admin) {
|
foreach ($admins as $admin) {
|
||||||
$this->info($admin);
|
$this->info($admin);
|
||||||
}
|
}
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.server-lost-connection', [
|
$this->mail->view('emails.server-lost-connection', [
|
||||||
'name' => $server->name,
|
'name' => $server->name,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -2,51 +2,74 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Actions\Server\StopSentinel;
|
||||||
|
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\InstanceSettings;
|
use App\Models\Environment;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class Init extends Command
|
class Init extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
|
protected $signature = 'app:init {--force-cloud}';
|
||||||
|
|
||||||
protected $description = 'Cleanup instance related stuffs';
|
protected $description = 'Cleanup instance related stuffs';
|
||||||
|
|
||||||
|
public $servers = null;
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->alive();
|
if (isCloud() && ! $this->option('force-cloud')) {
|
||||||
get_public_ips();
|
echo "Skipping init as we are on cloud and --force-cloud option is not set\n";
|
||||||
$full_cleanup = $this->option('full-cleanup');
|
|
||||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
|
||||||
if ($cleanup_deployments) {
|
|
||||||
echo "Running cleanup deployments.\n";
|
|
||||||
$this->cleanup_in_progress_application_deployments();
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($full_cleanup) {
|
|
||||||
// Required for falsely deleted coolify db
|
$this->servers = Server::all();
|
||||||
|
if (isCloud()) {
|
||||||
|
} else {
|
||||||
|
$this->send_alive_signal();
|
||||||
|
get_public_ips();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backward compatibility
|
||||||
|
// $this->disable_metrics();
|
||||||
|
$this->replace_slash_in_environment_name();
|
||||||
$this->restore_coolify_db_backup();
|
$this->restore_coolify_db_backup();
|
||||||
|
$this->update_user_emails();
|
||||||
|
//
|
||||||
|
$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->cleanup_in_progress_application_deployments();
|
||||||
$this->cleanup_stucked_helper_containers();
|
}
|
||||||
$this->call('cleanup:queue');
|
$this->call('cleanup:redis');
|
||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
if (! isCloud()) {
|
|
||||||
|
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 {
|
try {
|
||||||
$server = Server::find(0)->first();
|
$localhost = $this->servers->where('id', 0)->first();
|
||||||
$server->setupDynamicProxyConfiguration();
|
$localhost->setupDynamicProxyConfiguration();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
$settings = instanceSettings();
|
||||||
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
if (! is_null(env('AUTOUPDATE', null))) {
|
if (! is_null(env('AUTOUPDATE', null))) {
|
||||||
if (env('AUTOUPDATE') == true) {
|
if (env('AUTOUPDATE') == true) {
|
||||||
$settings->update(['is_auto_update_enabled' => true]);
|
$settings->update(['is_auto_update_enabled' => true]);
|
||||||
@@ -54,15 +77,105 @@ class Init extends Command
|
|||||||
$settings->update(['is_auto_update_enabled' => false]);
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
$this->cleanup_stucked_helper_containers();
|
}
|
||||||
$this->call('cleanup:stucked-resources');
|
|
||||||
|
// private function disable_metrics()
|
||||||
|
// {
|
||||||
|
// if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||||
|
// foreach ($this->servers as $server) {
|
||||||
|
// if ($server->settings->is_metrics_enabled === true) {
|
||||||
|
// $server->settings->update(['is_metrics_enabled' => false]);
|
||||||
|
// }
|
||||||
|
// if ($server->isFunctional()) {
|
||||||
|
// StopSentinel::dispatch($server)->onQueue('high');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
private function update_user_emails()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
User::whereRaw('email ~ \'[A-Z]\'')->get()->each(fn (User $user) => $user->update(['email' => strtolower($user->email)]));
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in updating user emails: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function update_traefik_labels()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Server::where('proxy->type', 'TRAEFIK_V2')->update(['proxy->type' => 'TRAEFIK']);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in updating traefik labels: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanup_unnecessary_dynamic_proxy_configuration()
|
||||||
|
{
|
||||||
|
foreach ($this->servers as $server) {
|
||||||
|
try {
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($server->id === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$file = $server->proxyPath().'/dynamic/coolify.yaml';
|
||||||
|
|
||||||
|
return instant_remote_process([
|
||||||
|
"rm -f $file",
|
||||||
|
], $server, false);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning up unnecessary dynamic proxy configuration: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanup_unused_network_from_coolify_proxy()
|
||||||
|
{
|
||||||
|
foreach ($this->servers as $server) {
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! $server->isProxyShouldRun()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
['networks' => $networks, 'allNetworks' => $allNetworks] = collectDockerNetworksByServer($server);
|
||||||
|
$removeNetworks = $allNetworks->diff($networks);
|
||||||
|
$commands = collect();
|
||||||
|
foreach ($removeNetworks as $network) {
|
||||||
|
$out = instant_remote_process(["docker network inspect -f json $network | jq '.[].Containers | if . == {} then null else . end'"], $server, false);
|
||||||
|
if (empty($out)) {
|
||||||
|
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||||
|
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||||
|
} else {
|
||||||
|
$data = collect(json_decode($out, true));
|
||||||
|
if ($data->count() === 1) {
|
||||||
|
// If only coolify-proxy itself is connected to that network (it should not be possible, but who knows)
|
||||||
|
$isCoolifyProxyItself = data_get($data->first(), 'Name') === 'coolify-proxy';
|
||||||
|
if ($isCoolifyProxyItself) {
|
||||||
|
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||||
|
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($commands->isNotEmpty()) {
|
||||||
|
echo "Cleaning up unused networks from coolify proxy\n";
|
||||||
|
remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning up unused networks from coolify proxy: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function restore_coolify_db_backup()
|
private function restore_coolify_db_backup()
|
||||||
{
|
{
|
||||||
|
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()) {
|
||||||
@@ -76,7 +189,7 @@ class Init extends Command
|
|||||||
'save_s3' => false,
|
'save_s3' => false,
|
||||||
'frequency' => '0 0 * * *',
|
'frequency' => '0 0 * * *',
|
||||||
'database_id' => $database->id,
|
'database_id' => $database->id,
|
||||||
'database_type' => 'App\Models\StandalonePostgresql',
|
'database_type' => \App\Models\StandalonePostgresql::class,
|
||||||
'team_id' => 0,
|
'team_id' => 0,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -85,22 +198,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()
|
|
||||||
{
|
|
||||||
$servers = Server::all();
|
|
||||||
foreach ($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";
|
||||||
@@ -114,34 +218,16 @@ 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
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
|
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
|
||||||
foreach ($queued_inprogress_deployments as $deployment) {
|
foreach ($queued_inprogress_deployments as $deployment) {
|
||||||
ray($deployment->id, $deployment->status);
|
|
||||||
echo "Cleaning up deployment: {$deployment->id}\n";
|
echo "Cleaning up deployment: {$deployment->id}\n";
|
||||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||||
$deployment->save();
|
$deployment->save();
|
||||||
@@ -150,4 +236,17 @@ class Init extends Command
|
|||||||
echo "Error: {$e->getMessage()}\n";
|
echo "Error: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function replace_slash_in_environment_name()
|
||||||
|
{
|
||||||
|
if (version_compare('4.0.0-beta.298', config('version'), '<=')) {
|
||||||
|
$environments = Environment::all();
|
||||||
|
foreach ($environments as $environment) {
|
||||||
|
if (str_contains($environment->name, '/')) {
|
||||||
|
$environment->name = str_replace('/', '-', $environment->name);
|
||||||
|
$environment->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ class NotifyDemo extends Command
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ray($channel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function showHelp()
|
private function showHelp()
|
||||||
|
|||||||
25
app/Console/Commands/OpenApi.php
Normal file
25
app/Console/Commands/OpenApi.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Process;
|
||||||
|
|
||||||
|
class OpenApi extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'openapi';
|
||||||
|
|
||||||
|
protected $description = 'Generate OpenApi file.';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
// Generate OpenAPI documentation
|
||||||
|
echo "Generating OpenAPI documentation.\n";
|
||||||
|
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
||||||
|
$error = $process->errorOutput();
|
||||||
|
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||||
|
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||||
|
echo $error;
|
||||||
|
echo $process->output();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -96,7 +96,7 @@ class ServicesDelete extends Command
|
|||||||
if (! $confirmed) {
|
if (! $confirmed) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
DeleteResourceJob::dispatch($toDelete);
|
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ class ServicesDelete extends Command
|
|||||||
if (! $confirmed) {
|
if (! $confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DeleteResourceJob::dispatch($toDelete);
|
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ class ServicesDelete extends Command
|
|||||||
if (! $confirmed) {
|
if (! $confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DeleteResourceJob::dispatch($toDelete);
|
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,128 +3,82 @@
|
|||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class ServicesGenerate extends Command
|
class ServicesGenerate extends Command
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* {@inheritdoc}
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
protected $signature = 'services:generate';
|
protected $signature = 'services:generate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* {@inheritdoc}
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
protected $description = 'Generate service-templates.yaml based on /templates/compose directory';
|
protected $description = 'Generate service-templates.yaml based on /templates/compose directory';
|
||||||
|
|
||||||
/**
|
public function handle(): int
|
||||||
* Execute the console command.
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
{
|
||||||
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
|
$serviceTemplatesJson = collect(glob(base_path('templates/compose/*.yaml')))
|
||||||
$files = array_filter($files, function ($file) {
|
->mapWithKeys(function ($file): array {
|
||||||
return strpos($file, '.yaml') !== false;
|
$file = basename($file);
|
||||||
});
|
$parsed = $this->processFile($file);
|
||||||
$serviceTemplatesJson = [];
|
|
||||||
foreach ($files as $file) {
|
return $parsed === false ? [] : [
|
||||||
$parsed = $this->process_file($file);
|
Arr::pull($parsed, 'name') => $parsed,
|
||||||
if ($parsed) {
|
];
|
||||||
$name = data_get($parsed, 'name');
|
})->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
$parsed = data_forget($parsed, 'name');
|
|
||||||
$serviceTemplatesJson[$name] = $parsed;
|
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson.PHP_EOL);
|
||||||
}
|
|
||||||
}
|
return self::SUCCESS;
|
||||||
$serviceTemplatesJson = json_encode($serviceTemplatesJson);
|
|
||||||
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function process_file($file)
|
private function processFile(string $file): false|array
|
||||||
{
|
{
|
||||||
$serviceName = str($file)->before('.yaml')->value();
|
|
||||||
$content = file_get_contents(base_path("templates/compose/$file"));
|
$content = file_get_contents(base_path("templates/compose/$file"));
|
||||||
// $this->info($content);
|
|
||||||
$ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values();
|
$data = collect(explode(PHP_EOL, $content))->mapWithKeys(function ($line): array {
|
||||||
if ($ignore->count() > 0) {
|
preg_match('/^#(?<key>.*):(?<value>.*)$/U', $line, $m);
|
||||||
$ignore = (bool) str($ignore[0])->after('# ignore:')->trim()->value();
|
|
||||||
} else {
|
return $m ? [trim($m['key']) => trim($m['value'])] : [];
|
||||||
$ignore = false;
|
});
|
||||||
}
|
|
||||||
if ($ignore) {
|
if (str($data->get('ignore'))->toBoolean()) {
|
||||||
$this->info("Ignoring $file");
|
$this->info("Ignoring $file");
|
||||||
|
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info("Processing $file");
|
$this->info("Processing $file");
|
||||||
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
|
|
||||||
if ($documentation->count() > 0) {
|
|
||||||
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
|
|
||||||
$documentation = str($documentation)->append('?utm_source=coolify.io');
|
|
||||||
} else {
|
|
||||||
$documentation = 'https://coolify.io/docs';
|
|
||||||
}
|
|
||||||
|
|
||||||
$slogan = collect(preg_grep('/^# slogan:/', explode("\n", $content)))->values();
|
$documentation = $data->get('documentation');
|
||||||
if ($slogan->count() > 0) {
|
$documentation = $documentation ? $documentation.'?utm_source=coolify.io' : 'https://coolify.io/docs';
|
||||||
$slogan = str($slogan[0])->after('# slogan:')->trim()->value();
|
|
||||||
} else {
|
|
||||||
$slogan = str($file)->headline()->value();
|
|
||||||
}
|
|
||||||
$logo = collect(preg_grep('/^# logo:/', explode("\n", $content)))->values();
|
|
||||||
if ($logo->count() > 0) {
|
|
||||||
$logo = str($logo[0])->after('# logo:')->trim()->value();
|
|
||||||
} else {
|
|
||||||
$logo = 'svgs/unknown.svg';
|
|
||||||
}
|
|
||||||
$minversion = collect(preg_grep('/^# minversion:/', explode("\n", $content)))->values();
|
|
||||||
if ($minversion->count() > 0) {
|
|
||||||
$minversion = str($minversion[0])->after('# minversion:')->trim()->value();
|
|
||||||
} else {
|
|
||||||
$minversion = '0.0.0';
|
|
||||||
}
|
|
||||||
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
|
|
||||||
if ($env_file->count() > 0) {
|
|
||||||
$env_file = str($env_file[0])->after('# env_file:')->trim()->value();
|
|
||||||
} else {
|
|
||||||
$env_file = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$tags = collect(preg_grep('/^# tags:/', explode("\n", $content)))->values();
|
|
||||||
if ($tags->count() > 0) {
|
|
||||||
$tags = str($tags[0])->after('# tags:')->trim()->explode(',')->map(function ($tag) {
|
|
||||||
return str($tag)->trim()->lower()->value();
|
|
||||||
})->values();
|
|
||||||
} else {
|
|
||||||
$tags = null;
|
|
||||||
}
|
|
||||||
$port = collect(preg_grep('/^# port:/', explode("\n", $content)))->values();
|
|
||||||
if ($port->count() > 0) {
|
|
||||||
$port = str($port[0])->after('# port:')->trim()->value();
|
|
||||||
} else {
|
|
||||||
$port = null;
|
|
||||||
}
|
|
||||||
$json = Yaml::parse($content);
|
$json = Yaml::parse($content);
|
||||||
$yaml = base64_encode(Yaml::dump($json, 10, 2));
|
$compose = base64_encode(Yaml::dump($json, 10, 2));
|
||||||
|
|
||||||
|
$tags = str($data->get('tags'))->lower()->explode(',')->map(fn ($tag) => trim($tag))->filter();
|
||||||
|
$tags = $tags->isEmpty() ? null : $tags->all();
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
'name' => $serviceName,
|
'name' => pathinfo($file, PATHINFO_FILENAME),
|
||||||
'documentation' => $documentation,
|
'documentation' => $documentation,
|
||||||
'slogan' => $slogan,
|
'slogan' => $data->get('slogan', str($file)->headline()),
|
||||||
'compose' => $yaml,
|
'compose' => $compose,
|
||||||
'tags' => $tags,
|
'tags' => $tags,
|
||||||
'logo' => $logo,
|
'logo' => $data->get('logo', 'svgs/coolify.png'),
|
||||||
'minversion' => $minversion,
|
'minversion' => $data->get('minversion', '0.0.0'),
|
||||||
];
|
];
|
||||||
if ($port) {
|
|
||||||
|
if ($port = $data->get('port')) {
|
||||||
$payload['port'] = $port;
|
$payload['port'] = $port;
|
||||||
}
|
}
|
||||||
if ($env_file) {
|
|
||||||
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
|
if ($envFile = $data->get('env_file')) {
|
||||||
$env_file_base64 = base64_encode($env_file_content);
|
$envFileContent = file_get_contents(base_path("templates/compose/$envFile"));
|
||||||
$payload['envs'] = $env_file_base64;
|
$payload['envs'] = base64_encode($envFileContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class SyncBunny extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'sync:bunny {--templates} {--release}';
|
protected $signature = 'sync:bunny {--templates} {--release} {--nightly}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -33,6 +33,7 @@ class SyncBunny extends Command
|
|||||||
$that = $this;
|
$that = $this;
|
||||||
$only_template = $this->option('templates');
|
$only_template = $this->option('templates');
|
||||||
$only_version = $this->option('release');
|
$only_version = $this->option('release');
|
||||||
|
$nightly = $this->option('nightly');
|
||||||
$bunny_cdn = 'https://cdn.coollabs.io';
|
$bunny_cdn = 'https://cdn.coollabs.io';
|
||||||
$bunny_cdn_path = 'coolify';
|
$bunny_cdn_path = 'coolify';
|
||||||
$bunny_cdn_storage_name = 'coolcdn';
|
$bunny_cdn_storage_name = 'coolcdn';
|
||||||
@@ -45,9 +46,15 @@ class SyncBunny extends Command
|
|||||||
$upgrade_script = 'upgrade.sh';
|
$upgrade_script = 'upgrade.sh';
|
||||||
$production_env = '.env.production';
|
$production_env = '.env.production';
|
||||||
$service_template = 'service-templates.json';
|
$service_template = 'service-templates.json';
|
||||||
|
|
||||||
$versions = 'versions.json';
|
$versions = 'versions.json';
|
||||||
|
|
||||||
|
$compose_file_location = "$parent_dir/$compose_file";
|
||||||
|
$compose_file_prod_location = "$parent_dir/$compose_file_prod";
|
||||||
|
$install_script_location = "$parent_dir/scripts/install.sh";
|
||||||
|
$upgrade_script_location = "$parent_dir/scripts/upgrade.sh";
|
||||||
|
$production_env_location = "$parent_dir/.env.production";
|
||||||
|
$versions_location = "$parent_dir/$versions";
|
||||||
|
|
||||||
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
||||||
$headers = [
|
$headers = [
|
||||||
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
||||||
@@ -73,8 +80,26 @@ class SyncBunny extends Command
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
|
if ($nightly) {
|
||||||
|
$bunny_cdn_path = 'coolify-nightly';
|
||||||
|
|
||||||
|
$compose_file_location = "$parent_dir/other/nightly/$compose_file";
|
||||||
|
$compose_file_prod_location = "$parent_dir/other/nightly/$compose_file_prod";
|
||||||
|
$production_env_location = "$parent_dir/other/nightly/$production_env";
|
||||||
|
$upgrade_script_location = "$parent_dir/other/nightly/$upgrade_script";
|
||||||
|
$install_script_location = "$parent_dir/other/nightly/$install_script";
|
||||||
|
$versions_location = "$parent_dir/other/nightly/$versions";
|
||||||
|
}
|
||||||
if (! $only_template && ! $only_version) {
|
if (! $only_template && ! $only_version) {
|
||||||
$this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
|
if ($nightly) {
|
||||||
|
$this->info('About to sync files NIGHTLY (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
|
||||||
|
} else {
|
||||||
|
$this->info('About to sync files PRODUCTION (docker-compose.yml, docker-compose.prod.yml, upgrade.sh, install.sh, etc) to BunnyCDN.');
|
||||||
|
}
|
||||||
|
$confirmed = confirm('Are you sure you want to sync?');
|
||||||
|
if (! $confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($only_template) {
|
if ($only_template) {
|
||||||
$this->info('About to sync service-templates.json to BunnyCDN.');
|
$this->info('About to sync service-templates.json to BunnyCDN.');
|
||||||
@@ -90,8 +115,12 @@ class SyncBunny extends Command
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
} elseif ($only_version) {
|
} elseif ($only_version) {
|
||||||
$this->info('About to sync versions.json to BunnyCDN.');
|
if ($nightly) {
|
||||||
$file = file_get_contents("$parent_dir/$versions");
|
$this->info('About to sync NIGHLTY versions.json to BunnyCDN.');
|
||||||
|
} else {
|
||||||
|
$this->info('About to sync PRODUCTION versions.json to BunnyCDN.');
|
||||||
|
}
|
||||||
|
$file = file_get_contents($versions_location);
|
||||||
$json = json_decode($file, true);
|
$json = json_decode($file, true);
|
||||||
$actual_version = data_get($json, 'coolify.v4.version');
|
$actual_version = data_get($json, 'coolify.v4.version');
|
||||||
|
|
||||||
@@ -100,7 +129,7 @@ class SyncBunny extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
$pool->storage(fileName: $versions_location)->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||||
]);
|
]);
|
||||||
$this->info('versions.json uploaded & purged...');
|
$this->info('versions.json uploaded & purged...');
|
||||||
@@ -109,11 +138,11 @@ class SyncBunny extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
$pool->storage(fileName: "$compose_file_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
||||||
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
$pool->storage(fileName: "$compose_file_prod_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
||||||
$pool->storage(fileName: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
|
$pool->storage(fileName: "$production_env_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
|
||||||
$pool->storage(fileName: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
|
$pool->storage(fileName: "$upgrade_script_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
|
||||||
$pool->storage(fileName: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
|
$pool->storage(fileName: "$install_script_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
|
||||||
]);
|
]);
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class WaitlistInvite extends Command
|
|||||||
if (! $already_registered) {
|
if (! $already_registered) {
|
||||||
$this->password = Str::password();
|
$this->password = Str::password();
|
||||||
User::create([
|
User::create([
|
||||||
'name' => Str::of($this->next_patient->email)->before('@'),
|
'name' => str($this->next_patient->email)->before('@'),
|
||||||
'email' => $this->next_patient->email,
|
'email' => $this->next_patient->email,
|
||||||
'password' => Hash::make($this->password),
|
'password' => Hash::make($this->password),
|
||||||
'force_password_reset' => true,
|
'force_password_reset' => true,
|
||||||
@@ -103,7 +103,7 @@ class WaitlistInvite extends Command
|
|||||||
{
|
{
|
||||||
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||||
$loginLink = route('auth.link', ['token' => $token]);
|
$loginLink = route('auth.link', ['token' => $token]);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->view('emails.waitlist-invitation', [
|
$mail->view('emails.waitlist-invitation', [
|
||||||
'loginLink' => $loginLink,
|
'loginLink' => $loginLink,
|
||||||
]);
|
]);
|
||||||
|
|||||||
58
app/Console/Commands/Weird.php
Normal file
58
app/Console/Commands/Weird.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Actions\Server\ServerCheck;
|
||||||
|
use App\Enums\ProxyStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Str;
|
||||||
|
|
||||||
|
class Weird extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'weird {--number=1} {--run}';
|
||||||
|
|
||||||
|
protected $description = 'Weird stuff';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (! isDev()) {
|
||||||
|
$this->error('This command can only be run in development mode');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$run = $this->option('run');
|
||||||
|
if ($run) {
|
||||||
|
$servers = Server::all();
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
ServerCheck::dispatch($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$number = $this->option('number');
|
||||||
|
for ($i = 0; $i < $number; $i++) {
|
||||||
|
$uuid = Str::uuid();
|
||||||
|
$server = Server::create([
|
||||||
|
'name' => 'localhost-'.$uuid,
|
||||||
|
'description' => 'This is a test docker container in development mode',
|
||||||
|
'ip' => 'coolify-testing-host',
|
||||||
|
'team_id' => 0,
|
||||||
|
'private_key_id' => 1,
|
||||||
|
'proxy' => [
|
||||||
|
'type' => ProxyTypes::NONE->value,
|
||||||
|
'status' => ProxyStatus::EXITED->value,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$server->settings->update([
|
||||||
|
'is_usable' => true,
|
||||||
|
'is_reachable' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,135 +2,195 @@
|
|||||||
|
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Jobs\CheckLogDrainContainerJob;
|
use App\Jobs\CheckAndStartSentinelJob;
|
||||||
|
use App\Jobs\CheckForUpdatesJob;
|
||||||
|
use App\Jobs\CheckHelperImageJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\CleanupStaleMultiplexedConnections;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
use App\Jobs\PullCoolifyImageJob;
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\PullHelperImageJob;
|
|
||||||
use App\Jobs\PullSentinelImageJob;
|
|
||||||
use App\Jobs\PullTemplatesFromCDN;
|
use App\Jobs\PullTemplatesFromCDN;
|
||||||
use App\Jobs\ScheduledTaskJob;
|
use App\Jobs\ScheduledTaskJob;
|
||||||
use App\Jobs\ServerStatusJob;
|
use App\Jobs\ServerCheckJob;
|
||||||
|
use App\Jobs\ServerCleanupMux;
|
||||||
|
use App\Jobs\ServerStorageCheckJob;
|
||||||
|
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
|
||||||
{
|
{
|
||||||
private $all_servers;
|
private $allServers;
|
||||||
|
|
||||||
|
private InstanceSettings $settings;
|
||||||
|
|
||||||
|
private string $updateCheckFrequency;
|
||||||
|
|
||||||
|
private string $instanceTimezone;
|
||||||
|
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
$this->all_servers = Server::all();
|
$this->allServers = Server::where('ip', '!=', '1.2.3.4');
|
||||||
|
|
||||||
|
$this->settings = instanceSettings();
|
||||||
|
$this->updateCheckFrequency = $this->settings->update_check_frequency ?: '0 * * * *';
|
||||||
|
$this->instanceTimezone = $this->settings->instance_timezone ?: config('app.timezone');
|
||||||
|
|
||||||
|
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||||
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyMinute();
|
$schedule->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
|
$schedule->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer();
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->checkResources($schedule);
|
||||||
$this->check_resources($schedule);
|
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->checkScheduledBackups($schedule);
|
||||||
$this->check_scheduled_tasks($schedule);
|
$this->checkScheduledTasks($schedule);
|
||||||
|
|
||||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->command('cleanup:unreachable-servers')->daily();
|
$schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
|
||||||
$schedule->job(new PullCoolifyImageJob)->everyTenMinutes()->onOneServer();
|
$schedule->job(new PullTemplatesFromCDN)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
$schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
|
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
$this->scheduleUpdates($schedule);
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->checkResources($schedule);
|
||||||
$this->check_resources($schedule);
|
|
||||||
$this->pull_images($schedule);
|
$this->pullImages($schedule);
|
||||||
$this->check_scheduled_tasks($schedule);
|
|
||||||
|
$this->checkScheduledBackups($schedule);
|
||||||
|
$this->checkScheduledTasks($schedule);
|
||||||
|
|
||||||
$schedule->command('cleanup:database --yes')->daily();
|
$schedule->command('cleanup:database --yes')->daily();
|
||||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function pull_images($schedule)
|
private function pullImages($schedule): void
|
||||||
{
|
{
|
||||||
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
if (config('coolify.is_sentinel_enabled')) {
|
if ($server->isSentinelEnabled()) {
|
||||||
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
|
$schedule->job(function () use ($server) {
|
||||||
|
CheckAndStartSentinelJob::dispatch($server);
|
||||||
|
})->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
|
}
|
||||||
|
$schedule->job(new CheckHelperImageJob)
|
||||||
|
->cron($this->updateCheckFrequency)
|
||||||
|
->timezone($this->instanceTimezone)
|
||||||
|
->onOneServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function scheduleUpdates($schedule): void
|
||||||
|
{
|
||||||
|
$schedule->job(new CheckForUpdatesJob)
|
||||||
|
->cron($this->updateCheckFrequency)
|
||||||
|
->timezone($this->instanceTimezone)
|
||||||
|
->onOneServer();
|
||||||
|
|
||||||
|
if ($this->settings->is_auto_update_enabled) {
|
||||||
|
$autoUpdateFrequency = $this->settings->auto_update_frequency;
|
||||||
|
$schedule->job(new UpdateCoolifyJob)
|
||||||
|
->cron($autoUpdateFrequency)
|
||||||
|
->timezone($this->instanceTimezone)
|
||||||
|
->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function check_resources($schedule)
|
private function checkResources($schedule): void
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
$servers = $this->allServers->whereHas('team.subscription')->get();
|
||||||
$own = Team::find(0)->servers;
|
$own = Team::find(0)->servers;
|
||||||
$servers = $servers->merge($own);
|
$servers = $servers->merge($own);
|
||||||
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
|
||||||
} else {
|
} else {
|
||||||
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
$servers = $this->allServers->get();
|
||||||
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
|
||||||
}
|
|
||||||
foreach ($containerServers as $server) {
|
|
||||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
|
||||||
if ($server->isLogDrainEnabled()) {
|
|
||||||
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
|
|
||||||
|
// Sentinel check
|
||||||
|
$lastSentinelUpdate = $server->sentinel_updated_at;
|
||||||
|
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
||||||
|
// Check container status every minute if Sentinel does not activated
|
||||||
|
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
||||||
|
// $schedule->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
|
||||||
|
|
||||||
|
// Check storage usage every 10 minutes if Sentinel does not activated
|
||||||
|
$schedule->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
||||||
|
}
|
||||||
|
if ($server->settings->force_docker_cleanup) {
|
||||||
|
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
|
} else {
|
||||||
|
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup multiplexed connections every hour
|
||||||
|
$schedule->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
||||||
|
|
||||||
|
// Temporary solution until we have better memory management for Sentinel
|
||||||
|
if ($server->isSentinelEnabled()) {
|
||||||
|
$schedule->job(function () use ($server) {
|
||||||
|
$server->restartContainer('coolify-sentinel');
|
||||||
|
})->daily()->onOneServer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function check_scheduled_backups($schedule)
|
private function checkScheduledBackups($schedule): void
|
||||||
{
|
{
|
||||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
$scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get();
|
||||||
if ($scheduled_backups->isEmpty()) {
|
if ($scheduled_backups->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foreach ($scheduled_backups as $scheduled_backup) {
|
foreach ($scheduled_backups as $scheduled_backup) {
|
||||||
if (! $scheduled_backup->enabled) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (is_null(data_get($scheduled_backup, 'database'))) {
|
if (is_null(data_get($scheduled_backup, 'database'))) {
|
||||||
ray('database not found');
|
|
||||||
$scheduled_backup->delete();
|
$scheduled_backup->delete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$server = $scheduled_backup->server();
|
||||||
|
|
||||||
|
if (is_null($server)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||||
}
|
}
|
||||||
$schedule->job(new DatabaseBackupJob(
|
$schedule->job(new DatabaseBackupJob(
|
||||||
backup: $scheduled_backup
|
backup: $scheduled_backup
|
||||||
))->cron($scheduled_backup->frequency)->onOneServer();
|
))->cron($scheduled_backup->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function check_scheduled_tasks($schedule)
|
private function checkScheduledTasks($schedule): void
|
||||||
{
|
{
|
||||||
$scheduled_tasks = ScheduledTask::all();
|
$scheduled_tasks = ScheduledTask::where('enabled', true)->get();
|
||||||
if ($scheduled_tasks->isEmpty()) {
|
if ($scheduled_tasks->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foreach ($scheduled_tasks as $scheduled_task) {
|
foreach ($scheduled_tasks as $scheduled_task) {
|
||||||
if ($scheduled_task->enabled === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$service = $scheduled_task->service;
|
$service = $scheduled_task->service;
|
||||||
$application = $scheduled_task->application;
|
$application = $scheduled_task->application;
|
||||||
|
|
||||||
if (! $application && ! $service) {
|
if (! $application && ! $service) {
|
||||||
ray('application/service attached to scheduled task does not exist');
|
|
||||||
$scheduled_task->delete();
|
$scheduled_task->delete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -145,12 +205,18 @@ class Kernel extends ConsoleKernel
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$server = $scheduled_task->server();
|
||||||
|
if (! $server) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
||||||
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
||||||
}
|
}
|
||||||
$schedule->job(new ScheduledTaskJob(
|
$schedule->job(new ScheduledTaskJob(
|
||||||
task: $scheduled_task
|
task: $scheduled_task
|
||||||
))->cron($scheduled_task->frequency)->onOneServer();
|
))->cron($scheduled_task->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,5 @@ class ServerMetadata extends Data
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
public ?ProxyTypes $type,
|
public ?ProxyTypes $type,
|
||||||
public ?ProxyStatus $status
|
public ?ProxyStatus $status
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ namespace App\Enums;
|
|||||||
enum ActivityTypes: string
|
enum ActivityTypes: string
|
||||||
{
|
{
|
||||||
case INLINE = 'inline';
|
case INLINE = 'inline';
|
||||||
|
case COMMAND = 'command';
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user