mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-01 12:33:45 +00:00
Compare commits
308 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d9c728a64 | ||
|
|
12a8e9b0e1 | ||
|
|
649cc2dac2 | ||
|
|
c169f1f64b | ||
|
|
5ecf31d1fc | ||
|
|
e937d30545 | ||
|
|
595a2414b1 | ||
|
|
07ed726c88 | ||
|
|
d373815f98 | ||
|
|
8bb8a7faa3 | ||
|
|
428c40aab5 | ||
|
|
6f578855a0 | ||
|
|
8967315c49 | ||
|
|
162fb7bfc5 | ||
|
|
4bdb5c9030 | ||
|
|
a2ea8814cf | ||
|
|
cbc2f1f015 | ||
|
|
35b9b7fdf2 | ||
|
|
d92819ab60 | ||
|
|
ea877f3623 | ||
|
|
5818c9cf6b | ||
|
|
b2bab451d3 | ||
|
|
7b4559c5e6 | ||
|
|
682b45a2b5 | ||
|
|
d44e3a1091 | ||
|
|
d2d56f136b | ||
|
|
9b48a99798 | ||
|
|
1322dc9c23 | ||
|
|
f71fb7266d | ||
|
|
35f23cfb96 | ||
|
|
0c4ce55a15 | ||
|
|
77ee80b562 | ||
|
|
2621292dc1 | ||
|
|
62a4d7055a | ||
|
|
3fd41c0a92 | ||
|
|
0e8291cd86 | ||
|
|
175b89ced2 | ||
|
|
2313fed546 | ||
|
|
e1a6c3e776 | ||
|
|
7037c779e2 | ||
|
|
f124a1e60d | ||
|
|
7ebb0a4579 | ||
|
|
4962c606bc | ||
|
|
b728d69ab0 | ||
|
|
3d6e53602c | ||
|
|
1c6450da24 | ||
|
|
15fe5bd864 | ||
|
|
08e4afbbc4 | ||
|
|
06fd7286da | ||
|
|
fce1e34fc8 | ||
|
|
0739e0f5e7 | ||
|
|
faf7ba50e6 | ||
|
|
e8179ec519 | ||
|
|
7948a0309f | ||
|
|
47277a68ec | ||
|
|
60f75f9deb | ||
|
|
e90d1af884 | ||
|
|
e3046698a5 | ||
|
|
bb773e1118 | ||
|
|
dcf91cc034 | ||
|
|
cddf8476de | ||
|
|
51c43e7457 | ||
|
|
7cac243589 | ||
|
|
0cfd8ed5f0 | ||
|
|
8318598cb5 | ||
|
|
2f692da1c9 | ||
|
|
ecd98bfcd5 | ||
|
|
ba860398f3 | ||
|
|
1f9af39fa7 | ||
|
|
62b995d26c | ||
|
|
b28d118609 | ||
|
|
81d0589c55 | ||
|
|
07893b432b | ||
|
|
568d47d1dd | ||
|
|
85cce4e453 | ||
|
|
888c1f7697 | ||
|
|
79c8ce7572 | ||
|
|
121afaa18c | ||
|
|
e48ad87cdb | ||
|
|
a587cae251 | ||
|
|
c04c1530f5 | ||
|
|
f4ee61fc6a | ||
|
|
b6080c2c8e | ||
|
|
1738286983 | ||
|
|
703bf51705 | ||
|
|
aa4980289d | ||
|
|
dd8a2dd3c1 | ||
|
|
3086ef1462 | ||
|
|
0b33315991 | ||
|
|
b6b9179bb3 | ||
|
|
9122983f22 | ||
|
|
ccdcea665d | ||
|
|
7b5aa78557 | ||
|
|
e38b29d833 | ||
|
|
d4679a8be1 | ||
|
|
7ac45aa706 | ||
|
|
8862b50c98 | ||
|
|
b5a56892fd | ||
|
|
664a990c60 | ||
|
|
16e472da19 | ||
|
|
7dd0588bfe | ||
|
|
a95ebb4d56 | ||
|
|
75f266fa9f | ||
|
|
90fd0ebf12 | ||
|
|
4aeb8ff02b | ||
|
|
8bc22e185b | ||
|
|
5244ef60bd | ||
|
|
f3748fc294 | ||
|
|
03d419e591 | ||
|
|
9e90daf414 | ||
|
|
420f1df998 | ||
|
|
f672a08afb | ||
|
|
67d44713e7 | ||
|
|
0b950f444a | ||
|
|
8712af7379 | ||
|
|
35dfb1b0f8 | ||
|
|
2edcd01493 | ||
|
|
72c08e4496 | ||
|
|
1a40bebaae | ||
|
|
25f5060333 | ||
|
|
6e48c94c6e | ||
|
|
410b55cf03 | ||
|
|
0881d001d6 | ||
|
|
483b4f8eb7 | ||
|
|
c9a52c552b | ||
|
|
72f0118506 | ||
|
|
bc13ad6b82 | ||
|
|
b145691e1e | ||
|
|
5d476e3924 | ||
|
|
3f3bf075fa | ||
|
|
6498e5b6f0 | ||
|
|
a3caad239c | ||
|
|
117fbeb07c | ||
|
|
33e9c9b0f9 | ||
|
|
577927a20b | ||
|
|
409d5ef60e | ||
|
|
f1a1deff26 | ||
|
|
9d42a7f146 | ||
|
|
826b64d056 | ||
|
|
2c9491d81c | ||
|
|
d378bb94be | ||
|
|
d74cfd09ce | ||
|
|
91c845732e | ||
|
|
83698ac8f2 | ||
|
|
f718604ac4 | ||
|
|
3e188bf947 | ||
|
|
f70160a17c | ||
|
|
5722d1a220 | ||
|
|
bae9ea79de | ||
|
|
f3845ce30a | ||
|
|
d57c9d8aa0 | ||
|
|
c7c2924990 | ||
|
|
aa30e83f4a | ||
|
|
77455cd444 | ||
|
|
2777fbc0ec | ||
|
|
2c929b300a | ||
|
|
27ad4441ee | ||
|
|
63729c7bbf | ||
|
|
1e68444f10 | ||
|
|
f8dfe643f4 | ||
|
|
66cf6df4b3 | ||
|
|
29acc4ee25 | ||
|
|
f73983e3dd | ||
|
|
44d417c07e | ||
|
|
1d72f76072 | ||
|
|
c2f7e85022 | ||
|
|
9e6486fa66 | ||
|
|
28bcd0023c | ||
|
|
84093afaf6 | ||
|
|
40347744e0 | ||
|
|
060988d923 | ||
|
|
b093c9757c | ||
|
|
25976a4870 | ||
|
|
4f9e1a3e5e | ||
|
|
c8218e6901 | ||
|
|
cc10d08a7c | ||
|
|
e5a980d544 | ||
|
|
59c7f2cb56 | ||
|
|
d2a306dab9 | ||
|
|
681a745fc7 | ||
|
|
abb6655fef | ||
|
|
aa586a5677 | ||
|
|
87ee3c511c | ||
|
|
a17daf96be | ||
|
|
a419496066 | ||
|
|
c668d2d1ff | ||
|
|
4611332a8f | ||
|
|
8b3eed5895 | ||
|
|
9861ad4045 | ||
|
|
5fb560dbbf | ||
|
|
435c0baa87 | ||
|
|
bb1454d255 | ||
|
|
a882d3c713 | ||
|
|
6010b3209d | ||
|
|
504133232a | ||
|
|
d816863d31 | ||
|
|
6b475cc1bf | ||
|
|
5ef2d476a4 | ||
|
|
3eb909d3bc | ||
|
|
a102e812cf | ||
|
|
254611dda8 | ||
|
|
d8f9e5910f | ||
|
|
68d4a30eb0 | ||
|
|
5c05ea4463 | ||
|
|
a2dac9394a | ||
|
|
7fabeec08d | ||
|
|
a9228b110e | ||
|
|
148c7d212c | ||
|
|
98154549cf | ||
|
|
3d4f02afde | ||
|
|
b57e2960f2 | ||
|
|
110fac944d | ||
|
|
518ba3502d | ||
|
|
676cee9e3e | ||
|
|
ec7b18556e | ||
|
|
8d2b280b03 | ||
|
|
ba0e29aaf4 | ||
|
|
fccd31009c | ||
|
|
34e213202a | ||
|
|
e7ab43c018 | ||
|
|
0c87a8f4ec | ||
|
|
54e69de2af | ||
|
|
545613b554 | ||
|
|
5c7b9473f8 | ||
|
|
a32e53c5a3 | ||
|
|
b44f4e49e8 | ||
|
|
321b8559e0 | ||
|
|
69794f4ca8 | ||
|
|
4e1663e9db | ||
|
|
2e5ed5969d | ||
|
|
ab055e0692 | ||
|
|
6c2717ad08 | ||
|
|
3ac52af673 | ||
|
|
22d99dec52 | ||
|
|
6a654e31bd | ||
|
|
d07cf59460 | ||
|
|
90e435d392 | ||
|
|
dfadadda10 | ||
|
|
0a7c7036f1 | ||
|
|
9215054d72 | ||
|
|
22de8a7d2b | ||
|
|
6f5b92d322 | ||
|
|
9ef44f9ffb | ||
|
|
5a604b2fc4 | ||
|
|
65b8d3c6d4 | ||
|
|
e05e0da058 | ||
|
|
e4854aaa1b | ||
|
|
2531574a9a | ||
|
|
3aa31f3da6 | ||
|
|
645337b09c | ||
|
|
bd3220ad73 | ||
|
|
6528bc5766 | ||
|
|
93af54ef84 | ||
|
|
08b9c79298 | ||
|
|
22bd305e8f | ||
|
|
dbad08f4dd | ||
|
|
b5232f3d32 | ||
|
|
58234ef65e | ||
|
|
d211362ab3 | ||
|
|
629316d68a | ||
|
|
05084cb69c | ||
|
|
401c410adb | ||
|
|
4888f4c405 | ||
|
|
b241f17eee | ||
|
|
bc22ec63e6 | ||
|
|
458a461388 | ||
|
|
a725ff0a75 | ||
|
|
a8fd7db9a8 | ||
|
|
fbb7568786 | ||
|
|
fae175039a | ||
|
|
9196571737 | ||
|
|
0dad77af33 | ||
|
|
f43298a610 | ||
|
|
02f950edc7 | ||
|
|
817a72e753 | ||
|
|
9c2f4ec04e | ||
|
|
3f34df251e | ||
|
|
bfeaae9caa | ||
|
|
ba90a52344 | ||
|
|
a3a61dbe55 | ||
|
|
5799e6d8b0 | ||
|
|
3b533c7d06 | ||
|
|
b2944f11db | ||
|
|
25e2b812cb | ||
|
|
59383d3678 | ||
|
|
7b2d09f9d1 | ||
|
|
d30faf7bc4 | ||
|
|
b9d3f9da62 | ||
|
|
86ee9c09ce | ||
|
|
4f32b48d29 | ||
|
|
04ce622465 | ||
|
|
ed7817906a | ||
|
|
e0748ee240 | ||
|
|
25480fe624 | ||
|
|
b0039885eb | ||
|
|
cadb12986c | ||
|
|
63a07e7649 | ||
|
|
08a80876f9 | ||
|
|
ae4c889fa2 | ||
|
|
f072823f00 | ||
|
|
bf475e538c | ||
|
|
fe477ba325 | ||
|
|
a26bc65137 | ||
|
|
b73db2d725 | ||
|
|
8168b564ea | ||
|
|
2b8c9920d8 | ||
|
|
548fc21e40 | ||
|
|
c2ea8996ee |
@@ -6,13 +6,7 @@ APP_KEY=
|
||||
APP_URL=http://localhost
|
||||
APP_PORT=8000
|
||||
APP_DEBUG=true
|
||||
MUX_ENABLED=false
|
||||
|
||||
# Enable Laravel Telescope for debugging
|
||||
TELESCOPE_ENABLED=false
|
||||
|
||||
# Selenium Driver URL for Dusk
|
||||
DUSK_DRIVER_URL=http://selenium:4444
|
||||
SSH_MUX_ENABLED=false
|
||||
|
||||
# PostgreSQL Database Configuration
|
||||
DB_DATABASE=coolify
|
||||
@@ -21,9 +15,22 @@ DB_PASSWORD=password
|
||||
DB_HOST=host.docker.internal
|
||||
DB_PORT=5432
|
||||
|
||||
#Set custom ray port
|
||||
# Ray Configuration
|
||||
# Set to true to enable Ray
|
||||
RAY_ENABLED=false
|
||||
# Set custom ray port
|
||||
RAY_PORT=
|
||||
|
||||
# Clockwork Configuration
|
||||
CLOCKWORK_ENABLED=false
|
||||
CLOCKWORK_QUEUE_COLLECT=true
|
||||
|
||||
# Enable Laravel Telescope for debugging
|
||||
TELESCOPE_ENABLED=false
|
||||
|
||||
# Selenium Driver URL for Dusk
|
||||
DUSK_DRIVER_URL=http://selenium:4444
|
||||
|
||||
# Special Keys for Andras
|
||||
# For cache purging
|
||||
BUNNY_API_KEY=
|
||||
|
||||
75
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
75
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
@@ -1,46 +1,65 @@
|
||||
name: Bug report
|
||||
description: "Create a new bug report."
|
||||
name: 🐞 Bug Report
|
||||
description: "File a new bug report."
|
||||
title: "[Bug]: "
|
||||
labels: ["🐛 Bug", "🔍 Triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >-
|
||||
# 💎 Bounty program (with
|
||||
[algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
||||
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).
|
||||
|
||||
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.
|
||||
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: Exception or Error
|
||||
description: Please provide error logs if possible.
|
||||
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: Version
|
||||
description: Coolify's version (see top of your screen).
|
||||
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: checkboxes
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Cloud?
|
||||
description: "Are you using the cloud version of Coolify?"
|
||||
label: Are you using Coolify Cloud?
|
||||
options:
|
||||
- label: 'Yes'
|
||||
required: false
|
||||
- label: 'No'
|
||||
required: false
|
||||
- "No (self-hosted)"
|
||||
- "Yes (Coolify Cloud)"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Operating System and Version (self-hosted)
|
||||
description: Run `cat /etc/os-release` or `lsb_release -a` in your terminal and provide the operating system and version.
|
||||
placeholder: "Ubuntu 22.04"
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: Any other relevant details about the issue.
|
||||
|
||||
31
.github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml
vendored
Normal file
31
.github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: 💎 Enhancement Bounty
|
||||
description: "Propose a new feature, service, or improvement with an attached bounty."
|
||||
title: "[Enhancement]: "
|
||||
labels: ["✨ Enhancement", "🔍 Triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> [!IMPORTANT]
|
||||
> **This issue template is exclusively for proposing new features, services, or improvements with an attached bounty.** Enhancements without a bounty can be discussed in the appropriate category of [Github Discussions](https://github.com/coollabsio/coolify/discussions).
|
||||
|
||||
# 💎 Add a Bounty (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
||||
- [Click here to add the required bounty](https://console.algora.io/org/coollabsio/bounties/new)
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Request Type
|
||||
description: Select the type of request you are making.
|
||||
options:
|
||||
- New Feature
|
||||
- New Service
|
||||
- Improvement
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: Provide a detailed description of the feature, improvement, or service you are proposing.
|
||||
validations:
|
||||
required: true
|
||||
20
.github/ISSUE_TEMPLATE/config.yml
vendored
20
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,18 @@
|
||||
blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
- name: 🤔 Community Support (Chat)
|
||||
- name: 🤔 Questions and Community Support
|
||||
url: https://coollabs.io/discord
|
||||
about: Reach out to us on Discord.
|
||||
- name: 🙋♂️ Feature Requests
|
||||
url: https://github.com/coollabsio/coolify/discussions/categories/new-features
|
||||
about: All feature requests will be discussed here.
|
||||
about: If you have any questions, reach out to us on Discord inside the "#support" channel.
|
||||
|
||||
- name: 💡 Feature Request
|
||||
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests
|
||||
about: Suggest a new feature for Coolify.
|
||||
|
||||
- name: ⚙️ Service Request
|
||||
url: https://github.com/coollabsio/coolify/discussions/categories/service-requests
|
||||
about: Request a new service integration for Coolify.
|
||||
|
||||
- name: 🔧 Improvements
|
||||
url: https://github.com/coollabsio/coolify/discussions/categories/improvements
|
||||
about: Suggest improvements to existing features for Coolify.
|
||||
|
||||
23
.github/workflows/coolify-helper-next.yml
vendored
23
.github/workflows/coolify-helper-next.yml
vendored
@@ -25,6 +25,10 @@ jobs:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
@@ -33,7 +37,9 @@ jobs:
|
||||
file: docker/coolify-helper/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
|
||||
labels: |
|
||||
coolify.managed=true
|
||||
aarch64:
|
||||
runs-on: [ self-hosted, arm64 ]
|
||||
permissions:
|
||||
@@ -47,6 +53,10 @@ jobs:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
@@ -55,7 +65,9 @@ jobs:
|
||||
file: docker/coolify-helper/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
|
||||
labels: |
|
||||
coolify.managed=true
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -75,10 +87,15 @@ jobs:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
|
||||
|
||||
22
.github/workflows/coolify-helper.yml
vendored
22
.github/workflows/coolify-helper.yml
vendored
@@ -25,6 +25,10 @@ jobs:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
@@ -33,7 +37,9 @@ jobs:
|
||||
file: docker/coolify-helper/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||
labels: |
|
||||
coolify.managed=true
|
||||
aarch64:
|
||||
runs-on: [ self-hosted, arm64 ]
|
||||
permissions:
|
||||
@@ -47,6 +53,10 @@ jobs:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
@@ -55,7 +65,9 @@ jobs:
|
||||
file: docker/coolify-helper/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||
labels: |
|
||||
coolify.managed=true
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -75,9 +87,13 @@ jobs:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
|
||||
103
.github/workflows/coolify-realtime.yml
vendored
Normal file
103
.github/workflows/coolify-realtime.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
name: Coolify Realtime (v4)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "next" ]
|
||||
paths:
|
||||
- .github/workflows/coolify-realtime.yml
|
||||
- docker/coolify-realtime/Dockerfile
|
||||
- docker/coolify-realtime/terminal-server.js
|
||||
- docker/coolify-realtime/package.json
|
||||
- docker/coolify-realtime/soketi-entrypoint.sh
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify-realtime"
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
no-cache: true
|
||||
context: .
|
||||
file: docker/coolify-realtime/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||
labels: |
|
||||
coolify.managed=true
|
||||
aarch64:
|
||||
runs-on: [ self-hosted, arm64 ]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
no-cache: true
|
||||
context: .
|
||||
file: docker/coolify-realtime/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||
labels: |
|
||||
coolify.managed=true
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs: [ amd64, aarch64 ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Version
|
||||
id: version
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||
12
.github/workflows/pr-build.yml
vendored
12
.github/workflows/pr-build.yml
vendored
@@ -16,6 +16,12 @@ env:
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
actions: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
@@ -37,6 +43,9 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
actions: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
@@ -58,6 +67,9 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
actions: write
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
2
.github/workflows/production-build.yml
vendored
2
.github/workflows/production-build.yml
vendored
@@ -4,6 +4,8 @@ on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths-ignore:
|
||||
- .github/workflows/coolify-helper.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
- templates/service-templates.json
|
||||
|
||||
env:
|
||||
|
||||
75
.github/workflows/remove-labels-and-assignees-on-close.yml
vendored
Normal file
75
.github/workflows/remove-labels-and-assignees-on-close.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
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 { data: closedIssues } = await github.rest.search.issuesAndPullRequests({
|
||||
q: `repo:${owner}/${repo} is:issue is:closed linked:${context.payload.pull_request.number}`,
|
||||
per_page: 100
|
||||
});
|
||||
for (const issue of closedIssues.items) {
|
||||
await processIssue(issue.number);
|
||||
}
|
||||
}
|
||||
124
CONTRIBUTING.md
124
CONTRIBUTING.md
@@ -1,17 +1,27 @@
|
||||
# Contributing
|
||||
# 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
|
||||
|
||||
## Code Contribution
|
||||
1. [Setup Development Environment](#1-setup-development-environment)
|
||||
2. [Verify Installation](#2-verify-installation-optional)
|
||||
3. [Fork and Setup Local Repository](#3-fork-and-setup-local-repository)
|
||||
4. [Set up Environment Variables](#4-set-up-environment-variables)
|
||||
5. [Start Coolify](#5-start-coolify)
|
||||
6. [Start Development](#6-start-development)
|
||||
7. [Development Notes](#7-development-notes)
|
||||
8. [Create a Pull Request](#8-create-a-pull-request)
|
||||
9. [Additional Contribution Guidelines](#additional-contribution-guidelines)
|
||||
|
||||
## 1. Setup your development environment
|
||||
## 1. Setup Development Environment
|
||||
|
||||
Follow the steps below for your operating system:
|
||||
|
||||
### Windows
|
||||
<details>
|
||||
<summary><strong>Windows</strong></summary>
|
||||
|
||||
1. Install `docker-ce`, Docker Desktop (or similar):
|
||||
- Docker CE (recommended):
|
||||
@@ -25,7 +35,10 @@ Follow the steps below for your operating system:
|
||||
2. Install Spin:
|
||||
- Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2)
|
||||
|
||||
### MacOS
|
||||
</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):
|
||||
@@ -36,7 +49,10 @@ Follow the steps below for your operating system:
|
||||
2. Install Spin:
|
||||
- Follow the instructions to install Spin on MacOS from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-macos/#download-and-install-spin)
|
||||
|
||||
### Linux
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Linux</strong></summary>
|
||||
|
||||
1. Install Docker Engine, Docker Desktop (or similar):
|
||||
- Docker Engine (recommended, as there is no VM overhead):
|
||||
@@ -47,8 +63,9 @@ Follow the steps below for your operating system:
|
||||
2. Install Spin:
|
||||
- Follow the instructions to install Spin on Linux from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-linux#configure-docker-permissions)
|
||||
|
||||
</details>
|
||||
|
||||
## 2. Verify installation (optional)
|
||||
## 2. Verify Installation (Optional)
|
||||
|
||||
After installing Docker (or Orbstack) and Spin, verify the installation:
|
||||
|
||||
@@ -60,25 +77,20 @@ After installing Docker (or Orbstack) and Spin, verify the installation:
|
||||
```
|
||||
You should see version information for both Docker and Spin.
|
||||
|
||||
|
||||
## 3. Fork the Coolify repository and setup your local repository
|
||||
## 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 (below are some popular choices, choose one):
|
||||
2. Install a code editor on your machine (choose one):
|
||||
|
||||
- Visual Studio Code (recommended free):
|
||||
- Windows/macOS/Linux: Download and install from [https://code.visualstudio.com/download](https://code.visualstudio.com/download)
|
||||
|
||||
- Cursor (recommended but paid for getting the full benefits):
|
||||
- Windows/macOS/Linux: Download and install from [https://www.cursor.com/](https://www.cursor.com/)
|
||||
|
||||
- Zed (very fast code editor):
|
||||
- macOS/Linux: Download and install from [https://zed.dev/download](https://zed.dev/download)
|
||||
- Windows: Not available yet
|
||||
| Editor | Platform | Download Link |
|
||||
|--------|----------|---------------|
|
||||
| Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download) |
|
||||
| Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/) |
|
||||
| Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download) |
|
||||
|
||||
3. Clone the Coolify Repository from your fork to your local machine
|
||||
- Use `git clone` in the command line
|
||||
- Use `git clone` in the command line, or
|
||||
- Use GitHub Desktop (recommended):
|
||||
- Download and install from [https://desktop.github.com/](https://desktop.github.com/)
|
||||
- Open GitHub Desktop and login with your GitHub account
|
||||
@@ -86,37 +98,32 @@ After installing Docker (or Orbstack) and Spin, verify the installation:
|
||||
|
||||
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.
|
||||
|
||||
> [!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.
|
||||
|
||||
> [!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
|
||||
|
||||
@@ -126,15 +133,17 @@ Note: If you change environment variables afterwards or anything seems broken, p
|
||||
- Password: `password`
|
||||
|
||||
2. Additional development tools:
|
||||
- Laravel Horizon (scheduler): `http://localhost:8000/horizon`
|
||||
Note: Only accessible when logged in as root user
|
||||
- Mailpit (email catcher): `http://localhost:8025`
|
||||
- Telescope (debugging tool): `http://localhost:8000/telescope`
|
||||
Note: Disabled by default (so the database is not overloaded), enable by adding the following environment variable to your `.env` file:
|
||||
```env
|
||||
TELESCOPE_ENABLED=true
|
||||
```
|
||||
| Tool | URL | Note |
|
||||
|------|-----|------|
|
||||
| Laravel Horizon (scheduler) | `http://localhost:8000/horizon` | Only accessible when logged in as root user |
|
||||
| Mailpit (email catcher) | `http://localhost:8025` | |
|
||||
| Telescope (debugging tool) | `http://localhost:8000/telescope` | Disabled by default |
|
||||
|
||||
> [!NOTE]
|
||||
> To enable Telescope, add the following to your `.env` file:
|
||||
> ```env
|
||||
> TELESCOPE_ENABLED=true
|
||||
> ```
|
||||
|
||||
## 7. Development Notes
|
||||
|
||||
@@ -150,18 +159,12 @@ When working on Coolify, keep the following in mind:
|
||||
docker exec -it coolify php artisan migrate:fresh --seed
|
||||
```
|
||||
|
||||
3. **Troubleshooting**: If you encounter unexpected behavior, ensure your database is up-to-date with the latest migrations and if possible reset the development setup to eliminate any envrionement specific issues.
|
||||
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.
|
||||
|
||||
Remember, forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches.
|
||||
> [!IMPORTANT]
|
||||
> Forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches.
|
||||
|
||||
|
||||
## 8. Contributing a New Service
|
||||
|
||||
To add a new service to Coolify, please refer to our documentation:
|
||||
[Adding a New Service](https://coolify.io/docs/knowledge-base/add-a-service)
|
||||
|
||||
|
||||
## 9. Create a Pull Request
|
||||
## 8. Create a Pull Request
|
||||
|
||||
1. After making changes or adding a new service:
|
||||
- Commit your changes to your forked repository.
|
||||
@@ -179,11 +182,26 @@ To add a new service to Coolify, please refer to our documentation:
|
||||
- In the description, explain the changes you've made.
|
||||
- Reference any related issues by using keywords like "Fixes #123" or "Closes #456".
|
||||
|
||||
4. Important note:
|
||||
Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
|
||||
> [!IMPORTANT]
|
||||
> Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
|
||||
|
||||
5. Submit your PR:
|
||||
4. Submit your PR:
|
||||
- Review your changes one last time.
|
||||
- Click "Create pull request" to submit.
|
||||
|
||||
> [!NOTE]
|
||||
> Make sure your PR is out of draft mode as soon as it's ready for review. PRs that are in draft mode for a long time may be closed by maintainers.
|
||||
|
||||
After submission, maintainers will review your PR and may request changes or provide feedback.
|
||||
|
||||
## Additional Contribution Guidelines
|
||||
|
||||
### Contributing a New Service
|
||||
|
||||
To add a new service to Coolify, please refer to our documentation:
|
||||
[Adding a New Service](https://coolify.io/docs/knowledge-base/contribute/service)
|
||||
|
||||
### Contributing to Documentation
|
||||
|
||||
To contribute to the Coolify documentation, please refer to this guide:
|
||||
[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/CONTRIBUTING.md)
|
||||
|
||||
43
README.md
43
README.md
@@ -35,22 +35,32 @@ Thank you so much!
|
||||
|
||||
Special thanks to our biggest sponsors!
|
||||
|
||||
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
|
||||
<a href="http://htznr.li/CoolifyXHetzner" target="_blank"><img src="./other/logos/hetzner.jpg" alt="hetzner logo" width="150"/></a>
|
||||
<a href="https://logto.io/?ref=coolify" target="_blank"><img src="./other/logos/logto.webp" alt="logto logo" width="150"/></a>
|
||||
<a href="https://bc.direct/?ref=coolify.io" target="_blank"><img src="./other/logos/bc.png" alt="bc direct logo" width="200"/></a>
|
||||
<a href="https://www.quantcdn.io/?ref=coolify.io" target="_blank"><img src="./other/logos/quant.svg" alt="quantcdn logo" width="150"/></a>
|
||||
<a href="https://arcjet.com/?ref=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a>
|
||||
<a href="https://supa.guide/?ref=coolify.io" target="_blank"><img src="./other/logos/supaguide.png" alt="supaguide logo" width="200"/></a>
|
||||
<a href="https://tigrisdata.com/?ref=coolify.io" target="_blank"><img src="./other/logos/tigris.svg" alt="tigris logo" width="140"/></a>
|
||||
<a href="https://fractalnetworks.co/?ref=coolify.io" target="_blank"><img src="./other/logos/fractal.svg" alt="fractal logo" width="180"/></a>
|
||||
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
|
||||
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a>
|
||||
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
|
||||
<a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a>
|
||||
<a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a>
|
||||
<a href="https://jobscollider.com/remote-jobs?ref=coolify.io" target="_blank"><img src="./other/logos/jobscollider.svg" alt="jobscollider logo" width="200"/></a>
|
||||
<a href="https://hostinger.com?ref=coolify.io" target="_blank"><img src="./other/logos/hostinger.svg" alt="hostinger logo" width="200"/></a>
|
||||
### Special Sponsors
|
||||
|
||||

|
||||
|
||||
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
|
||||
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
|
||||
* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities.
|
||||
* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies.
|
||||
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution.
|
||||
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks.
|
||||
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
|
||||
* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management.
|
||||
* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions.
|
||||
* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies.
|
||||
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets.
|
||||
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers.
|
||||
* [Latitude](https://latitude.sh/?ref=coolify.io) - A cloud computing platform offering bare metal servers and cloud instances for developers and businesses.
|
||||
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities.
|
||||
* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries.
|
||||
* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools.
|
||||
* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services.
|
||||
* [Ubicloud](https://ubicloud.com/?ref=coolify.io) - An open-source alternative to hyperscale cloud providers, offering high-performance cloud computing services.
|
||||
* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses.
|
||||
* [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly.
|
||||
* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
||||
|
||||
|
||||
## Github Sponsors ($40+)
|
||||
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
||||
@@ -77,6 +87,7 @@ Special thanks to our biggest sponsors!
|
||||
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
|
||||
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
||||
<a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a>
|
||||
<a href="https://x.com/adithsuhas17?utm_source=coolify.io"><img src="https://github.com/adith-suhas-sv.png" width="60px" alt="Adith Suhas" /></a>
|
||||
|
||||
## Organizations
|
||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||
|
||||
45
RELEASE.md
Normal file
45
RELEASE.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Coolify Release Guide
|
||||
|
||||
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed.
|
||||
|
||||
## Release Process
|
||||
|
||||
1. **Development on `next` or separate branches**
|
||||
- Changes, fixes and new features are developed on the `next` or even separate branches.
|
||||
|
||||
2. **Merging to `main`**
|
||||
- Once changes are ready, they are merged from `next` into the `main` branch.
|
||||
|
||||
3. **Building the release**
|
||||
- After merging to `main`, a new release is built.
|
||||
- Note: A push to `main` does not automatically mean a new version is released.
|
||||
|
||||
4. **Creating a GitHub release**
|
||||
- A new release is created on GitHub with the new version details.
|
||||
|
||||
5. **Updating the CDN**
|
||||
- The final step is updating the version information on the CDN:
|
||||
[https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json)
|
||||
|
||||
> [!NOTE]
|
||||
> The CDN update may not occur immediately after the GitHub release. It can happen hours or even days later due to additional testing, stability checks, or potential hotfixes.
|
||||
|
||||
|
||||
## Version Availability
|
||||
|
||||
It's important to understand that a new version released on GitHub may not immediately become available for users to update (through manual or auto-update).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you see a new release on GitHub but haven't received the update, it's likely because the CDN hasn't been updated yet. This is intentional and ensures stability and allows for hotfixes before the new version is officially released.
|
||||
|
||||
## Manually Update to Specific Versions
|
||||
|
||||
> [!CAUTION]
|
||||
> Updating to unreleased versions is not recommended and may cause issues. Use at your own risk!
|
||||
|
||||
To update your Coolify instance to a specific (unreleased) version, use the following command:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s <version>
|
||||
```
|
||||
-> Replace `<version>` with the version you want to update to (for example `4.0.0-beta.332`).
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
@@ -21,10 +22,16 @@ class CleanupDocker
|
||||
|
||||
private function getCommands(): array
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
$helperImageVersion = data_get($settings, 'helper_version');
|
||||
$helperImage = config('coolify.helper_image');
|
||||
$helperImageWithVersion = config('coolify.helper_image').':'.$helperImageVersion;
|
||||
|
||||
$commonCommands = [
|
||||
'docker container prune -f --filter "label=coolify.managed=true"',
|
||||
'docker image prune -af',
|
||||
'docker image prune -af --filter "label!=coolify.managed=true"',
|
||||
'docker builder prune -af',
|
||||
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi",
|
||||
];
|
||||
|
||||
return $commonCommands;
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use App\Jobs\PullHelperImageJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class UpdateCoolify
|
||||
@@ -27,11 +26,6 @@ class UpdateCoolify
|
||||
return;
|
||||
}
|
||||
CleanupDocker::dispatch($this->server)->onQueue('high');
|
||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
if ($response->successful()) {
|
||||
$versions = $response->json();
|
||||
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
|
||||
}
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->currentVersion = config('version');
|
||||
if (! $manual_update) {
|
||||
@@ -62,10 +56,18 @@ class UpdateCoolify
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$all_servers = Server::all();
|
||||
$servers = $all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||
foreach ($servers as $server) {
|
||||
PullHelperImageJob::dispatch($server);
|
||||
}
|
||||
|
||||
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
|
||||
|
||||
remote_process([
|
||||
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
|
||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion",
|
||||
], $this->server);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ class StartService
|
||||
$service->saveComposeConfigs();
|
||||
$commands[] = 'cd '.$service->workdir();
|
||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||
$commands[] = "echo 'Creating Docker network.'";
|
||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||
if ($service->networks()->count() > 0) {
|
||||
$commands[] = "echo 'Creating Docker network.'";
|
||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||
}
|
||||
$commands[] = 'echo Starting service.';
|
||||
$commands[] = "echo 'Pulling images.'";
|
||||
$commands[] = 'docker compose pull';
|
||||
@@ -29,7 +31,7 @@ class StartService
|
||||
$network = $service->destination->network;
|
||||
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
||||
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');
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
@@ -165,6 +166,18 @@ class CleanupStuckedResources extends Command
|
||||
echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||
foreach ($scheduled_backups as $scheduled_backup) {
|
||||
if (! $scheduled_backup->server()) {
|
||||
echo "Deleting stuck scheduledbackup: {$scheduled_backup->name}\n";
|
||||
$scheduled_backup->delete();
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck scheduledbackups: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
// Cleanup any resources that are not attached to any environment or destination or server
|
||||
try {
|
||||
$applications = Application::all();
|
||||
|
||||
@@ -4,9 +4,9 @@ namespace App\Console;
|
||||
|
||||
use App\Jobs\CheckForUpdatesJob;
|
||||
use App\Jobs\CleanupInstanceStuffsJob;
|
||||
use App\Jobs\CleanupStaleMultiplexedConnections;
|
||||
use App\Jobs\DatabaseBackupJob;
|
||||
use App\Jobs\DockerCleanupJob;
|
||||
use App\Jobs\PullCoolifyImageJob;
|
||||
use App\Jobs\PullHelperImageJob;
|
||||
use App\Jobs\PullSentinelImageJob;
|
||||
use App\Jobs\PullTemplatesFromCDN;
|
||||
@@ -30,7 +30,8 @@ class Kernel extends ConsoleKernel
|
||||
$this->all_servers = Server::all();
|
||||
$settings = InstanceSettings::get();
|
||||
|
||||
$schedule->command('telescope:prune')->daily();
|
||||
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||
|
||||
if (isDev()) {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyMinute();
|
||||
@@ -40,11 +41,12 @@ class Kernel extends ConsoleKernel
|
||||
$this->check_resources($schedule);
|
||||
$this->check_scheduled_tasks($schedule);
|
||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||
|
||||
$schedule->command('telescope:prune')->daily();
|
||||
} else {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
|
||||
$schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
$this->schedule_updates($schedule);
|
||||
@@ -139,6 +141,10 @@ class Kernel extends ConsoleKernel
|
||||
}
|
||||
|
||||
$server = $scheduled_backup->server();
|
||||
|
||||
if (! $server) {
|
||||
continue;
|
||||
}
|
||||
$serverTimezone = $server->settings->server_timezone;
|
||||
|
||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||
@@ -181,6 +187,9 @@ class Kernel extends ConsoleKernel
|
||||
}
|
||||
|
||||
$server = $scheduled_task->server();
|
||||
if (! $server) {
|
||||
continue;
|
||||
}
|
||||
$serverTimezone = $server->settings->server_timezone ?: config('app.timezone');
|
||||
|
||||
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
||||
|
||||
@@ -53,6 +53,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'List',
|
||||
description: 'List all applications.',
|
||||
path: '/applications',
|
||||
operationId: 'list-applications',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -101,6 +102,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Create (Public)',
|
||||
description: 'Create new application based on a public git repository.',
|
||||
path: '/applications/public',
|
||||
operationId: 'create-public-application',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -202,6 +204,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Create (Private - GH App)',
|
||||
description: 'Create new application based on a private repository through a Github App.',
|
||||
path: '/applications/private-github-app',
|
||||
operationId: 'create-private-github-app-application',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -303,6 +306,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Create (Private - Deploy Key)',
|
||||
description: 'Create new application based on a private repository through a Deploy Key.',
|
||||
path: '/applications/private-deploy-key',
|
||||
operationId: 'create-private-deploy-key-application',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -404,6 +408,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Create (Dockerfile)',
|
||||
description: 'Create new application based on a simple Dockerfile.',
|
||||
path: '/applications/dockerfile',
|
||||
operationId: 'create-dockerfile-application',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -490,6 +495,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Create (Docker Image)',
|
||||
description: 'Create new application based on a prebuilt docker image',
|
||||
path: '/applications/dockerimage',
|
||||
operationId: 'create-dockerimage-application',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -573,6 +579,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Create (Docker Compose)',
|
||||
description: 'Create new application based on a docker-compose file.',
|
||||
path: '/applications/dockercompose',
|
||||
operationId: 'create-dockercompose-application',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -1171,6 +1178,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Get',
|
||||
description: 'Get application by UUID.',
|
||||
path: '/applications/{uuid}',
|
||||
operationId: 'get-application-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -1235,6 +1243,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Delete',
|
||||
description: 'Delete application by UUID.',
|
||||
path: '/applications/{uuid}',
|
||||
operationId: 'delete-application-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -1321,6 +1330,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Update',
|
||||
description: 'Update application by UUID.',
|
||||
path: '/applications/{uuid}',
|
||||
operationId: 'update-application-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -1557,6 +1567,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'List Envs',
|
||||
description: 'List all envs by application UUID.',
|
||||
path: '/applications/{uuid}/envs',
|
||||
operationId: 'list-envs-by-application-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -1639,6 +1650,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Update Env',
|
||||
description: 'Update env by application UUID.',
|
||||
path: '/applications/{uuid}/envs',
|
||||
operationId: 'update-env-by-application-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -1821,6 +1833,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Update Envs (Bulk)',
|
||||
description: 'Update multiple envs by application UUID.',
|
||||
path: '/applications/{uuid}/envs/bulk',
|
||||
operationId: 'update-envs-by-application-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -2012,6 +2025,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Create Env',
|
||||
description: 'Create env by application UUID.',
|
||||
path: '/applications/{uuid}/envs',
|
||||
operationId: 'create-env-by-application-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -2171,6 +2185,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Delete Env',
|
||||
description: 'Delete env by UUID.',
|
||||
path: '/applications/{uuid}/envs/{env_uuid}',
|
||||
operationId: 'delete-env-by-application-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -2256,6 +2271,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Start',
|
||||
description: 'Start application. `Post` request is also accepted.',
|
||||
path: '/applications/{uuid}/start',
|
||||
operationId: 'start-application-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -2359,6 +2375,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Stop',
|
||||
description: 'Stop application. `Post` request is also accepted.',
|
||||
path: '/applications/{uuid}/stop',
|
||||
operationId: 'stop-application-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -2431,6 +2448,7 @@ class ApplicationsController extends Controller
|
||||
summary: 'Restart',
|
||||
description: 'Restart application. `Post` request is also accepted.',
|
||||
path: '/applications/{uuid}/restart',
|
||||
operationId: 'restart-application-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
|
||||
@@ -46,6 +46,7 @@ class DatabasesController extends Controller
|
||||
summary: 'List',
|
||||
description: 'List all databases.',
|
||||
path: '/databases',
|
||||
operationId: 'list-databases',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -91,6 +92,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Get',
|
||||
description: 'Get database by UUID.',
|
||||
path: '/databases/{uuid}',
|
||||
operationId: 'get-database-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -151,6 +153,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Update',
|
||||
description: 'Update database by UUID.',
|
||||
path: '/databases/{uuid}',
|
||||
operationId: 'update-database-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -510,6 +513,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Create (PostgreSQL)',
|
||||
description: 'Create a new PostgreSQL database.',
|
||||
path: '/databases/postgresql',
|
||||
operationId: 'create-database-postgresql',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -575,6 +579,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Create (Clickhouse)',
|
||||
description: 'Create a new Clickhouse database.',
|
||||
path: '/databases/clickhouse',
|
||||
operationId: 'create-database-clickhouse',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -636,6 +641,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Create (DragonFly)',
|
||||
description: 'Create a new DragonFly database.',
|
||||
path: '/databases/dragonfly',
|
||||
operationId: 'create-database-dragonfly',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -696,6 +702,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Create (Redis)',
|
||||
description: 'Create a new Redis database.',
|
||||
path: '/databases/redis',
|
||||
operationId: 'create-database-redis',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -757,6 +764,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Create (KeyDB)',
|
||||
description: 'Create a new KeyDB database.',
|
||||
path: '/databases/keydb',
|
||||
operationId: 'create-database-keydb',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -818,6 +826,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Create (MariaDB)',
|
||||
description: 'Create a new MariaDB database.',
|
||||
path: '/databases/mariadb',
|
||||
operationId: 'create-database-mariadb',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -882,6 +891,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Create (MySQL)',
|
||||
description: 'Create a new MySQL database.',
|
||||
path: '/databases/mysql',
|
||||
operationId: 'create-database-mysql',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -945,6 +955,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Create (MongoDB)',
|
||||
description: 'Create a new MongoDB database.',
|
||||
path: '/databases/mongodb',
|
||||
operationId: 'create-database-mongodb',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -1514,6 +1525,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Delete',
|
||||
description: 'Delete database by UUID.',
|
||||
path: '/databases/{uuid}',
|
||||
operationId: 'delete-database-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -1597,6 +1609,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Start',
|
||||
description: 'Start database. `Post` request is also accepted.',
|
||||
path: '/databases/{uuid}/start',
|
||||
operationId: 'start-database-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -1672,6 +1685,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Stop',
|
||||
description: 'Stop database. `Post` request is also accepted.',
|
||||
path: '/databases/{uuid}/stop',
|
||||
operationId: 'stop-database-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -1747,6 +1761,7 @@ class DatabasesController extends Controller
|
||||
summary: 'Restart',
|
||||
description: 'Restart database. `Post` request is also accepted.',
|
||||
path: '/databases/{uuid}/restart',
|
||||
operationId: 'restart-database-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
|
||||
@@ -32,6 +32,7 @@ class DeployController extends Controller
|
||||
summary: 'List',
|
||||
description: 'List currently running deployments',
|
||||
path: '/deployments',
|
||||
operationId: 'list-deployments',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -79,12 +80,13 @@ class DeployController extends Controller
|
||||
summary: 'Get',
|
||||
description: 'Get deployment by UUID.',
|
||||
path: '/deployments/{uuid}',
|
||||
operationId: 'get-deployment-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Deployments'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment UUID', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
@@ -134,6 +136,7 @@ class DeployController extends Controller
|
||||
summary: 'Deploy',
|
||||
description: 'Deploy by tag or uuid. `Post` request also accepted.',
|
||||
path: '/deploy',
|
||||
operationId: 'deploy-by-tag-or-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -147,7 +150,7 @@ class DeployController extends Controller
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get deployment(s) Uuid\'s',
|
||||
description: 'Get deployment(s) UUID\'s',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
|
||||
@@ -13,6 +13,7 @@ class OtherController extends Controller
|
||||
summary: 'Version',
|
||||
description: 'Get Coolify version.',
|
||||
path: '/version',
|
||||
operationId: 'version',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -43,6 +44,7 @@ class OtherController extends Controller
|
||||
summary: 'Enable API',
|
||||
description: 'Enable API (only with root permissions).',
|
||||
path: '/enable',
|
||||
operationId: 'enable-api',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -94,6 +96,7 @@ class OtherController extends Controller
|
||||
summary: 'Disable API',
|
||||
description: 'Disable API (only with root permissions).',
|
||||
path: '/disable',
|
||||
operationId: 'disable-api',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -158,6 +161,7 @@ class OtherController extends Controller
|
||||
summary: 'Healthcheck',
|
||||
description: 'Healthcheck endpoint.',
|
||||
path: '/healthcheck',
|
||||
operationId: 'healthcheck',
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
|
||||
@@ -11,8 +11,9 @@ class ProjectController extends Controller
|
||||
{
|
||||
#[OA\Get(
|
||||
summary: 'List',
|
||||
description: 'list projects.',
|
||||
description: 'List projects.',
|
||||
path: '/projects',
|
||||
operationId: 'list-projects',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -46,7 +47,7 @@ class ProjectController extends Controller
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$projects = Project::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
||||
$projects = Project::whereTeamId($teamId)->select('id', 'name', 'description', 'uuid')->get();
|
||||
|
||||
return response()->json(serializeApiResponse($projects),
|
||||
);
|
||||
@@ -54,8 +55,9 @@ class ProjectController extends Controller
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Get',
|
||||
description: 'Get project by Uuid.',
|
||||
description: 'Get project by UUID.',
|
||||
path: '/projects/{uuid}',
|
||||
operationId: 'get-project-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -102,6 +104,7 @@ class ProjectController extends Controller
|
||||
summary: 'Environment',
|
||||
description: 'Get environment by name.',
|
||||
path: '/projects/{uuid}/{environment_name}',
|
||||
operationId: 'get-environment-by-name',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -136,12 +139,15 @@ class ProjectController extends Controller
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
if (! $request->uuid) {
|
||||
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||
return response()->json(['message' => 'UUID is required.'], 422);
|
||||
}
|
||||
if (! $request->environment_name) {
|
||||
return response()->json(['message' => 'Environment name is required.'], 422);
|
||||
}
|
||||
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $project) {
|
||||
return response()->json(['message' => 'Project not found.'], 404);
|
||||
}
|
||||
$environment = $project->environments()->whereName($request->environment_name)->first();
|
||||
if (! $environment) {
|
||||
return response()->json(['message' => 'Environment not found.'], 404);
|
||||
@@ -155,6 +161,7 @@ class ProjectController extends Controller
|
||||
summary: 'Create',
|
||||
description: 'Create Project.',
|
||||
path: '/projects',
|
||||
operationId: 'create-project',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -167,7 +174,7 @@ class ProjectController extends Controller
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'uuid' => ['type' => 'string', 'description' => 'The name of the project.'],
|
||||
'name' => ['type' => 'string', 'description' => 'The name of the project.'],
|
||||
'description' => ['type' => 'string', 'description' => 'The description of the project.'],
|
||||
],
|
||||
),
|
||||
@@ -250,6 +257,7 @@ class ProjectController extends Controller
|
||||
summary: 'Update',
|
||||
description: 'Update Project.',
|
||||
path: '/projects/{uuid}',
|
||||
operationId: 'update-project-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -333,7 +341,7 @@ class ProjectController extends Controller
|
||||
}
|
||||
$uuid = $request->uuid;
|
||||
if (! $uuid) {
|
||||
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||
return response()->json(['message' => 'UUID is required.'], 422);
|
||||
}
|
||||
|
||||
$project = Project::whereTeamId($teamId)->whereUuid($uuid)->first();
|
||||
@@ -355,6 +363,7 @@ class ProjectController extends Controller
|
||||
summary: 'Delete',
|
||||
description: 'Delete project by UUID.',
|
||||
path: '/projects/{uuid}',
|
||||
operationId: 'delete-project-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -408,7 +417,7 @@ class ProjectController extends Controller
|
||||
}
|
||||
|
||||
if (! $request->uuid) {
|
||||
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||
return response()->json(['message' => 'UUID is required.'], 422);
|
||||
}
|
||||
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $project) {
|
||||
|
||||
@@ -13,6 +13,7 @@ class ResourcesController extends Controller
|
||||
summary: 'List',
|
||||
description: 'Get all resources.',
|
||||
path: '/resources',
|
||||
operationId: 'list-resources',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
|
||||
@@ -26,6 +26,7 @@ class SecurityController extends Controller
|
||||
summary: 'List',
|
||||
description: 'List all private keys.',
|
||||
path: '/security/keys',
|
||||
operationId: 'list-private-keys',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -68,12 +69,13 @@ class SecurityController extends Controller
|
||||
summary: 'Get',
|
||||
description: 'Get key by UUID.',
|
||||
path: '/security/keys/{uuid}',
|
||||
operationId: 'get-private-key-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Private Keys'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key UUID', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
@@ -124,6 +126,7 @@ class SecurityController extends Controller
|
||||
summary: 'Create',
|
||||
description: 'Create a new private key.',
|
||||
path: '/security/keys',
|
||||
operationId: 'create-private-key',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -217,6 +220,7 @@ class SecurityController extends Controller
|
||||
summary: 'Update',
|
||||
description: 'Update a private key.',
|
||||
path: '/security/keys',
|
||||
operationId: 'update-private-key',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -313,12 +317,13 @@ class SecurityController extends Controller
|
||||
summary: 'Delete',
|
||||
description: 'Delete a private key.',
|
||||
path: '/security/keys/{uuid}',
|
||||
operationId: 'delete-private-key-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Private Keys'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key UUID', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
|
||||
@@ -46,6 +46,7 @@ class ServersController extends Controller
|
||||
summary: 'List',
|
||||
description: 'List all servers.',
|
||||
path: '/servers',
|
||||
operationId: 'list-servers',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -100,12 +101,13 @@ class ServersController extends Controller
|
||||
summary: 'Get',
|
||||
description: 'Get server by UUID.',
|
||||
path: '/servers/{uuid}',
|
||||
operationId: 'get-server-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Servers'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s UUID', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
@@ -177,12 +179,13 @@ class ServersController extends Controller
|
||||
summary: 'Resources',
|
||||
description: 'Get resources by server.',
|
||||
path: '/servers/{uuid}/resources',
|
||||
operationId: 'get-resources-by-server-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Servers'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s UUID', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
@@ -254,12 +257,13 @@ class ServersController extends Controller
|
||||
summary: 'Domains',
|
||||
description: 'Get domains by server.',
|
||||
path: '/servers/{uuid}/domains',
|
||||
operationId: 'get-domains-by-server-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Servers'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s UUID', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
@@ -401,6 +405,7 @@ class ServersController extends Controller
|
||||
summary: 'Create',
|
||||
description: 'Create Server.',
|
||||
path: '/servers',
|
||||
operationId: 'create-server',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -545,6 +550,7 @@ class ServersController extends Controller
|
||||
summary: 'Update',
|
||||
description: 'Update Server.',
|
||||
path: '/servers/{uuid}',
|
||||
operationId: 'update-server-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -655,6 +661,7 @@ class ServersController extends Controller
|
||||
summary: 'Delete',
|
||||
description: 'Delete server by UUID.',
|
||||
path: '/servers/{uuid}',
|
||||
operationId: 'delete-server-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -727,6 +734,7 @@ class ServersController extends Controller
|
||||
summary: 'Validate',
|
||||
description: 'Validate server by UUID.',
|
||||
path: '/servers/{uuid}/validate',
|
||||
operationId: 'validate-server-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
|
||||
@@ -38,6 +38,7 @@ class ServicesController extends Controller
|
||||
summary: 'List',
|
||||
description: 'List all services.',
|
||||
path: '/services',
|
||||
operationId: 'list-services',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -88,6 +89,7 @@ class ServicesController extends Controller
|
||||
summary: 'Create',
|
||||
description: 'Create a one-click service',
|
||||
path: '/services',
|
||||
operationId: 'create-service',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -365,6 +367,7 @@ class ServicesController extends Controller
|
||||
summary: 'Get',
|
||||
description: 'Get service by UUID.',
|
||||
path: '/services/{uuid}',
|
||||
operationId: 'get-service-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -375,7 +378,7 @@ class ServicesController extends Controller
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get a service by Uuid.',
|
||||
description: 'Get a service by UUID.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
@@ -422,6 +425,7 @@ class ServicesController extends Controller
|
||||
summary: 'Delete',
|
||||
description: 'Delete service by UUID.',
|
||||
path: '/services/{uuid}',
|
||||
operationId: 'delete-service-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -432,7 +436,7 @@ class ServicesController extends Controller
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Delete a service by Uuid',
|
||||
description: 'Delete a service by UUID',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
@@ -479,10 +483,521 @@ class ServicesController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'List Envs',
|
||||
description: 'List all envs by service UUID.',
|
||||
path: '/services/{uuid}/envs',
|
||||
operationId: 'list-envs-by-service-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
description: 'UUID of the service.',
|
||||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'All environment variables by service UUID.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function envs(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
|
||||
$envs = $service->environment_variables->map(function ($env) {
|
||||
$env->makeHidden([
|
||||
'application_id',
|
||||
'standalone_clickhouse_id',
|
||||
'standalone_dragonfly_id',
|
||||
'standalone_keydb_id',
|
||||
'standalone_mariadb_id',
|
||||
'standalone_mongodb_id',
|
||||
'standalone_mysql_id',
|
||||
'standalone_postgresql_id',
|
||||
'standalone_redis_id',
|
||||
]);
|
||||
$env = $this->removeSensitiveData($env);
|
||||
|
||||
return $env;
|
||||
});
|
||||
|
||||
return response()->json($envs);
|
||||
}
|
||||
|
||||
#[OA\Patch(
|
||||
summary: 'Update Env',
|
||||
description: 'Update env by service UUID.',
|
||||
path: '/services/{uuid}/envs',
|
||||
operationId: 'update-env-by-service-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
description: 'UUID of the service.',
|
||||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
requestBody: new OA\RequestBody(
|
||||
description: 'Env updated.',
|
||||
required: true,
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
required: ['key', 'value'],
|
||||
properties: [
|
||||
'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'],
|
||||
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
||||
'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
|
||||
'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'],
|
||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 201,
|
||||
description: 'Environment variable updated.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Environment variable updated.'],
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function update_env_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
|
||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'key' => 'string|required',
|
||||
'value' => 'string|nullable',
|
||||
'is_build_time' => 'boolean',
|
||||
'is_literal' => 'boolean',
|
||||
'is_multiline' => 'boolean',
|
||||
'is_shown_once' => 'boolean',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$env = $service->environment_variables()->where('key', $request->key)->first();
|
||||
if (! $env) {
|
||||
return response()->json(['message' => 'Environment variable not found.'], 404);
|
||||
}
|
||||
|
||||
$env->fill($request->all());
|
||||
$env->save();
|
||||
|
||||
return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
|
||||
}
|
||||
|
||||
#[OA\Patch(
|
||||
summary: 'Update Envs (Bulk)',
|
||||
description: 'Update multiple envs by service UUID.',
|
||||
path: '/services/{uuid}/envs/bulk',
|
||||
operationId: 'update-envs-by-service-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
description: 'UUID of the service.',
|
||||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
requestBody: new OA\RequestBody(
|
||||
description: 'Bulk envs updated.',
|
||||
required: true,
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
required: ['data'],
|
||||
properties: [
|
||||
'data' => [
|
||||
'type' => 'array',
|
||||
'items' => new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'],
|
||||
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
||||
'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
|
||||
'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'],
|
||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'],
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 201,
|
||||
description: 'Environment variables updated.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Environment variables updated.'],
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function create_bulk_envs(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
|
||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
|
||||
$bulk_data = $request->get('data');
|
||||
if (! $bulk_data) {
|
||||
return response()->json(['message' => 'Bulk data is required.'], 400);
|
||||
}
|
||||
|
||||
$updatedEnvs = collect();
|
||||
foreach ($bulk_data as $item) {
|
||||
$validator = customApiValidator($item, [
|
||||
'key' => 'string|required',
|
||||
'value' => 'string|nullable',
|
||||
'is_build_time' => 'boolean',
|
||||
'is_literal' => 'boolean',
|
||||
'is_multiline' => 'boolean',
|
||||
'is_shown_once' => 'boolean',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$env = $service->environment_variables()->updateOrCreate(
|
||||
['key' => $item['key']],
|
||||
$item
|
||||
);
|
||||
|
||||
$updatedEnvs->push($this->removeSensitiveData($env));
|
||||
}
|
||||
|
||||
return response()->json($updatedEnvs)->setStatusCode(201);
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
summary: 'Create Env',
|
||||
description: 'Create env by service UUID.',
|
||||
path: '/services/{uuid}/envs',
|
||||
operationId: 'create-env-by-service-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
description: 'UUID of the service.',
|
||||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
description: 'Env created.',
|
||||
content: new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'],
|
||||
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
||||
'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
|
||||
'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'],
|
||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 201,
|
||||
description: 'Environment variable created.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'uuid' => ['type' => 'string', 'example' => 'nc0k04gk8g0cgsk440g0koko'],
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function create_env(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
|
||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'key' => 'string|required',
|
||||
'value' => 'string|nullable',
|
||||
'is_build_time' => 'boolean',
|
||||
'is_literal' => 'boolean',
|
||||
'is_multiline' => 'boolean',
|
||||
'is_shown_once' => 'boolean',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$existingEnv = $service->environment_variables()->where('key', $request->key)->first();
|
||||
if ($existingEnv) {
|
||||
return response()->json([
|
||||
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
||||
], 409);
|
||||
}
|
||||
|
||||
$env = $service->environment_variables()->create($request->all());
|
||||
|
||||
return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
|
||||
}
|
||||
|
||||
#[OA\Delete(
|
||||
summary: 'Delete Env',
|
||||
description: 'Delete env by UUID.',
|
||||
path: '/services/{uuid}/envs/{env_uuid}',
|
||||
operationId: 'delete-env-by-service-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
description: 'UUID of the service.',
|
||||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'env_uuid',
|
||||
in: 'path',
|
||||
description: 'UUID of the environment variable.',
|
||||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Environment variable deleted.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Environment variable deleted.'],
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function delete_env_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
|
||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
|
||||
$env = EnvironmentVariable::where('uuid', $request->env_uuid)
|
||||
->where('service_id', $service->id)
|
||||
->first();
|
||||
|
||||
if (! $env) {
|
||||
return response()->json(['message' => 'Environment variable not found.'], 404);
|
||||
}
|
||||
|
||||
$env->forceDelete();
|
||||
|
||||
return response()->json(['message' => 'Environment variable deleted.']);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Start',
|
||||
description: 'Start service. `Post` request is also accepted.',
|
||||
path: '/services/{uuid}/start',
|
||||
operationId: 'start-service-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -558,6 +1073,7 @@ class ServicesController extends Controller
|
||||
summary: 'Stop',
|
||||
description: 'Stop service. `Post` request is also accepted.',
|
||||
path: '/services/{uuid}/stop',
|
||||
operationId: 'stop-service-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -633,6 +1149,7 @@ class ServicesController extends Controller
|
||||
summary: 'Restart',
|
||||
description: 'Restart service. `Post` request is also accepted.',
|
||||
path: '/services/{uuid}/restart',
|
||||
operationId: 'restart-service-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
|
||||
@@ -32,6 +32,7 @@ class TeamController extends Controller
|
||||
summary: 'List',
|
||||
description: 'Get all teams.',
|
||||
path: '/teams',
|
||||
operationId: 'list-teams',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -79,6 +80,7 @@ class TeamController extends Controller
|
||||
summary: 'Get',
|
||||
description: 'Get team by TeamId.',
|
||||
path: '/teams/{id}',
|
||||
operationId: 'get-team-by-id',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -129,6 +131,7 @@ class TeamController extends Controller
|
||||
summary: 'Members',
|
||||
description: 'Get members by TeamId.',
|
||||
path: '/teams/{id}/members',
|
||||
operationId: 'get-members-by-team-id',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -189,6 +192,7 @@ class TeamController extends Controller
|
||||
summary: 'Authenticated Team',
|
||||
description: 'Get currently authenticated team.',
|
||||
path: '/teams/current',
|
||||
operationId: 'get-current-team',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@@ -225,6 +229,7 @@ class TeamController extends Controller
|
||||
summary: 'Authenticated Team Members',
|
||||
description: 'Get currently authenticated team members.',
|
||||
path: '/teams/current/members',
|
||||
operationId: 'get-current-team-members',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
|
||||
@@ -12,6 +12,7 @@ use App\Models\ApplicationPreview;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\GitlabApp;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
@@ -109,7 +110,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private bool $is_debug_enabled;
|
||||
|
||||
private $build_args;
|
||||
private Collection|string $build_args;
|
||||
|
||||
private $env_args;
|
||||
|
||||
@@ -168,6 +169,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
|
||||
$this->application = Application::find($this->application_deployment_queue->application_id);
|
||||
$this->build_pack = data_get($this->application, 'build_pack');
|
||||
$this->build_args = collect([]);
|
||||
|
||||
$this->application_deployment_queue_id = $application_deployment_queue_id;
|
||||
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
|
||||
@@ -512,7 +514,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
'hidden' => true,
|
||||
'ignore_errors' => true,
|
||||
], [
|
||||
"docker network connect {$networkId} coolify-proxy || true",
|
||||
"docker network connect {$networkId} coolify-proxy >/dev/null 2>&1 || true",
|
||||
'hidden' => true,
|
||||
'ignore_errors' => true,
|
||||
]);
|
||||
@@ -917,10 +919,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
||||
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
||||
}
|
||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,10 +978,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
||||
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
||||
}
|
||||
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1064,15 +1066,55 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->environment_variables = $envs;
|
||||
}
|
||||
|
||||
private function elixir_finetunes()
|
||||
{
|
||||
if ($this->pull_request_id === 0) {
|
||||
$envType = 'environment_variables';
|
||||
} else {
|
||||
$envType = 'environment_variables_preview';
|
||||
}
|
||||
$mix_env = $this->application->{$envType}->where('key', 'MIX_ENV')->first();
|
||||
if ($mix_env) {
|
||||
if ($mix_env->is_build_time === false) {
|
||||
$this->application_deployment_queue->addLogEntry('MIX_ENV environment variable is not set as build time.', type: 'error');
|
||||
$this->application_deployment_queue->addLogEntry('Please set MIX_ENV environment variable to be build time variable if you facing any issues with the deployment.', type: 'error');
|
||||
}
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry('MIX_ENV environment variable not found.', type: 'error');
|
||||
$this->application_deployment_queue->addLogEntry('Please add MIX_ENV environment variable and set it to be build time variable if you facing any issues with the deployment.', type: 'error');
|
||||
}
|
||||
$secret_key_base = $this->application->{$envType}->where('key', 'SECRET_KEY_BASE')->first();
|
||||
if ($secret_key_base) {
|
||||
if ($secret_key_base->is_build_time === false) {
|
||||
$this->application_deployment_queue->addLogEntry('SECRET_KEY_BASE environment variable is not set as build time.', type: 'error');
|
||||
$this->application_deployment_queue->addLogEntry('Please set SECRET_KEY_BASE environment variable to be build time variable if you facing any issues with the deployment.', type: 'error');
|
||||
}
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry('SECRET_KEY_BASE environment variable not found.', type: 'error');
|
||||
$this->application_deployment_queue->addLogEntry('Please add SECRET_KEY_BASE environment variable and set it to be build time variable if you facing any issues with the deployment.', type: 'error');
|
||||
}
|
||||
$database_url = $this->application->{$envType}->where('key', 'DATABASE_URL')->first();
|
||||
if ($database_url) {
|
||||
if ($database_url->is_build_time === false) {
|
||||
$this->application_deployment_queue->addLogEntry('DATABASE_URL environment variable is not set as build time.', type: 'error');
|
||||
$this->application_deployment_queue->addLogEntry('Please set DATABASE_URL environment variable to be build time variable if you facing any issues with the deployment.', type: 'error');
|
||||
}
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry('DATABASE_URL environment variable not found.', type: 'error');
|
||||
$this->application_deployment_queue->addLogEntry('Please add DATABASE_URL environment variable and set it to be build time variable if you facing any issues with the deployment.', type: 'error');
|
||||
}
|
||||
}
|
||||
|
||||
private function laravel_finetunes()
|
||||
{
|
||||
if ($this->pull_request_id === 0) {
|
||||
$nixpacks_php_fallback_path = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
|
||||
$nixpacks_php_root_dir = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
|
||||
$envType = 'environment_variables';
|
||||
} else {
|
||||
$nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
|
||||
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
|
||||
$envType = 'environment_variables_preview';
|
||||
}
|
||||
$nixpacks_php_fallback_path = $this->application->{$envType}->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
|
||||
$nixpacks_php_root_dir = $this->application->{$envType}->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
|
||||
|
||||
if (! $nixpacks_php_fallback_path) {
|
||||
$nixpacks_php_fallback_path = new EnvironmentVariable;
|
||||
$nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
|
||||
@@ -1292,7 +1334,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private function prepare_builder_image()
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
$helperImage = config('coolify.helper_image');
|
||||
$helperImage = "{$helperImage}:{$settings->helper_version}";
|
||||
// Get user home directory
|
||||
$this->serverUserHomeDir = instant_remote_process(['echo $HOME'], $this->server);
|
||||
$this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
|
||||
@@ -1529,6 +1573,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
data_set($parsed, 'variables.NIXPACKS_PHP_FALLBACK_PATH', $variables[0]->value);
|
||||
data_set($parsed, 'variables.NIXPACKS_PHP_ROOT_DIR', $variables[1]->value);
|
||||
}
|
||||
if ($this->nixpacks_type === 'elixir') {
|
||||
$this->elixir_finetunes();
|
||||
}
|
||||
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
|
||||
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
|
||||
if ($this->nixpacks_type === 'rust') {
|
||||
@@ -2002,6 +2049,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
@@ -2021,6 +2072,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
@@ -2063,6 +2118,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
@@ -2082,6 +2141,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
@@ -2110,6 +2173,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
@@ -2129,6 +2196,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||
'hidden' => true,
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Traits\ExecuteRemoteCommand;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ApplicationRestartJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, ExecuteRemoteCommand, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 3600;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public string $applicationDeploymentQueueId;
|
||||
|
||||
public function __construct(string $applicationDeploymentQueueId)
|
||||
{
|
||||
$this->applicationDeploymentQueueId = $applicationDeploymentQueueId;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
ray('Restarting application');
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
@@ -25,12 +26,14 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
if ($response->successful()) {
|
||||
$versions = $response->json();
|
||||
|
||||
$latest_version = data_get($versions, 'coolify.v4.version');
|
||||
$current_version = config('version');
|
||||
|
||||
if (version_compare($latest_version, $current_version, '>')) {
|
||||
// New version available
|
||||
$settings->update(['new_version_available' => true]);
|
||||
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
|
||||
} else {
|
||||
$settings->update(['new_version_available' => false]);
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Server\InstallLogDrain;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Container\ContainerRestarted;
|
||||
use App\Notifications\Container\ContainerStopped;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Sleep;
|
||||
|
||||
class CheckLogDrainContainerJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||
}
|
||||
|
||||
public function uniqueId(): int
|
||||
{
|
||||
return $this->server->id;
|
||||
}
|
||||
|
||||
public function healthcheck()
|
||||
{
|
||||
$status = instant_remote_process(["docker inspect --format='{{json .State.Status}}' coolify-log-drain"], $this->server, false);
|
||||
if (str($status)->contains('running')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
// ray("checking log drain statuses for {$this->server->id}");
|
||||
try {
|
||||
if (! $this->server->isFunctional()) {
|
||||
return;
|
||||
}
|
||||
$containers = instant_remote_process(['docker container ls -q'], $this->server, false);
|
||||
if (! $containers) {
|
||||
return;
|
||||
}
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
|
||||
$foundLogDrainContainer = $containers->filter(function ($value, $key) {
|
||||
return data_get($value, 'Name') === '/coolify-log-drain';
|
||||
})->first();
|
||||
if (! $foundLogDrainContainer || ! $this->healthcheck()) {
|
||||
ray('Log drain container not found or unhealthy. Restarting...');
|
||||
InstallLogDrain::run($this->server);
|
||||
Sleep::for(10)->seconds();
|
||||
if ($this->healthcheck()) {
|
||||
if ($this->server->log_drain_notification_sent) {
|
||||
$this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
|
||||
$this->server->update(['log_drain_notification_sent' => false]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (! $this->server->log_drain_notification_sent) {
|
||||
ray('Log drain container still unhealthy. Sending notification...');
|
||||
// $this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
|
||||
$this->server->update(['log_drain_notification_sent' => true]);
|
||||
}
|
||||
} else {
|
||||
if ($this->server->log_drain_notification_sent) {
|
||||
$this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
|
||||
$this->server->update(['log_drain_notification_sent' => false]);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
if (! isCloud()) {
|
||||
send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: ".$e->getMessage());
|
||||
}
|
||||
ray($e->getMessage());
|
||||
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
app/Jobs/CleanupStaleMultiplexedConnections.php
Normal file
37
app/Jobs/CleanupStaleMultiplexedConnections.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
class CleanupStaleMultiplexedConnections implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
Server::chunk(100, function ($servers) {
|
||||
foreach ($servers as $server) {
|
||||
$this->cleanupStaleConnection($server);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function cleanupStaleConnection(Server $server)
|
||||
{
|
||||
$muxSocket = "/tmp/mux_{$server->id}";
|
||||
$checkCommand = "ssh -O check -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null";
|
||||
$checkProcess = Process::run($checkCommand);
|
||||
|
||||
if ($checkProcess->exitCode() !== 0) {
|
||||
$closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null";
|
||||
Process::run($closeCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Jobs;
|
||||
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Events\BackupCreated;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ScheduledDatabaseBackupExecution;
|
||||
@@ -25,6 +26,7 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Str;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
@@ -398,6 +400,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
||||
|
||||
$commands[] = $backupCommand;
|
||||
ray($commands);
|
||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||
$this->backup_output = trim($this->backup_output);
|
||||
if ($this->backup_output === '') {
|
||||
@@ -476,6 +479,34 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
// private function upload_to_s3(): void
|
||||
// {
|
||||
// try {
|
||||
// if (is_null($this->s3)) {
|
||||
// return;
|
||||
// }
|
||||
// $key = $this->s3->key;
|
||||
// $secret = $this->s3->secret;
|
||||
// // $region = $this->s3->region;
|
||||
// $bucket = $this->s3->bucket;
|
||||
// $endpoint = $this->s3->endpoint;
|
||||
// $this->s3->testConnection(shouldSave: true);
|
||||
// $configName = new Cuid2;
|
||||
|
||||
// $s3_copy_dir = str($this->backup_location)->replace(backup_dir(), '/var/www/html/storage/app/backups/');
|
||||
// $commands[] = "docker exec coolify bash -c 'mc config host add {$configName} {$endpoint} $key $secret'";
|
||||
// $commands[] = "docker exec coolify bash -c 'mc cp $s3_copy_dir {$configName}/{$bucket}{$this->backup_dir}/'";
|
||||
// instant_remote_process($commands, $this->server);
|
||||
// $this->add_to_backup_output('Uploaded to S3.');
|
||||
// } catch (\Throwable $e) {
|
||||
// $this->add_to_backup_output($e->getMessage());
|
||||
// throw $e;
|
||||
// } finally {
|
||||
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc config remove {$configName}'";
|
||||
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc alias rm {$configName}'";
|
||||
// instant_remote_process($removeConfigCommands, $this->server, false);
|
||||
// }
|
||||
// }
|
||||
private function upload_to_s3(): void
|
||||
{
|
||||
try {
|
||||
@@ -493,12 +524,15 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
} else {
|
||||
$network = $this->database->destination->network;
|
||||
}
|
||||
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
|
||||
|
||||
$this->ensureHelperImageAvailable();
|
||||
|
||||
$fullImageName = $this->getFullImageName();
|
||||
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
|
||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
|
||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
||||
instant_remote_process($commands, $this->server);
|
||||
$this->add_to_backup_output('Uploaded to S3.');
|
||||
ray('Uploaded to S3. '.$this->backup_location.' to s3://'.$bucket.$this->backup_dir);
|
||||
} catch (\Throwable $e) {
|
||||
$this->add_to_backup_output($e->getMessage());
|
||||
throw $e;
|
||||
@@ -507,4 +541,42 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
instant_remote_process([$command], $this->server);
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureHelperImageAvailable(): void
|
||||
{
|
||||
$fullImageName = $this->getFullImageName();
|
||||
|
||||
$imageExists = $this->checkImageExists($fullImageName);
|
||||
|
||||
if (! $imageExists) {
|
||||
$this->pullHelperImage($fullImageName);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkImageExists(string $fullImageName): bool
|
||||
{
|
||||
$result = instant_remote_process(["docker image inspect {$fullImageName} >/dev/null 2>&1 && echo 'exists' || echo 'not exists'"], $this->server, false);
|
||||
|
||||
return trim($result) === 'exists';
|
||||
}
|
||||
|
||||
private function pullHelperImage(string $fullImageName): void
|
||||
{
|
||||
try {
|
||||
instant_remote_process(["docker pull {$fullImageName}"], $this->server);
|
||||
} catch (\Exception $e) {
|
||||
$errorMessage = 'Failed to pull helper image: '.$e->getMessage();
|
||||
$this->add_to_backup_output($errorMessage);
|
||||
throw new \RuntimeException($errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private function getFullImageName(): string
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
$helperImage = config('coolify.helper_image');
|
||||
$latestVersion = $settings->helper_version;
|
||||
|
||||
return "{$helperImage}:{$latestVersion}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Server\UpdateCoolify;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InstanceAutoUpdateJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 600;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
UpdateCoolify::run();
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if (isDev() || isCloud()) {
|
||||
return;
|
||||
}
|
||||
$settings = InstanceSettings::get();
|
||||
$server = Server::findOrFail(0);
|
||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
if ($response->successful()) {
|
||||
$versions = $response->json();
|
||||
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
|
||||
}
|
||||
$latest_version = get_latest_version_of_coolify();
|
||||
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
|
||||
|
||||
$current_version = config('version');
|
||||
if (! $settings->is_auto_update_enabled) {
|
||||
return;
|
||||
}
|
||||
if ($latest_version === $current_version) {
|
||||
return;
|
||||
}
|
||||
if (version_compare($latest_version, $current_version, '<')) {
|
||||
return;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
@@ -10,6 +11,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
@@ -32,10 +34,20 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$helperImage = config('coolify.helper_image');
|
||||
ray("Pulling {$helperImage}");
|
||||
instant_remote_process(["docker pull -q {$helperImage}"], $this->server, false);
|
||||
ray('PullHelperImageJob done');
|
||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
if ($response->successful()) {
|
||||
$versions = $response->json();
|
||||
$settings = InstanceSettings::get();
|
||||
$latest_version = data_get($versions, 'coolify.helper.version');
|
||||
$current_version = $settings->helper_version;
|
||||
if (version_compare($latest_version, $current_version, '>')) {
|
||||
// New version available
|
||||
// $helperImage = config('coolify.helper_image');
|
||||
// instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
|
||||
$settings->update(['helper_version' => $latest_version]);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('PullHelperImageJob failed with: '.$e->getMessage());
|
||||
ray($e->getMessage());
|
||||
|
||||
@@ -26,6 +26,8 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
public $tries = 3;
|
||||
|
||||
public $timeout = 60;
|
||||
|
||||
public $containers;
|
||||
|
||||
public $applications;
|
||||
@@ -43,15 +45,15 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
|
||||
// public function middleware(): array
|
||||
// {
|
||||
// return [(new WithoutOverlapping($this->server->uuid))];
|
||||
// }
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->id))];
|
||||
}
|
||||
|
||||
// public function uniqueId(): int
|
||||
// {
|
||||
// return $this->server->uuid;
|
||||
// }
|
||||
public function uniqueId(): int
|
||||
{
|
||||
return $this->server->id;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
@@ -124,6 +126,9 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private function checkLogDrainContainer()
|
||||
{
|
||||
if (! $this->server->isLogDrainEnabled()) {
|
||||
return;
|
||||
}
|
||||
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
||||
return data_get($value, 'Name') === '/coolify-log-drain';
|
||||
})->first();
|
||||
|
||||
@@ -73,6 +73,8 @@ class Index extends Component
|
||||
}
|
||||
$this->privateKeyName = generate_random_name();
|
||||
$this->remoteServerName = generate_random_name();
|
||||
$this->remoteServerPort = $this->remoteServerPort;
|
||||
$this->remoteServerUser = $this->remoteServerUser;
|
||||
if (isDev()) {
|
||||
$this->privateKey = '-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
@@ -154,6 +156,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
$this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||
if ($this->servers->count() > 0) {
|
||||
$this->selectedExistingServer = $this->servers->first()->id;
|
||||
$this->updateServerDetails();
|
||||
$this->currentState = 'select-existing-server';
|
||||
|
||||
return;
|
||||
@@ -173,9 +176,18 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
}
|
||||
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||
$this->updateServerDetails();
|
||||
$this->currentState = 'validate-server';
|
||||
}
|
||||
|
||||
private function updateServerDetails()
|
||||
{
|
||||
if ($this->createdServer) {
|
||||
$this->remoteServerPort = $this->createdServer->port;
|
||||
$this->remoteServerUser = $this->createdServer->user;
|
||||
}
|
||||
}
|
||||
|
||||
public function getProxyType()
|
||||
{
|
||||
// Set Default Proxy Type
|
||||
@@ -235,11 +247,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
public function saveServer()
|
||||
{
|
||||
$this->validate([
|
||||
'remoteServerName' => 'required',
|
||||
'remoteServerHost' => 'required',
|
||||
'remoteServerName' => 'required|string',
|
||||
'remoteServerHost' => 'required|string',
|
||||
'remoteServerPort' => 'required|integer',
|
||||
'remoteServerUser' => 'required',
|
||||
'remoteServerUser' => 'required|string',
|
||||
]);
|
||||
|
||||
$this->privateKey = formatPrivateKey($this->privateKey);
|
||||
$foundServer = Server::whereIp($this->remoteServerHost)->first();
|
||||
if ($foundServer) {
|
||||
@@ -269,7 +282,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
public function validateServer()
|
||||
{
|
||||
try {
|
||||
config()->set('coolify.mux_enabled', false);
|
||||
config()->set('constants.ssh.mux_enabled', false);
|
||||
|
||||
// EC2 does not have `uptime` command, lol
|
||||
instant_remote_process(['ls /'], $this->createdServer, true);
|
||||
@@ -277,9 +290,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
$this->createdServer->settings()->update([
|
||||
'is_reachable' => true,
|
||||
]);
|
||||
$this->serverReachable = true;
|
||||
} catch (\Throwable $e) {
|
||||
$this->serverReachable = false;
|
||||
$this->createdServer->delete();
|
||||
$this->createdServer->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
|
||||
return handleError(error: $e, livewire: $this);
|
||||
}
|
||||
@@ -296,6 +312,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
]);
|
||||
$this->getProxyType();
|
||||
} catch (\Throwable $e) {
|
||||
$this->createdServer->settings()->update([
|
||||
'is_usable' => false,
|
||||
]);
|
||||
|
||||
return handleError(error: $e, livewire: $this);
|
||||
}
|
||||
}
|
||||
@@ -349,6 +369,21 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
);
|
||||
}
|
||||
|
||||
public function saveAndValidateServer()
|
||||
{
|
||||
$this->validate([
|
||||
'remoteServerPort' => 'required|integer|min:1|max:65535',
|
||||
'remoteServerUser' => 'required|string',
|
||||
]);
|
||||
|
||||
$this->createdServer->update([
|
||||
'port' => $this->remoteServerPort,
|
||||
'user' => $this->remoteServerUser,
|
||||
'timezone' => 'UTC',
|
||||
]);
|
||||
$this->validateServer();
|
||||
}
|
||||
|
||||
private function createNewPrivateKey()
|
||||
{
|
||||
$this->privateKeyName = generate_random_name();
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\CommandCenter;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
public $servers = [];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->servers = Server::isReachable()->get();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.command-center.index');
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application\Deployment;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
@@ -69,6 +70,20 @@ class Show extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function getLogLinesProperty()
|
||||
{
|
||||
return decode_remote_command_output($this->application_deployment_queue)->map(function ($logLine) {
|
||||
$logLine['line'] = e($logLine['line']);
|
||||
$logLine['line'] = preg_replace(
|
||||
'/(https?:\/\/[^\s]+)/',
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer" class="underline text-neutral-400">$1</a>',
|
||||
$logLine['line'],
|
||||
);
|
||||
|
||||
return $logLine;
|
||||
});
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.application.deployment.show');
|
||||
|
||||
@@ -99,6 +99,16 @@ class PublicGitRepository extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedDockerComposeLocation()
|
||||
{
|
||||
if ($this->docker_compose_location) {
|
||||
$this->docker_compose_location = rtrim($this->docker_compose_location, '/');
|
||||
if (! str($this->docker_compose_location)->startsWith('/')) {
|
||||
$this->docker_compose_location = '/'.$this->docker_compose_location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedBuildPack()
|
||||
{
|
||||
if ($this->build_pack === 'nixpacks') {
|
||||
|
||||
@@ -43,6 +43,7 @@ class EditCompose extends Component
|
||||
{
|
||||
$this->dispatch('info', 'Saving new docker compose...');
|
||||
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
|
||||
$this->dispatch('refreshStorages');
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
|
||||
@@ -20,6 +20,8 @@ class Navbar extends Component
|
||||
|
||||
public $isDeploymentProgress = false;
|
||||
|
||||
public $title = 'Configuration';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
|
||||
|
||||
@@ -24,6 +24,7 @@ class All extends Component
|
||||
|
||||
protected $listeners = [
|
||||
'saveKey' => 'submit',
|
||||
'refreshEnvs',
|
||||
'environmentVariableDeleted' => 'refreshEnvs',
|
||||
];
|
||||
|
||||
@@ -61,7 +62,7 @@ class All extends Component
|
||||
$sortBy = data_get($this->resource, 'settings.is_env_sorting_enabled') ? 'key' : 'order';
|
||||
|
||||
$sortFunction = function ($variables) use ($sortBy) {
|
||||
if (!$variables) {
|
||||
if (! $variables) {
|
||||
return $variables;
|
||||
}
|
||||
if ($sortBy === 'key') {
|
||||
|
||||
@@ -136,4 +136,4 @@ class Show extends Component
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,16 @@
|
||||
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Actions\Server\RunCommand;
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
||||
class ExecuteContainerCommand extends Component
|
||||
{
|
||||
public string $command;
|
||||
|
||||
public string $container;
|
||||
public $container;
|
||||
|
||||
public Collection $containers;
|
||||
|
||||
@@ -23,8 +21,6 @@ class ExecuteContainerCommand extends Component
|
||||
|
||||
public string $type;
|
||||
|
||||
public string $workDir = '';
|
||||
|
||||
public Server $server;
|
||||
|
||||
public Collection $servers;
|
||||
@@ -33,11 +29,13 @@ class ExecuteContainerCommand extends Component
|
||||
'server' => 'required',
|
||||
'container' => 'required',
|
||||
'command' => 'required',
|
||||
'workDir' => 'nullable',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (! auth()->user()->isAdmin()) {
|
||||
abort(403);
|
||||
}
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->containers = collect();
|
||||
$this->servers = collect();
|
||||
@@ -62,24 +60,13 @@ class ExecuteContainerCommand extends Component
|
||||
if ($this->resource->destination->server->isFunctional()) {
|
||||
$this->servers = $this->servers->push($this->resource->destination->server);
|
||||
}
|
||||
$this->container = $this->resource->uuid;
|
||||
$this->containers->push($this->container);
|
||||
} elseif (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->type = 'service';
|
||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||
$this->resource->applications()->get()->each(function ($application) {
|
||||
$this->containers->push(data_get($application, 'name').'-'.data_get($this->resource, 'uuid'));
|
||||
});
|
||||
$this->resource->databases()->get()->each(function ($database) {
|
||||
$this->containers->push(data_get($database, 'name').'-'.data_get($this->resource, 'uuid'));
|
||||
});
|
||||
if ($this->resource->server->isFunctional()) {
|
||||
$this->servers = $this->servers->push($this->resource->server);
|
||||
}
|
||||
}
|
||||
if ($this->containers->count() > 0) {
|
||||
$this->container = $this->containers->first();
|
||||
}
|
||||
}
|
||||
|
||||
public function loadContainers()
|
||||
@@ -102,44 +89,65 @@ class ExecuteContainerCommand extends Component
|
||||
];
|
||||
$this->containers = $this->containers->push($payload);
|
||||
}
|
||||
} elseif (data_get($this->parameters, 'database_uuid')) {
|
||||
if ($this->resource->isRunning()) {
|
||||
$this->containers = $this->containers->push([
|
||||
'server' => $server,
|
||||
'container' => [
|
||||
'Names' => $this->resource->uuid,
|
||||
],
|
||||
]);
|
||||
}
|
||||
} elseif (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->resource->applications()->get()->each(function ($application) {
|
||||
ray($application);
|
||||
if ($application->isRunning()) {
|
||||
$this->containers->push([
|
||||
'server' => $this->resource->server,
|
||||
'container' => [
|
||||
'Names' => data_get($application, 'name').'-'.data_get($this->resource, 'uuid'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
});
|
||||
$this->resource->databases()->get()->each(function ($database) {
|
||||
if ($database->isRunning()) {
|
||||
$this->containers->push([
|
||||
'server' => $this->resource->server,
|
||||
'container' => [
|
||||
'Names' => data_get($database, 'name').'-'.data_get($this->resource, 'uuid'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
if ($this->containers->count() > 0) {
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$this->container = data_get($this->containers->first(), 'container.Names');
|
||||
} elseif (data_get($this->parameters, 'database_uuid')) {
|
||||
$this->container = $this->containers->first();
|
||||
} elseif (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->container = $this->containers->first();
|
||||
}
|
||||
$this->container = $this->containers->first();
|
||||
}
|
||||
}
|
||||
|
||||
public function runCommand()
|
||||
#[On('connectToContainer')]
|
||||
public function connectToContainer()
|
||||
{
|
||||
try {
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$container = $this->containers->where('container.Names', $this->container)->first();
|
||||
$container_name = data_get($container, 'container.Names');
|
||||
if (is_null($container)) {
|
||||
throw new \RuntimeException('Container not found.');
|
||||
}
|
||||
$server = data_get($container, 'server');
|
||||
} else {
|
||||
$container_name = $this->container;
|
||||
$server = $this->servers->first();
|
||||
$container_name = data_get($this->container, 'container.Names');
|
||||
if (is_null($container_name)) {
|
||||
throw new \RuntimeException('Container not found.');
|
||||
}
|
||||
$server = data_get($this->container, 'server');
|
||||
|
||||
if ($server->isForceDisabled()) {
|
||||
throw new \RuntimeException('Server is disabled.');
|
||||
}
|
||||
$cmd = "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; ".str_replace("'", "'\''", $this->command)."'";
|
||||
if (! empty($this->workDir)) {
|
||||
$exec = "docker exec -w {$this->workDir} {$container_name} {$cmd}";
|
||||
} else {
|
||||
$exec = "docker exec {$container_name} {$cmd}";
|
||||
}
|
||||
$activity = RunCommand::run(server: $server, command: $exec);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
|
||||
$this->dispatch('send-terminal-command',
|
||||
true,
|
||||
$container_name,
|
||||
$server->uuid,
|
||||
);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
43
app/Livewire/Project/Shared/Terminal.php
Normal file
43
app/Livewire/Project/Shared/Terminal.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
||||
class Terminal extends Component
|
||||
{
|
||||
#[On('send-terminal-command')]
|
||||
public function sendTerminalCommand($isContainer, $identifier, $serverUuid)
|
||||
{
|
||||
|
||||
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
|
||||
|
||||
if ($isContainer) {
|
||||
$status = getContainerStatus($server, $identifier);
|
||||
if ($status !== 'running') {
|
||||
return;
|
||||
}
|
||||
$command = generateSshCommand($server, "docker exec -it {$identifier} sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
|
||||
} else {
|
||||
$command = generateSshCommand($server, "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
|
||||
}
|
||||
|
||||
// ssh command is sent back to frontend then to websocket
|
||||
// this is done because the websocket connection is not available here
|
||||
// a better solution would be to remove websocket on NodeJS and work with something like
|
||||
// 1. Laravel Pusher/Echo connection (not possible without a sdk)
|
||||
// 2. Ratchet / Revolt / ReactPHP / Event Loop (possible but hard to implement and huge dependencies)
|
||||
// 3. Just found out about this https://github.com/sirn-se/websocket-php, perhaps it can be used
|
||||
// 4. Follow-up discussions here:
|
||||
// - https://github.com/coollabsio/coolify/issues/2298
|
||||
// - https://github.com/coollabsio/coolify/discussions/3362
|
||||
$this->dispatch('send-back-command', $command);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.terminal');
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Actions\Server\RunCommand as ServerRunCommand;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class RunCommand extends Component
|
||||
{
|
||||
public string $command;
|
||||
|
||||
public $server;
|
||||
|
||||
public $servers = [];
|
||||
|
||||
protected $rules = [
|
||||
'server' => 'required',
|
||||
'command' => 'required',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
'server' => 'server',
|
||||
'command' => 'command',
|
||||
];
|
||||
|
||||
public function mount($servers)
|
||||
{
|
||||
$this->servers = $servers;
|
||||
$this->server = $servers[0]->uuid;
|
||||
}
|
||||
|
||||
public function runCommand()
|
||||
{
|
||||
$this->validate();
|
||||
try {
|
||||
$activity = ServerRunCommand::run(server: Server::where('uuid', $this->server)->first(), command: $this->command);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,11 @@ class Form extends Component
|
||||
|
||||
public $timezones;
|
||||
|
||||
protected $listeners = ['serverInstalled', 'revalidate' => '$refresh'];
|
||||
protected $listeners = [
|
||||
'serverInstalled',
|
||||
'refreshServerShow' => 'serverInstalled',
|
||||
'revalidate' => '$refresh',
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
'server.name' => 'required',
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Livewire\Server\New;
|
||||
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class ByIp extends Component
|
||||
@@ -40,7 +40,7 @@ class ByIp extends Component
|
||||
|
||||
public bool $is_build_server = false;
|
||||
|
||||
public $swarm_managers = [];
|
||||
public Collection $swarm_managers;
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
@@ -102,11 +102,6 @@ class ByIp extends Component
|
||||
'port' => $this->port,
|
||||
'team_id' => currentTeam()->id,
|
||||
'private_key_id' => $this->private_key_id,
|
||||
'proxy' => [
|
||||
// set default proxy type to traefik v2
|
||||
'type' => ProxyTypes::TRAEFIK->value,
|
||||
'status' => ProxyStatus::EXITED->value,
|
||||
],
|
||||
];
|
||||
if ($this->is_swarm_worker) {
|
||||
$payload['swarm_cluster'] = $this->selected_swarm_cluster;
|
||||
@@ -115,6 +110,9 @@ class ByIp extends Component
|
||||
data_forget($payload, 'proxy');
|
||||
}
|
||||
$server = Server::create($payload);
|
||||
$server->proxy->set('status', 'exited');
|
||||
$server->proxy->set('type', ProxyTypes::TRAEFIK->value);
|
||||
$server->save();
|
||||
if ($this->is_build_server) {
|
||||
$this->is_swarm_manager = false;
|
||||
$this->is_swarm_worker = false;
|
||||
|
||||
@@ -14,7 +14,7 @@ class Show extends Component
|
||||
|
||||
public $parameters = [];
|
||||
|
||||
protected $listeners = ['refreshServerShow' => '$refresh'];
|
||||
protected $listeners = ['refreshServerShow'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@@ -29,6 +29,12 @@ class Show extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function refreshServerShow()
|
||||
{
|
||||
$this->server->refresh();
|
||||
$this->dispatch('$refresh');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->dispatch('serverRefresh', false);
|
||||
|
||||
76
app/Livewire/Terminal/Index.php
Normal file
76
app/Livewire/Terminal/Index.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Terminal;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
public $selected_uuid = 'default';
|
||||
|
||||
public $servers = [];
|
||||
|
||||
public $containers = [];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (! auth()->user()->isAdmin()) {
|
||||
abort(403);
|
||||
}
|
||||
$this->servers = Server::isReachable()->get();
|
||||
$this->containers = $this->getAllActiveContainers();
|
||||
}
|
||||
|
||||
private function getAllActiveContainers()
|
||||
{
|
||||
return collect($this->servers)->flatMap(function ($server) {
|
||||
if (! $server->isFunctional()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $server->loadAllContainers()->map(function ($container) use ($server) {
|
||||
$state = data_get_str($container, 'State')->lower();
|
||||
if ($state->contains('running')) {
|
||||
return [
|
||||
'name' => data_get($container, 'Names'),
|
||||
'connection_name' => data_get($container, 'Names'),
|
||||
'uuid' => data_get($container, 'Names'),
|
||||
'status' => data_get_str($container, 'State')->lower(),
|
||||
'server' => $server,
|
||||
'server_uuid' => $server->uuid,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
})->filter();
|
||||
});
|
||||
}
|
||||
|
||||
public function updatedSelectedUuid()
|
||||
{
|
||||
$this->connectToContainer();
|
||||
}
|
||||
|
||||
#[On('connectToContainer')]
|
||||
public function connectToContainer()
|
||||
{
|
||||
if ($this->selected_uuid === 'default') {
|
||||
$this->dispatch('error', 'Please select a server or a container.');
|
||||
|
||||
return;
|
||||
}
|
||||
$container = collect($this->containers)->firstWhere('uuid', $this->selected_uuid);
|
||||
$this->dispatch('send-terminal-command',
|
||||
isset($container),
|
||||
$container['connection_name'] ?? $this->selected_uuid,
|
||||
$container['server_uuid'] ?? $this->selected_uuid
|
||||
);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.terminal.index');
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ namespace App\Livewire;
|
||||
|
||||
use App\Actions\Server\UpdateCoolify;
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Livewire\Component;
|
||||
|
||||
class Upgrade extends Component
|
||||
@@ -22,13 +21,8 @@ class Upgrade extends Component
|
||||
public function checkUpdate()
|
||||
{
|
||||
try {
|
||||
$settings = InstanceSettings::get();
|
||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
if ($response->successful()) {
|
||||
$versions = $response->json();
|
||||
$this->latestVersion = data_get($versions, 'coolify.v4.version');
|
||||
}
|
||||
$this->isUpgradeAvailable = $settings->new_version_available;
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->isUpgradeAvailable = data_get(InstanceSettings::get(), 'new_version_available', false);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -11,6 +11,7 @@ use OpenApi\Attributes as OA;
|
||||
'id' => ['type' => 'integer'],
|
||||
'uuid' => ['type' => 'string'],
|
||||
'name' => ['type' => 'string'],
|
||||
'description' => ['type' => 'string'],
|
||||
'environments' => new OA\Property(
|
||||
property: 'environments',
|
||||
type: 'array',
|
||||
|
||||
@@ -22,7 +22,8 @@ class ScheduledDatabaseBackup extends BaseModel
|
||||
|
||||
public function executions(): HasMany
|
||||
{
|
||||
return $this->hasMany(ScheduledDatabaseBackupExecution::class);
|
||||
// Last execution first
|
||||
return $this->hasMany(ScheduledDatabaseBackupExecution::class)->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
public function s3()
|
||||
|
||||
@@ -28,6 +28,7 @@ class ScheduledTask extends BaseModel
|
||||
|
||||
public function executions(): HasMany
|
||||
{
|
||||
// Last execution first
|
||||
return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,16 @@ class Server extends BaseModel
|
||||
'proxy',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'ip',
|
||||
'port',
|
||||
'user',
|
||||
'description',
|
||||
'private_key_id',
|
||||
'team_id',
|
||||
];
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public static function isReachable()
|
||||
@@ -295,6 +305,13 @@ respond 404
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
],
|
||||
'coolify-terminal-ws' => [
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify-terminal',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/terminal/ws`)",
|
||||
],
|
||||
],
|
||||
'services' => [
|
||||
'coolify' => [
|
||||
@@ -315,6 +332,15 @@ respond 404
|
||||
],
|
||||
],
|
||||
],
|
||||
'coolify-terminal' => [
|
||||
'loadBalancer' => [
|
||||
'servers' => [
|
||||
0 => [
|
||||
'url' => 'http://coolify-realtime:6002',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -344,6 +370,16 @@ respond 404
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-terminal-wss'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify-terminal',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/terminal/ws`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
}
|
||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||
$yaml =
|
||||
@@ -377,6 +413,9 @@ $schema://$host {
|
||||
handle /app/* {
|
||||
reverse_proxy coolify-realtime:6001
|
||||
}
|
||||
handle /terminal/ws/* {
|
||||
reverse_proxy coolify-realtime:6002
|
||||
}
|
||||
reverse_proxy coolify:80
|
||||
}";
|
||||
$base64 = base64_encode($caddy_file);
|
||||
@@ -736,6 +775,18 @@ $schema://$host {
|
||||
}
|
||||
}
|
||||
|
||||
public function loadAllContainers(): Collection
|
||||
{
|
||||
if ($this->isFunctional()) {
|
||||
$containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
|
||||
return collect($containers);
|
||||
}
|
||||
|
||||
return collect([]);
|
||||
}
|
||||
|
||||
public function loadUnmanagedContainers(): Collection
|
||||
{
|
||||
if ($this->isFunctional()) {
|
||||
@@ -880,7 +931,7 @@ $schema://$host {
|
||||
|
||||
public function muxFilename()
|
||||
{
|
||||
return "{$this->ip}_{$this->port}_{$this->user}";
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function team()
|
||||
@@ -957,7 +1008,7 @@ $schema://$host {
|
||||
|
||||
public function validateConnection()
|
||||
{
|
||||
config()->set('coolify.mux_enabled', false);
|
||||
config()->set('constants.ssh.mux_enabled', false);
|
||||
|
||||
$server = Server::find($this->id);
|
||||
if (! $server) {
|
||||
|
||||
@@ -667,7 +667,7 @@ class Service extends BaseModel
|
||||
}
|
||||
$data = $data->merge([
|
||||
'Root User' => [
|
||||
'key' => 'N/A',
|
||||
'key' => 'GITLAB_ROOT_USER',
|
||||
'value' => 'root',
|
||||
'rules' => 'required',
|
||||
'isPassword' => true,
|
||||
@@ -1016,10 +1016,20 @@ class Service extends BaseModel
|
||||
$commands[] = 'rm -f .env || true';
|
||||
|
||||
$envs_from_coolify = $this->environment_variables()->get();
|
||||
foreach ($envs_from_coolify as $env) {
|
||||
$sorted = $envs_from_coolify->sortBy(function ($env) {
|
||||
if (str($env->key)->startsWith('SERVICE_')) {
|
||||
return 1;
|
||||
}
|
||||
if (str($env->value)->startsWith('$SERVICE_') || str($env->value)->startsWith('${SERVICE_')) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 3;
|
||||
});
|
||||
foreach ($sorted as $env) {
|
||||
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
|
||||
}
|
||||
if ($envs_from_coolify->count() === 0) {
|
||||
if ($sorted->count() === 0) {
|
||||
$commands[] = 'touch .env';
|
||||
}
|
||||
instant_remote_process($commands, $this->server);
|
||||
|
||||
@@ -32,6 +32,16 @@ class ServiceApplication extends BaseModel
|
||||
return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
|
||||
}
|
||||
|
||||
public function isRunning()
|
||||
{
|
||||
return str($this->status)->contains('running');
|
||||
}
|
||||
|
||||
public function isExited()
|
||||
{
|
||||
return str($this->status)->contains('exited');
|
||||
}
|
||||
|
||||
public function isLogDrainEnabled()
|
||||
{
|
||||
return data_get($this, 'is_log_drain_enabled', false);
|
||||
|
||||
@@ -25,6 +25,16 @@ class ServiceDatabase extends BaseModel
|
||||
remote_process(["docker restart {$container_id}"], $this->service->server);
|
||||
}
|
||||
|
||||
public function isRunning()
|
||||
{
|
||||
return str($this->status)->contains('running');
|
||||
}
|
||||
|
||||
public function isExited()
|
||||
{
|
||||
return str($this->status)->contains('exited');
|
||||
}
|
||||
|
||||
public function isLogDrainEnabled()
|
||||
{
|
||||
return data_get($this, 'is_log_drain_enabled', false);
|
||||
|
||||
@@ -75,6 +75,11 @@ class StandaloneClickhouse extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function isRunning()
|
||||
{
|
||||
return (bool) str($this->status)->contains('running');
|
||||
}
|
||||
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
|
||||
@@ -75,6 +75,11 @@ class StandaloneDragonfly extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function isRunning()
|
||||
{
|
||||
return (bool) str($this->status)->contains('running');
|
||||
}
|
||||
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
|
||||
@@ -75,6 +75,11 @@ class StandaloneKeydb extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function isRunning()
|
||||
{
|
||||
return (bool) str($this->status)->contains('running');
|
||||
}
|
||||
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
@@ -209,7 +214,7 @@ class StandaloneKeydb extends BaseModel
|
||||
protected function internalDbUrl(): Attribute
|
||||
{
|
||||
return new Attribute(
|
||||
get: fn () => "redis://{$this->keydb_password}@{$this->uuid}:6379/0",
|
||||
get: fn () => "redis://:{$this->keydb_password}@{$this->uuid}:6379/0",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -218,7 +223,7 @@ class StandaloneKeydb extends BaseModel
|
||||
return new Attribute(
|
||||
get: function () {
|
||||
if ($this->is_public && $this->public_port) {
|
||||
return "redis://{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||
return "redis://:{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -75,6 +75,11 @@ class StandaloneMariadb extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function isRunning()
|
||||
{
|
||||
return (bool) str($this->status)->contains('running');
|
||||
}
|
||||
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
|
||||
@@ -79,6 +79,11 @@ class StandaloneMongodb extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function isRunning()
|
||||
{
|
||||
return (bool) str($this->status)->contains('running');
|
||||
}
|
||||
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
|
||||
@@ -76,6 +76,11 @@ class StandaloneMysql extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function isRunning()
|
||||
{
|
||||
return (bool) str($this->status)->contains('running');
|
||||
}
|
||||
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
|
||||
@@ -102,6 +102,11 @@ class StandalonePostgresql extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function isRunning()
|
||||
{
|
||||
return (bool) str($this->status)->contains('running');
|
||||
}
|
||||
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
|
||||
@@ -71,6 +71,11 @@ class StandaloneRedis extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function isRunning()
|
||||
{
|
||||
return (bool) str($this->status)->contains('running');
|
||||
}
|
||||
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
|
||||
@@ -46,6 +46,7 @@ const SUPPORTED_OS = [
|
||||
'centos fedora rhel ol rocky amzn almalinux',
|
||||
'sles opensuse-leap opensuse-tumbleweed',
|
||||
'arch',
|
||||
'alpine',
|
||||
];
|
||||
|
||||
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];
|
||||
|
||||
@@ -40,6 +40,20 @@ function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pul
|
||||
return $containers;
|
||||
}
|
||||
|
||||
function getCurrentServiceContainerStatus(Server $server, int $id): Collection
|
||||
{
|
||||
$containers = collect([]);
|
||||
if (! $server->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.serviceId={$id}' --format '{{json .}}' "], $server);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containers = $containers->filter();
|
||||
|
||||
return $containers;
|
||||
}
|
||||
|
||||
return $containers;
|
||||
}
|
||||
|
||||
function format_docker_command_output_to_json($rawOutput): Collection
|
||||
{
|
||||
$outputLines = explode(PHP_EOL, $rawOutput);
|
||||
@@ -215,12 +229,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
||||
}
|
||||
if (is_null($MINIO_BROWSER_REDIRECT_URL?->value)) {
|
||||
$MINIO_BROWSER_REDIRECT_URL?->update([
|
||||
'value' => generateFqdn($server, 'console-'.$uuid),
|
||||
'value' => generateFqdn($server, 'console-'.$uuid, true),
|
||||
]);
|
||||
}
|
||||
if (is_null($MINIO_SERVER_URL?->value)) {
|
||||
$MINIO_SERVER_URL?->update([
|
||||
'value' => generateFqdn($server, 'minio-'.$uuid),
|
||||
'value' => generateFqdn($server, 'minio-'.$uuid, true),
|
||||
]);
|
||||
}
|
||||
$payload = collect([
|
||||
|
||||
@@ -95,8 +95,24 @@ function generateScpCommand(Server $server, string $source, string $dest)
|
||||
$timeout = config('constants.ssh.command_timeout');
|
||||
$connectionTimeout = config('constants.ssh.connection_timeout');
|
||||
$serverInterval = config('constants.ssh.server_interval');
|
||||
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||
|
||||
$scp_command = "timeout $timeout scp ";
|
||||
$muxEnabled = config('constants.ssh.mux_enabled', true) && config('coolify.is_windows_docker_desktop') == false;
|
||||
// ray('SSH Multiplexing Enabled:', $muxEnabled)->blue();
|
||||
|
||||
if ($muxEnabled) {
|
||||
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
|
||||
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||
ensureMultiplexedConnection($server);
|
||||
// ray('Using SSH Multiplexing')->green();
|
||||
} else {
|
||||
// ray('Not using SSH Multiplexing')->red();
|
||||
}
|
||||
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
$scp_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
|
||||
}
|
||||
$scp_command .= "-i {$privateKeyLocation} "
|
||||
.'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
||||
.'-o PasswordAuthentication=no '
|
||||
@@ -145,9 +161,18 @@ function generateSshCommand(Server $server, string $command)
|
||||
|
||||
$ssh_command = "timeout $timeout ssh ";
|
||||
|
||||
if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) {
|
||||
$ssh_command .= "-o ControlMaster=auto -o ControlPersist={$muxPersistTime} -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ";
|
||||
$muxEnabled = config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false;
|
||||
// ray('SSH Multiplexing Enabled:', $muxEnabled)->blue();
|
||||
if ($muxEnabled) {
|
||||
// Always use multiplexing when enabled
|
||||
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
|
||||
$ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||
ensureMultiplexedConnection($server);
|
||||
// ray('Using SSH Multiplexing')->green();
|
||||
} else {
|
||||
// ray('Not using SSH Multiplexing')->red();
|
||||
}
|
||||
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
$ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
|
||||
}
|
||||
@@ -167,11 +192,120 @@ function generateSshCommand(Server $server, string $command)
|
||||
.$command.PHP_EOL
|
||||
.$delimiter;
|
||||
|
||||
// ray($ssh_command);
|
||||
return $ssh_command;
|
||||
}
|
||||
|
||||
function ensureMultiplexedConnection(Server $server)
|
||||
{
|
||||
if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
static $ensuredConnections = [];
|
||||
|
||||
if (isset($ensuredConnections[$server->id])) {
|
||||
if (! shouldResetMultiplexedConnection($server)) {
|
||||
// ray('Using Existing Multiplexed Connection')->green();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
|
||||
$checkCommand = "ssh -O check -o ControlPath=$muxSocket ";
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
$checkCommand .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
|
||||
}
|
||||
$checkCommand .= " {$server->user}@{$server->ip}";
|
||||
|
||||
$process = Process::run($checkCommand);
|
||||
|
||||
if ($process->exitCode() === 0) {
|
||||
// ray('Existing Multiplexed Connection is Valid')->green();
|
||||
$ensuredConnections[$server->id] = [
|
||||
'timestamp' => now(),
|
||||
'muxSocket' => $muxSocket,
|
||||
];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ray('Establishing New Multiplexed Connection')->orange();
|
||||
|
||||
$privateKeyLocation = savePrivateKeyToFs($server);
|
||||
$connectionTimeout = config('constants.ssh.connection_timeout');
|
||||
$serverInterval = config('constants.ssh.server_interval');
|
||||
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||
|
||||
$establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
$establishCommand .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
|
||||
}
|
||||
$establishCommand .= "-i {$privateKeyLocation} "
|
||||
.'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
||||
.'-o PasswordAuthentication=no '
|
||||
."-o ConnectTimeout=$connectionTimeout "
|
||||
."-o ServerAliveInterval=$serverInterval "
|
||||
.'-o RequestTTY=no '
|
||||
.'-o LogLevel=ERROR '
|
||||
."-p {$server->port} "
|
||||
."{$server->user}@{$server->ip}";
|
||||
|
||||
$establishProcess = Process::run($establishCommand);
|
||||
|
||||
if ($establishProcess->exitCode() !== 0) {
|
||||
throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput());
|
||||
}
|
||||
|
||||
$ensuredConnections[$server->id] = [
|
||||
'timestamp' => now(),
|
||||
'muxSocket' => $muxSocket,
|
||||
];
|
||||
|
||||
// ray('Established New Multiplexed Connection')->green();
|
||||
}
|
||||
|
||||
function shouldResetMultiplexedConnection(Server $server)
|
||||
{
|
||||
if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static $ensuredConnections = [];
|
||||
|
||||
if (! isset($ensuredConnections[$server->id])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$lastEnsured = $ensuredConnections[$server->id]['timestamp'];
|
||||
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||
$resetInterval = strtotime($muxPersistTime) - time();
|
||||
|
||||
return $lastEnsured->addSeconds($resetInterval)->isPast();
|
||||
}
|
||||
|
||||
function resetMultiplexedConnection(Server $server)
|
||||
{
|
||||
if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
static $ensuredConnections = [];
|
||||
|
||||
if (isset($ensuredConnections[$server->id])) {
|
||||
$muxSocket = $ensuredConnections[$server->id]['muxSocket'];
|
||||
$closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip}";
|
||||
Process::run($closeCommand);
|
||||
unset($ensuredConnections[$server->id]);
|
||||
}
|
||||
}
|
||||
|
||||
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string
|
||||
{
|
||||
static $processCount = 0;
|
||||
$processCount++;
|
||||
|
||||
$timeout = config('constants.ssh.command_timeout');
|
||||
if ($command instanceof Collection) {
|
||||
$command = $command->toArray();
|
||||
@@ -180,10 +314,18 @@ function instant_remote_process(Collection|array $command, Server $server, bool
|
||||
$command = parseCommandsByLineForSudo(collect($command), $server);
|
||||
}
|
||||
$command_string = implode("\n", $command);
|
||||
$ssh_command = generateSshCommand($server, $command_string, $no_sudo);
|
||||
$process = Process::timeout($timeout)->run($ssh_command);
|
||||
|
||||
$start_time = microtime(true);
|
||||
$sshCommand = generateSshCommand($server, $command_string);
|
||||
$process = Process::timeout($timeout)->run($sshCommand);
|
||||
$end_time = microtime(true);
|
||||
|
||||
$execution_time = ($end_time - $start_time) * 1000; // Convert to milliseconds
|
||||
// ray('SSH command execution time:', $execution_time.' ms')->orange();
|
||||
|
||||
$output = trim($process->output());
|
||||
$exitCode = $process->exitCode();
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
if (! $throwError) {
|
||||
return null;
|
||||
@@ -223,7 +365,6 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
||||
if (is_null($application_deployment_queue)) {
|
||||
return collect([]);
|
||||
}
|
||||
// ray(data_get($application_deployment_queue, 'logs'));
|
||||
try {
|
||||
$decoded = json_decode(
|
||||
data_get($application_deployment_queue, 'logs'),
|
||||
@@ -233,7 +374,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
||||
} catch (\JsonException $exception) {
|
||||
return collect([]);
|
||||
}
|
||||
// ray($decoded );
|
||||
$seenCommands = collect();
|
||||
$formatted = collect($decoded);
|
||||
if (! $is_debug_enabled) {
|
||||
$formatted = $formatted->filter(fn ($i) => $i['hidden'] === false ?? false);
|
||||
@@ -244,7 +385,42 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
||||
data_set($i, 'timestamp', Carbon::parse(data_get($i, 'timestamp'))->format('Y-M-d H:i:s.u'));
|
||||
|
||||
return $i;
|
||||
});
|
||||
})
|
||||
->reduce(function ($deploymentLogLines, $logItem) use ($seenCommands) {
|
||||
$command = data_get($logItem, 'command');
|
||||
$isStderr = data_get($logItem, 'type') === 'stderr';
|
||||
$isNewCommand = ! is_null($command) && ! $seenCommands->first(function ($seenCommand) use ($logItem) {
|
||||
return data_get($seenCommand, 'command') === data_get($logItem, 'command') && data_get($seenCommand, 'batch') === data_get($logItem, 'batch');
|
||||
});
|
||||
|
||||
if ($isNewCommand) {
|
||||
$deploymentLogLines->push([
|
||||
'line' => $command,
|
||||
'timestamp' => data_get($logItem, 'timestamp'),
|
||||
'stderr' => $isStderr,
|
||||
'hidden' => data_get($logItem, 'hidden'),
|
||||
'command' => true,
|
||||
]);
|
||||
|
||||
$seenCommands->push([
|
||||
'command' => $command,
|
||||
'batch' => data_get($logItem, 'batch'),
|
||||
]);
|
||||
}
|
||||
|
||||
$lines = explode(PHP_EOL, data_get($logItem, 'output'));
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$deploymentLogLines->push([
|
||||
'line' => $line,
|
||||
'timestamp' => data_get($logItem, 'timestamp'),
|
||||
'stderr' => $isStderr,
|
||||
'hidden' => data_get($logItem, 'hidden'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $deploymentLogLines;
|
||||
}, collect());
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
@@ -258,6 +434,10 @@ function remove_mux_and_private_key(Server $server)
|
||||
{
|
||||
$muxFilename = $server->muxFilename();
|
||||
$privateKeyLocation = savePrivateKeyToFs($server);
|
||||
|
||||
$closeCommand = "ssh -O exit -o ControlPath=/var/www/html/storage/app/ssh/mux/{$muxFilename} {$server->user}@{$server->ip}";
|
||||
Process::run($closeCommand);
|
||||
|
||||
Storage::disk('ssh-mux')->delete($muxFilename);
|
||||
Storage::disk('ssh-keys')->delete($privateKeyLocation);
|
||||
}
|
||||
@@ -267,7 +447,10 @@ function refresh_server_connection(?PrivateKey $private_key = null)
|
||||
return;
|
||||
}
|
||||
foreach ($private_key->servers as $server) {
|
||||
Storage::disk('ssh-mux')->delete($server->muxFilename());
|
||||
$muxFilename = $server->muxFilename();
|
||||
$closeCommand = "ssh -O exit -o ControlPath=/var/www/html/storage/app/ssh/mux/{$muxFilename} {$server->user}@{$server->ip}";
|
||||
Process::run($closeCommand);
|
||||
Storage::disk('ssh-mux')->delete($muxFilename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,24 +460,17 @@ function checkRequiredCommands(Server $server)
|
||||
foreach ($commands as $command) {
|
||||
$commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false);
|
||||
if ($commandFound) {
|
||||
ray($command.' found');
|
||||
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'apt update && apt install -y {$command}'"], $server);
|
||||
} catch (\Throwable $e) {
|
||||
ray('could not install '.$command);
|
||||
ray($e);
|
||||
break;
|
||||
}
|
||||
$commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false);
|
||||
if ($commandFound) {
|
||||
ray($command.' found');
|
||||
|
||||
continue;
|
||||
}
|
||||
ray('could not install '.$command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use App\Models\Application;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Support\Stringable;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
@@ -15,9 +16,9 @@ function collectRegex(string $name)
|
||||
{
|
||||
return "/{$name}\w+/";
|
||||
}
|
||||
function replaceVariables($variable)
|
||||
function replaceVariables(string $variable): Stringable
|
||||
{
|
||||
return $variable->before('}')->replaceFirst('$', '')->replaceFirst('{', '');
|
||||
return str($variable)->before('}')->replaceFirst('$', '')->replaceFirst('{', '');
|
||||
}
|
||||
|
||||
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false)
|
||||
|
||||
@@ -478,7 +478,7 @@ function data_get_str($data, $key, $default = null): Stringable
|
||||
return str($str);
|
||||
}
|
||||
|
||||
function generateFqdn(Server $server, string $random): string
|
||||
function generateFqdn(Server $server, string $random, bool $forceHttps = false): string
|
||||
{
|
||||
$wildcard = data_get($server, 'settings.wildcard_domain');
|
||||
if (is_null($wildcard) || $wildcard === '') {
|
||||
@@ -488,6 +488,9 @@ function generateFqdn(Server $server, string $random): string
|
||||
$host = $url->getHost();
|
||||
$path = $url->getPath() === '/' ? '' : $url->getPath();
|
||||
$scheme = $url->getScheme();
|
||||
if ($forceHttps) {
|
||||
$scheme = 'https';
|
||||
}
|
||||
$finalFqdn = "$scheme://{$random}.$host$path";
|
||||
|
||||
return $finalFqdn;
|
||||
@@ -786,7 +789,7 @@ function replaceLocalSource(Stringable $source, Stringable $replacedWith)
|
||||
if ($source->startsWith('..')) {
|
||||
$source = $source->replaceFirst('..', $replacedWith->value());
|
||||
}
|
||||
if ($source->endsWith('/')) {
|
||||
if ($source->endsWith('/') && $source->value() !== '/') {
|
||||
$source = $source->replaceLast('/', '');
|
||||
}
|
||||
|
||||
@@ -1866,7 +1869,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
'key' => $key,
|
||||
'service_id' => $resource->id,
|
||||
])->first();
|
||||
$value = str(replaceVariables($value));
|
||||
$value = replaceVariables($value);
|
||||
$key = $value;
|
||||
if ($value->startsWith('SERVICE_')) {
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
@@ -2100,16 +2103,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
|
||||
// TODO: move this in a shared function
|
||||
if (! $parsedServiceVariables->has('COOLIFY_APP_NAME')) {
|
||||
$parsedServiceVariables->put('COOLIFY_APP_NAME', $resource->name);
|
||||
$parsedServiceVariables->put('COOLIFY_APP_NAME', "\"{$resource->name}\"");
|
||||
}
|
||||
if (! $parsedServiceVariables->has('COOLIFY_SERVER_IP')) {
|
||||
$parsedServiceVariables->put('COOLIFY_SERVER_IP', $resource->destination->server->ip);
|
||||
$parsedServiceVariables->put('COOLIFY_SERVER_IP', "\"{$resource->destination->server->ip}\"");
|
||||
}
|
||||
if (! $parsedServiceVariables->has('COOLIFY_ENVIRONMENT_NAME')) {
|
||||
$parsedServiceVariables->put('COOLIFY_ENVIRONMENT_NAME', $resource->environment->name);
|
||||
$parsedServiceVariables->put('COOLIFY_ENVIRONMENT_NAME', "\"{$resource->environment->name}\"");
|
||||
}
|
||||
if (! $parsedServiceVariables->has('COOLIFY_PROJECT_NAME')) {
|
||||
$parsedServiceVariables->put('COOLIFY_PROJECT_NAME', $resource->project()->name);
|
||||
$parsedServiceVariables->put('COOLIFY_PROJECT_NAME', "\"{$resource->project()->name}\"");
|
||||
}
|
||||
|
||||
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
|
||||
@@ -2627,7 +2630,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
'application_id' => $resource->id,
|
||||
'is_preview' => false,
|
||||
])->first();
|
||||
$value = str(replaceVariables($value));
|
||||
$value = replaceVariables($value);
|
||||
$key = $value;
|
||||
if ($value->startsWith('SERVICE_')) {
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
@@ -2864,6 +2867,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
return collect($finalServices);
|
||||
}
|
||||
}
|
||||
|
||||
function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $preview_id = null): Collection
|
||||
{
|
||||
$isApplication = $resource instanceof Application;
|
||||
@@ -2920,6 +2924,182 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
}
|
||||
|
||||
$parsedServices = collect([]);
|
||||
ray()->clearAll();
|
||||
|
||||
$allMagicEnvironments = collect([]);
|
||||
foreach ($services as $serviceName => $service) {
|
||||
$magicEnvironments = collect([]);
|
||||
$image = data_get_str($service, 'image');
|
||||
$environment = collect(data_get($service, 'environment', []));
|
||||
$buildArgs = collect(data_get($service, 'build.args', []));
|
||||
$environment = $environment->merge($buildArgs);
|
||||
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
|
||||
|
||||
if ($isService) {
|
||||
if ($isDatabase) {
|
||||
$savedService = ServiceDatabase::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
} else {
|
||||
$savedService = ServiceApplication::firstOrCreate([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $resource->id,
|
||||
]);
|
||||
}
|
||||
$environment = collect(data_get($service, 'environment', []));
|
||||
$buildArgs = collect(data_get($service, 'build.args', []));
|
||||
$environment = $environment->merge($buildArgs);
|
||||
|
||||
// convert environment variables to one format
|
||||
$environment = convertComposeEnvironmentToArray($environment);
|
||||
|
||||
// Add Coolify defined environments
|
||||
$allEnvironments = $resource->environment_variables()->get(['key', 'value']);
|
||||
|
||||
$allEnvironments = $allEnvironments->mapWithKeys(function ($item) {
|
||||
return [$item['key'] => $item['value']];
|
||||
});
|
||||
// filter and add magic environments
|
||||
foreach ($environment as $key => $value) {
|
||||
// Get all SERVICE_ variables from keys and values
|
||||
$key = str($key);
|
||||
$value = str($value);
|
||||
|
||||
$regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/';
|
||||
preg_match_all($regex, $value, $valueMatches);
|
||||
if (count($valueMatches[1]) > 0) {
|
||||
foreach ($valueMatches[1] as $match) {
|
||||
$match = replaceVariables($match);
|
||||
if ($match->startsWith('SERVICE_')) {
|
||||
if ($magicEnvironments->has($match->value())) {
|
||||
continue;
|
||||
}
|
||||
$magicEnvironments->put($match->value(), '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get magic environments where we need to preset the FQDN
|
||||
if ($key->startsWith('SERVICE_FQDN_')) {
|
||||
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
|
||||
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
||||
if ($isApplication) {
|
||||
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
|
||||
} elseif ($isService) {
|
||||
if ($fqdnFor) {
|
||||
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
|
||||
} else {
|
||||
$fqdn = generateFqdn($server, "{$savedService->name}-$uuid");
|
||||
}
|
||||
}
|
||||
if ($value && get_class($value) === 'Illuminate\Support\Stringable' && $value->startsWith('/')) {
|
||||
$path = $value->value();
|
||||
if ($path !== '/') {
|
||||
$fqdn = "$fqdn$path";
|
||||
}
|
||||
}
|
||||
if ($isApplication && is_null($resource->fqdn)) {
|
||||
data_forget($resource, 'environment_variables');
|
||||
data_forget($resource, 'environment_variables_preview');
|
||||
$resource->fqdn = $fqdn;
|
||||
$resource->save();
|
||||
} elseif ($isService && is_null($savedService->fqdn)) {
|
||||
$savedService->fqdn = $fqdn;
|
||||
$savedService->save();
|
||||
}
|
||||
|
||||
if (substr_count(str($key)->value(), '_') === 2) {
|
||||
$resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([
|
||||
'key' => $key->value(),
|
||||
$nameOfId => $resource->id,
|
||||
], [
|
||||
'value' => $fqdn,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
if (substr_count(str($key)->value(), '_') === 3) {
|
||||
$newKey = str($key)->beforeLast('_');
|
||||
$resource->environment_variables()->where('key', $newKey->value())->where($nameOfId, $resource->id)->firstOrCreate([
|
||||
'key' => $newKey->value(),
|
||||
$nameOfId => $resource->id,
|
||||
], [
|
||||
'value' => $fqdn,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
|
||||
|
||||
if ($magicEnvironments->count() > 0) {
|
||||
foreach ($magicEnvironments as $key => $value) {
|
||||
$key = str($key);
|
||||
$value = replaceVariables($value);
|
||||
$command = $key->after('SERVICE_')->before('_');
|
||||
$found = $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->first();
|
||||
if ($found) {
|
||||
continue;
|
||||
}
|
||||
if ($command->value() === 'FQDN') {
|
||||
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
||||
if (str($fqdnFor)->contains('_')) {
|
||||
$fqdnFor = str($fqdnFor)->before('_');
|
||||
}
|
||||
if ($isApplication) {
|
||||
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
|
||||
} elseif ($isService) {
|
||||
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
|
||||
}
|
||||
$resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([
|
||||
'key' => $key->value(),
|
||||
$nameOfId => $resource->id,
|
||||
], [
|
||||
'value' => $fqdn,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
} elseif ($command->value() === 'URL') {
|
||||
$fqdnFor = $key->after('SERVICE_URL_')->lower()->value();
|
||||
if (str($fqdnFor)->contains('_')) {
|
||||
$fqdnFor = str($fqdnFor)->before('_');
|
||||
}
|
||||
if ($isApplication) {
|
||||
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
|
||||
} elseif ($isService) {
|
||||
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
|
||||
}
|
||||
$resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([
|
||||
'key' => $key->value(),
|
||||
$nameOfId => $resource->id,
|
||||
], [
|
||||
'value' => $fqdn,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
|
||||
} else {
|
||||
$value = generateEnvValue($command, $resource);
|
||||
$resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([
|
||||
'key' => $key->value(),
|
||||
$nameOfId => $resource->id,
|
||||
], [
|
||||
'value' => $value,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the rest of the services
|
||||
foreach ($services as $serviceName => $service) {
|
||||
$image = data_get_str($service, 'image');
|
||||
$restart = data_get_str($service, 'restart', RESTART_MODE);
|
||||
@@ -2932,12 +3112,17 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
}
|
||||
$volumes = collect(data_get($service, 'volumes', []));
|
||||
$networks = collect(data_get($service, 'networks', []));
|
||||
$use_network_mode = data_get($service, 'network_mode') !== null;
|
||||
$depends_on = collect(data_get($service, 'depends_on', []));
|
||||
$labels = collect(data_get($service, 'labels', []));
|
||||
$environment = collect(data_get($service, 'environment', []));
|
||||
$ports = collect(data_get($service, 'ports', []));
|
||||
$buildArgs = collect(data_get($service, 'build.args', []));
|
||||
$environment = $environment->merge($buildArgs);
|
||||
|
||||
$environment = convertComposeEnvironmentToArray($environment);
|
||||
$coolifyEnvironments = collect([]);
|
||||
|
||||
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
|
||||
$volumesParsed = collect([]);
|
||||
|
||||
@@ -3047,7 +3232,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
if ($isApplication && $isPullRequest) {
|
||||
$source = $source."-pr-$pullRequestId";
|
||||
}
|
||||
|
||||
LocalFileVolume::updateOrCreate(
|
||||
[
|
||||
'mount_path' => $target,
|
||||
@@ -3069,10 +3253,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
if ($topLevel->get('volumes')->has($source->value())) {
|
||||
$temp = $topLevel->get('volumes')->get($source->value());
|
||||
if (data_get($temp, 'driver_opts.type') === 'cifs') {
|
||||
return $volume;
|
||||
continue;
|
||||
}
|
||||
if (data_get($temp, 'driver_opts.type') === 'nfs') {
|
||||
return $volume;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$slugWithoutUuid = Str::slug($source, '-');
|
||||
@@ -3092,10 +3276,9 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
$topLevel->get('volumes')->put($name, [
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
LocalPersistentVolume::updateOrCreate(
|
||||
[
|
||||
'mount_path' => $target,
|
||||
'name' => $name,
|
||||
'resource_id' => $originalResource->id,
|
||||
'resource_type' => get_class($originalResource),
|
||||
],
|
||||
@@ -3128,32 +3311,34 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
$depends_on = $newDependsOn;
|
||||
}
|
||||
}
|
||||
if ($topLevel->get('networks')?->count() > 0) {
|
||||
foreach ($topLevel->get('networks') as $networkName => $network) {
|
||||
if ($networkName === 'default') {
|
||||
continue;
|
||||
}
|
||||
// ignore aliases
|
||||
if ($network['aliases'] ?? false) {
|
||||
continue;
|
||||
}
|
||||
$networkExists = $networks->contains(function ($value, $key) use ($networkName) {
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
if (! $networkExists) {
|
||||
$networks->put($networkName, null);
|
||||
if (! $use_network_mode) {
|
||||
if ($topLevel->get('networks')?->count() > 0) {
|
||||
foreach ($topLevel->get('networks') as $networkName => $network) {
|
||||
if ($networkName === 'default') {
|
||||
continue;
|
||||
}
|
||||
// ignore aliases
|
||||
if ($network['aliases'] ?? false) {
|
||||
continue;
|
||||
}
|
||||
$networkExists = $networks->contains(function ($value, $key) use ($networkName) {
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
if (! $networkExists) {
|
||||
$networks->put($networkName, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) {
|
||||
return $value == $baseNetwork;
|
||||
});
|
||||
if (! $baseNetworkExists) {
|
||||
foreach ($baseNetwork as $network) {
|
||||
$topLevel->get('networks')->put($network, [
|
||||
'name' => $network,
|
||||
'external' => true,
|
||||
]);
|
||||
$baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) {
|
||||
return $value == $baseNetwork;
|
||||
});
|
||||
if (! $baseNetworkExists) {
|
||||
foreach ($baseNetwork as $network) {
|
||||
$topLevel->get('networks')->put($network, [
|
||||
'name' => $network,
|
||||
'external' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3179,177 +3364,46 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
|
||||
$networks_temp = collect();
|
||||
|
||||
foreach ($networks as $key => $network) {
|
||||
if (gettype($network) === 'string') {
|
||||
// networks:
|
||||
// - appwrite
|
||||
$networks_temp->put($network, null);
|
||||
} elseif (gettype($network) === 'array') {
|
||||
// networks:
|
||||
// default:
|
||||
// ipv4_address: 192.168.203.254
|
||||
$networks_temp->put($key, $network);
|
||||
if (! $use_network_mode) {
|
||||
foreach ($networks as $key => $network) {
|
||||
if (gettype($network) === 'string') {
|
||||
// networks:
|
||||
// - appwrite
|
||||
$networks_temp->put($network, null);
|
||||
} elseif (gettype($network) === 'array') {
|
||||
// networks:
|
||||
// default:
|
||||
// ipv4_address: 192.168.203.254
|
||||
$networks_temp->put($key, $network);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($baseNetwork as $key => $network) {
|
||||
$networks_temp->put($network, null);
|
||||
}
|
||||
|
||||
if ($isApplication) {
|
||||
if (data_get($resource, 'settings.connect_to_docker_network')) {
|
||||
$network = $resource->destination->network;
|
||||
foreach ($baseNetwork as $key => $network) {
|
||||
$networks_temp->put($network, null);
|
||||
$topLevel->get('networks')->put($network, [
|
||||
'name' => $network,
|
||||
'external' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
// convert environment variables to one format
|
||||
$environment = convertComposeEnvironmentToArray($environment);
|
||||
|
||||
// Add Coolify defined environments
|
||||
$allEnvironments = $resource->environment_variables()->get(['key', 'value']);
|
||||
|
||||
$allEnvironments = $allEnvironments->mapWithKeys(function ($item) {
|
||||
return [$item['key'] => $item['value']];
|
||||
});
|
||||
|
||||
// remove $environment from $allEnvironments
|
||||
$coolifyDefinedEnvironments = $allEnvironments->diffKeys($environment);
|
||||
|
||||
// filter magic environments
|
||||
$magicEnvironments = $environment->filter(function ($value, $key) {
|
||||
$value = str(replaceVariables(str($value)));
|
||||
|
||||
return str($key)->startsWith('SERVICE_') || str($value)->startsWith('SERVICE_');
|
||||
});
|
||||
$normalEnvironments = $environment->diffKeys($magicEnvironments);
|
||||
if ($magicEnvironments->count() > 0) {
|
||||
foreach ($magicEnvironments as $key => $value) {
|
||||
$key = str($key);
|
||||
$value = str(replaceVariables(str($value)));
|
||||
$originalValue = $value;
|
||||
$keyCommand = $key->after('SERVICE_')->before('_');
|
||||
$valueCommand = $value->after('SERVICE_')->before('_');
|
||||
if ($key->startsWith('SERVICE_FQDN_')) {
|
||||
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
||||
if (str($fqdnFor)->contains('_')) {
|
||||
$fqdnFor = str($fqdnFor)->before('_');
|
||||
}
|
||||
} elseif ($value->startsWith('SERVICE_FQDN_')) {
|
||||
$fqdnFor = $value->after('SERVICE_FQDN_')->lower()->value();
|
||||
if (str($fqdnFor)->contains('_')) {
|
||||
$fqdnFor = str($fqdnFor)->before('_');
|
||||
}
|
||||
} else {
|
||||
$fqdnFor = null;
|
||||
}
|
||||
if ($keyCommand->value() === 'FQDN' || $valueCommand->value() === 'FQDN') {
|
||||
if ($isApplication) {
|
||||
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
|
||||
} elseif ($isService) {
|
||||
if ($fqdnFor) {
|
||||
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
|
||||
} else {
|
||||
$fqdn = generateFqdn($server, "{$savedService->name}-$uuid");
|
||||
}
|
||||
}
|
||||
if ($value && get_class($value) === 'Illuminate\Support\Stringable' && $value->startsWith('/')) {
|
||||
$path = $value->value();
|
||||
if ($value === '/') {
|
||||
$value = "$fqdn";
|
||||
} else {
|
||||
$value = "$fqdn$path";
|
||||
}
|
||||
} else {
|
||||
$value = $fqdn;
|
||||
}
|
||||
if (! $isDatabase) {
|
||||
if ($isApplication && is_null($resource->fqdn)) {
|
||||
data_forget($resource, 'environment_variables');
|
||||
data_forget($resource, 'environment_variables_preview');
|
||||
$resource->fqdn = $value;
|
||||
$resource->save();
|
||||
} elseif ($isService && is_null($savedService->fqdn)) {
|
||||
if ($key->startsWith('SERVICE_FQDN_')) {
|
||||
$savedService->fqdn = $value;
|
||||
$savedService->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($keyCommand->value() === 'URL' || $valueCommand->value() === 'URL') {
|
||||
if ($isApplication) {
|
||||
$fqdn = generateFqdn($server, "{$resource->name}-{$uuid}");
|
||||
} elseif ($isService) {
|
||||
$fqdn = generateFqdn($server, "{$savedService->name}-{$uuid}");
|
||||
}
|
||||
if ($value && get_class($value) === 'Illuminate\Support\Stringable' && $value->startsWith('/')) {
|
||||
$path = $value->value();
|
||||
$value = "$fqdn$path";
|
||||
} else {
|
||||
$value = $fqdn;
|
||||
}
|
||||
$value = str($fqdn)->replace('http://', '')->replace('https://', '');
|
||||
} else {
|
||||
$generatedValue = generateEnvValue($valueCommand, $resource);
|
||||
if ($generatedValue) {
|
||||
$value = $generatedValue;
|
||||
}
|
||||
}
|
||||
if (str($fqdnFor)->startsWith('/')) {
|
||||
$fqdnFor = null;
|
||||
}
|
||||
// Lets save the magic value to the environment variables
|
||||
if (! $originalValue->startsWith('/')) {
|
||||
if ($key->startsWith('SERVICE_')) {
|
||||
$originalValue = $key;
|
||||
}
|
||||
$resource->environment_variables()->where('key', $originalValue->value())->where($nameOfId, $resource->id)->firstOrCreate([
|
||||
'key' => $originalValue->value(),
|
||||
$nameOfId => $resource->id,
|
||||
], [
|
||||
'value' => $value,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
if ($isApplication) {
|
||||
if (data_get($resource, 'settings.connect_to_docker_network')) {
|
||||
$network = $resource->destination->network;
|
||||
$networks_temp->put($network, null);
|
||||
$topLevel->get('networks')->put($network, [
|
||||
'name' => $network,
|
||||
'external' => true,
|
||||
]);
|
||||
}
|
||||
// Save the original value to the environment variables
|
||||
if ($originalValue->startsWith('SERVICE_')) {
|
||||
$value = "$$originalValue";
|
||||
}
|
||||
$resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([
|
||||
'key' => $key->value(),
|
||||
$nameOfId => $resource->id,
|
||||
], [
|
||||
'value' => "$value",
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$normalEnvironments = $environment->diffKeys($allMagicEnvironments);
|
||||
$normalEnvironments = $normalEnvironments->filter(function ($value, $key) {
|
||||
return ! str($value)->startsWith('SERVICE_');
|
||||
});
|
||||
|
||||
foreach ($normalEnvironments as $key => $value) {
|
||||
$key = str($key);
|
||||
$value = str($value);
|
||||
if ($value->startsWith('$')) {
|
||||
$value = str(replaceVariables(str($value)));
|
||||
if ($value->contains(':-')) {
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':-');
|
||||
} elseif ($value->contains('-')) {
|
||||
$key = $value->before('-');
|
||||
$value = $value->after('-');
|
||||
} elseif ($value->contains(':?')) {
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':?');
|
||||
} elseif ($value->contains('?')) {
|
||||
$key = $value->before('?');
|
||||
$value = $value->after('?');
|
||||
} else {
|
||||
$key = $value;
|
||||
$value = null;
|
||||
}
|
||||
$originalValue = $value;
|
||||
$parsedValue = replaceVariables($value);
|
||||
if ($value->startsWith('$SERVICE_')) {
|
||||
$resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([
|
||||
'key' => $key,
|
||||
$nameOfId => $resource->id,
|
||||
@@ -3358,6 +3412,57 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
if (! $value->startsWith('$')) {
|
||||
continue;
|
||||
}
|
||||
if ($key->value() === $parsedValue->value()) {
|
||||
$value = null;
|
||||
$resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([
|
||||
'key' => $key,
|
||||
$nameOfId => $resource->id,
|
||||
], [
|
||||
'value' => $value,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
} else {
|
||||
if ($value->startsWith('$')) {
|
||||
if ($value->contains(':-')) {
|
||||
$value = replaceVariables($value);
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':-');
|
||||
} elseif ($value->contains('-')) {
|
||||
$value = replaceVariables($value);
|
||||
|
||||
$key = $value->before('-');
|
||||
$value = $value->after('-');
|
||||
} elseif ($value->contains(':?')) {
|
||||
$value = replaceVariables($value);
|
||||
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':?');
|
||||
} elseif ($value->contains('?')) {
|
||||
$value = replaceVariables($value);
|
||||
|
||||
$key = $value->before('?');
|
||||
$value = $value->after('?');
|
||||
}
|
||||
if ($originalValue->value() === $value->value()) {
|
||||
continue;
|
||||
}
|
||||
$resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([
|
||||
'key' => $key,
|
||||
$nameOfId => $resource->id,
|
||||
], [
|
||||
'value' => $value,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if ($isApplication) {
|
||||
@@ -3366,13 +3471,13 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
$branch = "pull/{$pullRequestId}/head";
|
||||
}
|
||||
if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||
$environment->put('COOLIFY_BRANCH', $branch);
|
||||
$coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\"");
|
||||
}
|
||||
}
|
||||
|
||||
// Add COOLIFY_CONTAINER_NAME to environment
|
||||
if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||
$environment->put('COOLIFY_CONTAINER_NAME', $containerName);
|
||||
$coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "\"{$containerName}\"");
|
||||
}
|
||||
|
||||
if ($isApplication) {
|
||||
@@ -3426,21 +3531,26 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
}
|
||||
// Add COOLIFY_FQDN & COOLIFY_URL to environment
|
||||
if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
|
||||
$environment->put('COOLIFY_URL', $fqdns->implode(','));
|
||||
$coolifyEnvironments->put('COOLIFY_URL', $fqdns->implode(','));
|
||||
|
||||
$urls = $fqdns->map(function ($fqdn) {
|
||||
return str($fqdn)->replace('http://', '')->replace('https://', '');
|
||||
});
|
||||
$environment->put('COOLIFY_FQDN', $urls->implode(','));
|
||||
$coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(','));
|
||||
}
|
||||
add_coolify_default_environment_variables($resource, $environment, $resource->environment_variables);
|
||||
add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables);
|
||||
|
||||
if ($environment->count() > 0) {
|
||||
$environment = $environment->filter(function ($value, $key) {
|
||||
return ! str($key)->startsWith('SERVICE_FQDN_');
|
||||
});
|
||||
}
|
||||
$serviceLabels = $labels->merge($defaultLabels);
|
||||
if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
|
||||
if ($isApplication) {
|
||||
$shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels;
|
||||
$uuid = $resource->uuid;
|
||||
$network = $resource->destination->network;
|
||||
$network = data_get($resource, 'destination.network');
|
||||
if ($isPullRequest) {
|
||||
$uuid = "{$resource->uuid}-{$pullRequestId}";
|
||||
}
|
||||
@@ -3450,7 +3560,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
} else {
|
||||
$shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels;
|
||||
$uuid = $resource->uuid;
|
||||
$network = $resource->destination->network;
|
||||
$network = data_get($resource, 'destination.network');
|
||||
}
|
||||
if ($shouldGenerateLabelsExactly) {
|
||||
switch ($server->proxyType()) {
|
||||
@@ -3520,17 +3630,19 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
$payload = collect($service)->merge([
|
||||
'container_name' => $containerName,
|
||||
'restart' => $restart->value(),
|
||||
'networks' => $networks_temp,
|
||||
'labels' => $serviceLabels,
|
||||
]);
|
||||
if (! $use_network_mode) {
|
||||
$payload['networks'] = $networks_temp;
|
||||
}
|
||||
if ($ports->count() > 0) {
|
||||
$payload['ports'] = $ports;
|
||||
}
|
||||
if ($volumesParsed->count() > 0) {
|
||||
$payload['volumes'] = $volumesParsed;
|
||||
}
|
||||
if ($environment->count() > 0 || $coolifyDefinedEnvironments->count() > 0) {
|
||||
$payload['environment'] = $environment->merge($coolifyDefinedEnvironments);
|
||||
if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) {
|
||||
$payload['environment'] = $environment->merge($coolifyEnvironments);
|
||||
}
|
||||
if ($logging) {
|
||||
$payload['logging'] = $logging;
|
||||
@@ -3613,63 +3725,48 @@ function add_coolify_default_environment_variables(StandaloneRedis|StandalonePos
|
||||
}
|
||||
if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_APP_NAME')->isEmpty()) {
|
||||
if ($isAssociativeArray) {
|
||||
$where_to_add->put('COOLIFY_APP_NAME', $resource->name);
|
||||
$where_to_add->put('COOLIFY_APP_NAME', "\"{$resource->name}\"");
|
||||
} else {
|
||||
$where_to_add->push("COOLIFY_APP_NAME={$resource->name}");
|
||||
$where_to_add->push("COOLIFY_APP_NAME=\"{$resource->name}\"");
|
||||
}
|
||||
}
|
||||
if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_SERVER_IP')->isEmpty()) {
|
||||
if ($isAssociativeArray) {
|
||||
$where_to_add->put('COOLIFY_SERVER_IP', $ip);
|
||||
$where_to_add->put('COOLIFY_SERVER_IP', "\"{$ip}\"");
|
||||
} else {
|
||||
$where_to_add->push("COOLIFY_SERVER_IP={$ip}");
|
||||
$where_to_add->push("COOLIFY_SERVER_IP=\"{$ip}\"");
|
||||
}
|
||||
}
|
||||
if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_ENVIRONMENT_NAME')->isEmpty()) {
|
||||
if ($isAssociativeArray) {
|
||||
$where_to_add->put('COOLIFY_ENVIRONMENT_NAME', $resource->environment->name);
|
||||
$where_to_add->put('COOLIFY_ENVIRONMENT_NAME', "\"{$resource->environment->name}\"");
|
||||
} else {
|
||||
$where_to_add->push("COOLIFY_ENVIRONMENT_NAME={$resource->environment->name}");
|
||||
$where_to_add->push("COOLIFY_ENVIRONMENT_NAME=\"{$resource->environment->name}\"");
|
||||
}
|
||||
}
|
||||
if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_PROJECT_NAME')->isEmpty()) {
|
||||
if ($isAssociativeArray) {
|
||||
$where_to_add->put('COOLIFY_PROJECT_NAME', $resource->project()->name);
|
||||
$where_to_add->put('COOLIFY_PROJECT_NAME', "\"{$resource->project()->name}\"");
|
||||
} else {
|
||||
$where_to_add->push("COOLIFY_PROJECT_NAME={$resource->project()->name}");
|
||||
$where_to_add->push("COOLIFY_PROJECT_NAME=\"{$resource->project()->name}\"");
|
||||
}
|
||||
}
|
||||
ray($where_to_add);
|
||||
}
|
||||
|
||||
function convertComposeEnvironmentToArray($environment)
|
||||
{
|
||||
$convertedServiceVariables = collect([]);
|
||||
foreach ($environment as $variableName => $variableValue) {
|
||||
if (is_array($variableValue)) {
|
||||
$key = str(collect($variableValue)->keys()->first());
|
||||
$value = str(collect($variableValue)->values()->first());
|
||||
} elseif (is_string($variableValue)) {
|
||||
if (str($variableValue)->contains('=')) {
|
||||
$key = str($variableValue)->before('=');
|
||||
$value = str($variableValue)->after('=');
|
||||
} else {
|
||||
if (is_numeric($variableName)) {
|
||||
$key = str($variableValue);
|
||||
$value = null;
|
||||
} else {
|
||||
$key = str($variableName);
|
||||
if ($variableValue) {
|
||||
$value = str($variableValue);
|
||||
} else {
|
||||
$value = null;
|
||||
}
|
||||
}
|
||||
if (isAssociativeArray($environment)) {
|
||||
$convertedServiceVariables = $environment;
|
||||
} else {
|
||||
foreach ($environment as $value) {
|
||||
$parts = explode('=', $value, 2);
|
||||
$key = $parts[0];
|
||||
$realValue = $parts[1] ?? '';
|
||||
if ($key) {
|
||||
$convertedServiceVariables->put($key, $realValue);
|
||||
}
|
||||
}
|
||||
if ($key) {
|
||||
$convertedServiceVariables->put($key->value(), $value?->value() ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
return $convertedServiceVariables;
|
||||
|
||||
@@ -110,4 +110,4 @@
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
}
|
||||
|
||||
430
composer.lock
generated
430
composer.lock
generated
File diff suppressed because it is too large
Load Diff
424
config/clockwork.php
Normal file
424
config/clockwork.php
Normal file
@@ -0,0 +1,424 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Enable Clockwork
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork is enabled by default only when your application is in debug mode. Here you can explicitly enable or
|
||||
| disable Clockwork. When disabled, no data is collected and the api and web ui are inactive.
|
||||
| Unless explicitly enabled, Clockwork only runs on localhost, *.local, *.test and *.wip domains.
|
||||
|
|
||||
*/
|
||||
|
||||
'enable' => env('CLOCKWORK_ENABLE', null),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Features
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| You can enable or disable various Clockwork features here. Some features have additional settings (eg. slow query
|
||||
| threshold for database queries).
|
||||
|
|
||||
*/
|
||||
|
||||
'features' => [
|
||||
|
||||
// Cache usage stats and cache queries including results
|
||||
'cache' => [
|
||||
'enabled' => env('CLOCKWORK_CACHE_ENABLED', true),
|
||||
|
||||
// Collect cache queries
|
||||
'collect_queries' => env('CLOCKWORK_CACHE_QUERIES', true),
|
||||
|
||||
// Collect values from cache queries (high performance impact with a very high number of queries)
|
||||
'collect_values' => env('CLOCKWORK_CACHE_COLLECT_VALUES', false)
|
||||
],
|
||||
|
||||
// Database usage stats and queries
|
||||
'database' => [
|
||||
'enabled' => env('CLOCKWORK_DATABASE_ENABLED', true),
|
||||
|
||||
// Collect database queries (high performance impact with a very high number of queries)
|
||||
'collect_queries' => env('CLOCKWORK_DATABASE_COLLECT_QUERIES', true),
|
||||
|
||||
// Collect details of models updates (high performance impact with a lot of model updates)
|
||||
'collect_models_actions' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_ACTIONS', true),
|
||||
|
||||
// Collect details of retrieved models (very high performance impact with a lot of models retrieved)
|
||||
'collect_models_retrieved' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_RETRIEVED', false),
|
||||
|
||||
// Query execution time threshold in milliseconds after which the query will be marked as slow
|
||||
'slow_threshold' => env('CLOCKWORK_DATABASE_SLOW_THRESHOLD'),
|
||||
|
||||
// Collect only slow database queries
|
||||
'slow_only' => env('CLOCKWORK_DATABASE_SLOW_ONLY', false),
|
||||
|
||||
// Detect and report duplicate queries
|
||||
'detect_duplicate_queries' => env('CLOCKWORK_DATABASE_DETECT_DUPLICATE_QUERIES', false)
|
||||
],
|
||||
|
||||
// Dispatched events
|
||||
'events' => [
|
||||
'enabled' => env('CLOCKWORK_EVENTS_ENABLED', true),
|
||||
|
||||
// Ignored events (framework events are ignored by default)
|
||||
'ignored_events' => [
|
||||
// App\Events\UserRegistered::class,
|
||||
// 'user.registered'
|
||||
],
|
||||
],
|
||||
|
||||
// Laravel log (you can still log directly to Clockwork with laravel log disabled)
|
||||
'log' => [
|
||||
'enabled' => env('CLOCKWORK_LOG_ENABLED', true)
|
||||
],
|
||||
|
||||
// Sent notifications
|
||||
'notifications' => [
|
||||
'enabled' => env('CLOCKWORK_NOTIFICATIONS_ENABLED', true),
|
||||
],
|
||||
|
||||
// Performance metrics
|
||||
'performance' => [
|
||||
// Allow collecting of client metrics. Requires separate clockwork-browser npm package.
|
||||
'client_metrics' => env('CLOCKWORK_PERFORMANCE_CLIENT_METRICS', true)
|
||||
],
|
||||
|
||||
// Dispatched queue jobs
|
||||
'queue' => [
|
||||
'enabled' => env('CLOCKWORK_QUEUE_ENABLED', true)
|
||||
],
|
||||
|
||||
// Redis commands
|
||||
'redis' => [
|
||||
'enabled' => env('CLOCKWORK_REDIS_ENABLED', true)
|
||||
],
|
||||
|
||||
// Routes list
|
||||
'routes' => [
|
||||
'enabled' => env('CLOCKWORK_ROUTES_ENABLED', false),
|
||||
|
||||
// Collect only routes from particular namespaces (only application routes by default)
|
||||
'only_namespaces' => [ 'App' ]
|
||||
],
|
||||
|
||||
// Rendered views
|
||||
'views' => [
|
||||
'enabled' => env('CLOCKWORK_VIEWS_ENABLED', true),
|
||||
|
||||
// Collect views including view data (high performance impact with a high number of views)
|
||||
'collect_data' => env('CLOCKWORK_VIEWS_COLLECT_DATA', false),
|
||||
|
||||
// Use Twig profiler instead of Laravel events for apps using laravel-twigbridge (more precise, but does
|
||||
// not support collecting view data)
|
||||
'use_twig_profiler' => env('CLOCKWORK_VIEWS_USE_TWIG_PROFILER', false)
|
||||
]
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Enable web UI
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork comes with a web UI accessible via http://your.app/clockwork. Here you can enable or disable this
|
||||
| feature. You can also set a custom path for the web UI.
|
||||
|
|
||||
*/
|
||||
|
||||
'web' => env('CLOCKWORK_WEB', true),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Enable toolbar
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can show a toolbar with basic metrics on all responses. Here you can enable or disable this feature.
|
||||
| Requires a separate clockwork-browser npm library.
|
||||
| For installation instructions see https://underground.works/clockwork/#docs-viewing-data
|
||||
|
|
||||
*/
|
||||
|
||||
'toolbar' => env('CLOCKWORK_TOOLBAR', true),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| HTTP requests collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork collects data about HTTP requests to your app. Here you can choose which requests should be collected.
|
||||
|
|
||||
*/
|
||||
|
||||
'requests' => [
|
||||
// With on-demand mode enabled, Clockwork will only profile requests when the browser extension is open or you
|
||||
// manually pass a "clockwork-profile" cookie or get/post data key.
|
||||
// Optionally you can specify a "secret" that has to be passed as the value to enable profiling.
|
||||
'on_demand' => env('CLOCKWORK_REQUESTS_ON_DEMAND', false),
|
||||
|
||||
// Collect only errors (requests with HTTP 4xx and 5xx responses)
|
||||
'errors_only' => env('CLOCKWORK_REQUESTS_ERRORS_ONLY', false),
|
||||
|
||||
// Response time threshold in milliseconds after which the request will be marked as slow
|
||||
'slow_threshold' => env('CLOCKWORK_REQUESTS_SLOW_THRESHOLD'),
|
||||
|
||||
// Collect only slow requests
|
||||
'slow_only' => env('CLOCKWORK_REQUESTS_SLOW_ONLY', false),
|
||||
|
||||
// Sample the collected requests (e.g. set to 100 to collect only 1 in 100 requests)
|
||||
'sample' => env('CLOCKWORK_REQUESTS_SAMPLE', false),
|
||||
|
||||
// List of URIs that should not be collected
|
||||
'except' => [
|
||||
'/horizon/.*', // Laravel Horizon requests
|
||||
'/telescope/.*', // Laravel Telescope requests
|
||||
'/_tt/.*', // Laravel Telescope toolbar
|
||||
'/_debugbar/.*', // Laravel DebugBar requests
|
||||
],
|
||||
|
||||
// List of URIs that should be collected, any other URI will not be collected if not empty
|
||||
'only' => [
|
||||
// '/api/.*'
|
||||
],
|
||||
|
||||
// Don't collect OPTIONS requests, mostly used in the CSRF pre-flight requests and are rarely of interest
|
||||
'except_preflight' => env('CLOCKWORK_REQUESTS_EXCEPT_PREFLIGHT', true)
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Artisan commands collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can collect data about executed artisan commands. Here you can enable and configure which commands
|
||||
| should be collected.
|
||||
|
|
||||
*/
|
||||
|
||||
'artisan' => [
|
||||
// Enable or disable collection of executed Artisan commands
|
||||
'collect' => env('CLOCKWORK_ARTISAN_COLLECT', false),
|
||||
|
||||
// List of commands that should not be collected (built-in commands are not collected by default)
|
||||
'except' => [
|
||||
// 'inspire'
|
||||
],
|
||||
|
||||
// List of commands that should be collected, any other command will not be collected if not empty
|
||||
'only' => [
|
||||
// 'inspire'
|
||||
],
|
||||
|
||||
// Enable or disable collection of command output
|
||||
'collect_output' => env('CLOCKWORK_ARTISAN_COLLECT_OUTPUT', false),
|
||||
|
||||
// Enable or disable collection of built-in Laravel commands
|
||||
'except_laravel_commands' => env('CLOCKWORK_ARTISAN_EXCEPT_LARAVEL_COMMANDS', true)
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Queue jobs collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can collect data about executed queue jobs. Here you can enable and configure which queue jobs should
|
||||
| be collected.
|
||||
|
|
||||
*/
|
||||
|
||||
'queue' => [
|
||||
// Enable or disable collection of executed queue jobs
|
||||
'collect' => env('CLOCKWORK_QUEUE_COLLECT', false),
|
||||
|
||||
// List of queue jobs that should not be collected
|
||||
'except' => [
|
||||
// App\Jobs\ExpensiveJob::class
|
||||
],
|
||||
|
||||
// List of queue jobs that should be collected, any other queue job will not be collected if not empty
|
||||
'only' => [
|
||||
// App\Jobs\BuggyJob::class
|
||||
]
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Tests collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can collect data about executed tests. Here you can enable and configure which tests should be
|
||||
| collected.
|
||||
|
|
||||
*/
|
||||
|
||||
'tests' => [
|
||||
// Enable or disable collection of ran tests
|
||||
'collect' => env('CLOCKWORK_TESTS_COLLECT', false),
|
||||
|
||||
// List of tests that should not be collected
|
||||
'except' => [
|
||||
// Tests\Unit\ExampleTest::class
|
||||
]
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Enable data collection when Clockwork is disabled
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| You can enable this setting to collect data even when Clockwork is disabled, e.g. for future analysis.
|
||||
|
|
||||
*/
|
||||
|
||||
'collect_data_always' => env('CLOCKWORK_COLLECT_DATA_ALWAYS', false),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Metadata storage
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Configure how is the metadata collected by Clockwork stored. Three options are available:
|
||||
| - files - A simple fast storage implementation storing data in one-per-request files.
|
||||
| - sql - Stores requests in a sql database. Supports MySQL, PostgreSQL and SQLite. Requires PDO.
|
||||
| - redis - Stores requests in redis. Requires phpredis.
|
||||
*/
|
||||
|
||||
'storage' => env('CLOCKWORK_STORAGE', 'files'),
|
||||
|
||||
// Path where the Clockwork metadata is stored
|
||||
'storage_files_path' => env('CLOCKWORK_STORAGE_FILES_PATH', storage_path('clockwork')),
|
||||
|
||||
// Compress the metadata files using gzip, trading a little bit of performance for lower disk usage
|
||||
'storage_files_compress' => env('CLOCKWORK_STORAGE_FILES_COMPRESS', false),
|
||||
|
||||
// SQL database to use, can be a name of database configured in database.php or a path to a SQLite file
|
||||
'storage_sql_database' => env('CLOCKWORK_STORAGE_SQL_DATABASE', storage_path('clockwork.sqlite')),
|
||||
|
||||
// SQL table name to use, the table is automatically created and updated when needed
|
||||
'storage_sql_table' => env('CLOCKWORK_STORAGE_SQL_TABLE', 'clockwork'),
|
||||
|
||||
// Redis connection, name of redis connection or cluster configured in database.php
|
||||
'storage_redis' => env('CLOCKWORK_STORAGE_REDIS', 'default'),
|
||||
|
||||
// Redis prefix for Clockwork keys ("clockwork" if not set)
|
||||
'storage_redis_prefix' => env('CLOCKWORK_STORAGE_REDIS_PREFIX', 'clockwork'),
|
||||
|
||||
// Maximum lifetime of collected metadata in minutes, older requests will automatically be deleted, false to disable
|
||||
'storage_expiration' => env('CLOCKWORK_STORAGE_EXPIRATION', 60 * 24 * 7),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Authentication
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can be configured to require authentication before allowing access to the collected data. This might be
|
||||
| useful when the application is publicly accessible. Setting to true will enable a simple authentication with a
|
||||
| pre-configured password. You can also pass a class name of a custom implementation.
|
||||
|
|
||||
*/
|
||||
|
||||
'authentication' => env('CLOCKWORK_AUTHENTICATION', false),
|
||||
|
||||
// Password for the simple authentication
|
||||
'authentication_password' => env('CLOCKWORK_AUTHENTICATION_PASSWORD', 'VerySecretPassword'),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Stack traces collection
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork can collect stack traces for log messages and certain data like database queries. Here you can set
|
||||
| whether to collect stack traces, limit the number of collected frames and set further configuration. Collecting
|
||||
| long stack traces considerably increases metadata size.
|
||||
|
|
||||
*/
|
||||
|
||||
'stack_traces' => [
|
||||
// Enable or disable collecting of stack traces
|
||||
'enabled' => env('CLOCKWORK_STACK_TRACES_ENABLED', true),
|
||||
|
||||
// Limit the number of frames to be collected
|
||||
'limit' => env('CLOCKWORK_STACK_TRACES_LIMIT', 10),
|
||||
|
||||
// List of vendor names to skip when determining caller, common vendors are automatically added
|
||||
'skip_vendors' => [
|
||||
// 'phpunit'
|
||||
],
|
||||
|
||||
// List of namespaces to skip when determining caller
|
||||
'skip_namespaces' => [
|
||||
// 'Laravel'
|
||||
],
|
||||
|
||||
// List of class names to skip when determining caller
|
||||
'skip_classes' => [
|
||||
// App\CustomLog::class
|
||||
]
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Serialization
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork serializes the collected data to json for storage and transfer. Here you can configure certain aspects
|
||||
| of serialization. Serialization has a large effect on the cpu time and memory usage.
|
||||
|
|
||||
*/
|
||||
|
||||
// Maximum depth of serialized multi-level arrays and objects
|
||||
'serialization_depth' => env('CLOCKWORK_SERIALIZATION_DEPTH', 10),
|
||||
|
||||
// A list of classes that will never be serialized (e.g. a common service container class)
|
||||
'serialization_blackbox' => [
|
||||
\Illuminate\Container\Container::class,
|
||||
\Illuminate\Foundation\Application::class,
|
||||
\Laravel\Lumen\Application::class
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Register helpers
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork comes with a "clock" global helper function. You can use this helper to quickly log something and to
|
||||
| access the Clockwork instance.
|
||||
|
|
||||
*/
|
||||
|
||||
'register_helpers' => env('CLOCKWORK_REGISTER_HELPERS', true),
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Send headers for AJAX request
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| When trying to collect data, the AJAX method can sometimes fail if it is missing required headers. For example, an
|
||||
| API might require a version number using Accept headers to route the HTTP request to the correct codebase.
|
||||
|
|
||||
*/
|
||||
|
||||
'headers' => [
|
||||
// 'Accept' => 'application/vnd.com.whatever.v1+json',
|
||||
],
|
||||
|
||||
/*
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
| Server timing
|
||||
|------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
| Clockwork supports the W3C Server Timing specification, which allows for collecting a simple performance metrics
|
||||
| in a cross-browser way. E.g. in Chrome, your app, database and timeline event timings will be shown in the Dev
|
||||
| Tools network tab. This setting specifies the max number of timeline events that will be sent. Setting to false
|
||||
| will disable the feature.
|
||||
|
|
||||
*/
|
||||
|
||||
'server_timing' => env('CLOCKWORK_SERVER_TIMING', 10)
|
||||
|
||||
];
|
||||
@@ -6,7 +6,9 @@ return [
|
||||
'contact' => 'https://coolify.io/docs/contact',
|
||||
],
|
||||
'ssh' => [
|
||||
'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1m'),
|
||||
// Using MUX
|
||||
'mux_enabled' => env('MUX_ENABLED', env('SSH_MUX_ENABLED', true), true),
|
||||
'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1h'),
|
||||
'connection_timeout' => 10,
|
||||
'server_interval' => 20,
|
||||
'command_timeout' => 7200,
|
||||
|
||||
@@ -7,11 +7,10 @@ return [
|
||||
'self_hosted' => env('SELF_HOSTED', true),
|
||||
'waitlist' => env('WAITLIST', false),
|
||||
'license_url' => 'https://licenses.coollabs.io',
|
||||
'mux_enabled' => env('MUX_ENABLED', true),
|
||||
'dev_webhook' => env('SERVEO_URL'),
|
||||
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
|
||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
|
||||
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper'),
|
||||
'is_horizon_enabled' => env('HORIZON_ENABLED', true),
|
||||
'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true),
|
||||
];
|
||||
|
||||
@@ -7,7 +7,7 @@ return [
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.326',
|
||||
'release' => '4.0.0-beta.340',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.326';
|
||||
return '4.0.0-beta.340';
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('instance_settings', function (Blueprint $table) {
|
||||
$table->string('helper_version')->default('1.0.0');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('instance_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('helper_version');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use App\Models\ServerSetting;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->boolean('force_docker_cleanup')->default(true)->change();
|
||||
});
|
||||
$serverSettings = ServerSetting::all();
|
||||
foreach ($serverSettings as $serverSetting) {
|
||||
if ($serverSetting->force_docker_cleanup === false) {
|
||||
$serverSetting->force_docker_cleanup = true;
|
||||
$serverSetting->docker_cleanup_frequency = '*/10 * * * *';
|
||||
$serverSetting->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->boolean('force_docker_cleanup')->default(false)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use App\Models\ServerSetting;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->string('docker_cleanup_frequency')->default('0 0 * * *')->change();
|
||||
});
|
||||
|
||||
$serverSettings = ServerSetting::all();
|
||||
foreach ($serverSettings as $serverSetting) {
|
||||
if ($serverSetting->force_docker_cleanup && $serverSetting->docker_cleanup_frequency === '*/10 * * * *') {
|
||||
$serverSetting->docker_cleanup_frequency = '0 0 * * *';
|
||||
$serverSetting->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->string('docker_cleanup_frequency')->default('*/10 * * * *')->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class UpdateServerSettingsDefaultTimezone extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->string('server_timezone')->default('UTC')->change();
|
||||
});
|
||||
|
||||
DB::table('server_settings')
|
||||
->whereNull('server_timezone')
|
||||
->orWhere('server_timezone', '')
|
||||
->update(['server_timezone' => 'UTC']);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->string('server_timezone')->default('')->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ services:
|
||||
PUSHER_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
||||
volumes:
|
||||
- .:/var/www/html/:cached
|
||||
- /data/coolify/backups/:/var/www/html/storage/app/backups
|
||||
postgres:
|
||||
pull_policy: always
|
||||
ports:
|
||||
@@ -43,10 +44,17 @@ services:
|
||||
- /data/coolify/_volumes/redis/:/data
|
||||
# - coolify-redis-data-dev:/data
|
||||
soketi:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/coolify-realtime/Dockerfile
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "${FORWARD_SOKETI_PORT:-6001}:6001"
|
||||
- "6002:6002"
|
||||
volumes:
|
||||
- ./storage:/var/www/html/storage
|
||||
- ./docker/coolify-realtime/terminal-server.js:/terminal/terminal-server.js
|
||||
environment:
|
||||
SOKETI_DEBUG: "false"
|
||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||
|
||||
@@ -48,6 +48,7 @@ services:
|
||||
- PUSHER_APP_SECRET
|
||||
- AUTOUPDATE
|
||||
- SELF_HOSTED
|
||||
- SSH_MUX_ENABLED
|
||||
- SSH_MUX_PERSIST_TIME
|
||||
- FEEDBACK_DISCORD_WEBHOOK
|
||||
- WAITLIST
|
||||
@@ -109,18 +110,24 @@ services:
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
soketi:
|
||||
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.1'
|
||||
ports:
|
||||
- "${SOKETI_PORT:-6001}:6001"
|
||||
- "6002:6002"
|
||||
volumes:
|
||||
- /data/coolify/ssh:/var/www/html/storage/app/ssh
|
||||
environment:
|
||||
APP_NAME: "${APP_NAME:-Coolify}"
|
||||
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
|
||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
|
||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
||||
healthcheck:
|
||||
test: wget -qO- http://127.0.0.1:6001/ready || exit 1
|
||||
test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1" ]
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
|
||||
volumes:
|
||||
coolify-db:
|
||||
name: coolify-db
|
||||
|
||||
@@ -45,7 +45,7 @@ services:
|
||||
- PUSHER_APP_SECRET
|
||||
- AUTOUPDATE=true
|
||||
- SELF_HOSTED=true
|
||||
- MUX_ENABLED=false
|
||||
- SSH_MUX_ENABLED=false
|
||||
- IS_WINDOWS_DOCKER_DESKTOP=true
|
||||
ports:
|
||||
- "${APP_PORT:-8000}:80"
|
||||
@@ -103,7 +103,7 @@ services:
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
soketi:
|
||||
image: 'quay.io/soketi/soketi:1.6-16-alpine'
|
||||
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.0'
|
||||
pull_policy: always
|
||||
container_name: coolify-realtime
|
||||
restart: always
|
||||
@@ -111,16 +111,21 @@ services:
|
||||
- .env
|
||||
ports:
|
||||
- "${SOKETI_PORT:-6001}:6001"
|
||||
- "6002:6002"
|
||||
volumes:
|
||||
- ./ssh:/var/www/html/storage/app/ssh
|
||||
environment:
|
||||
APP_NAME: "${APP_NAME:-Coolify}"
|
||||
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
|
||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
|
||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
||||
healthcheck:
|
||||
test: wget -qO- http://localhost:6001/ready || exit 1
|
||||
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1"]
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
|
||||
volumes:
|
||||
coolify-db:
|
||||
name: coolify-db
|
||||
|
||||
@@ -24,8 +24,9 @@ services:
|
||||
networks:
|
||||
- coolify
|
||||
soketi:
|
||||
image: 'quay.io/soketi/soketi:1.6-16-alpine'
|
||||
container_name: coolify-realtime
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
restart: always
|
||||
networks:
|
||||
- coolify
|
||||
|
||||
@@ -8,9 +8,9 @@ ARG DOCKER_COMPOSE_VERSION=2.27.1
|
||||
# https://github.com/docker/buildx/releases
|
||||
ARG DOCKER_BUILDX_VERSION=0.14.1
|
||||
# https://github.com/buildpacks/pack/releases
|
||||
ARG PACK_VERSION=0.34.1
|
||||
ARG PACK_VERSION=0.35.1
|
||||
# https://github.com/railwayapp/nixpacks/releases
|
||||
ARG NIXPACKS_VERSION=1.24.1
|
||||
ARG NIXPACKS_VERSION=1.28.0
|
||||
|
||||
USER root
|
||||
WORKDIR /artifacts
|
||||
@@ -34,7 +34,7 @@ RUN if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
|
||||
chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack /root/.docker/cli-plugins/docker-buildx \
|
||||
;fi
|
||||
|
||||
COPY --from=minio/mc:RELEASE.2024-03-13T23-51-57Z /usr/bin/mc /usr/bin/mc
|
||||
COPY --from=minio/mc:RELEASE.2024-09-09T07-53-10Z /usr/bin/mc /usr/bin/mc
|
||||
RUN chmod +x /usr/bin/mc
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
|
||||
9
docker/coolify-realtime/Dockerfile
Normal file
9
docker/coolify-realtime/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM quay.io/soketi/soketi:1.6-16-alpine
|
||||
WORKDIR /terminal
|
||||
RUN apk add --no-cache openssh-client make g++ python3
|
||||
COPY docker/coolify-realtime/package.json ./
|
||||
RUN npm i
|
||||
RUN npm rebuild node-pty --update-binary
|
||||
COPY docker/coolify-realtime/soketi-entrypoint.sh /soketi-entrypoint.sh
|
||||
COPY docker/coolify-realtime/terminal-server.js /terminal/terminal-server.js
|
||||
ENTRYPOINT ["/bin/sh", "/soketi-entrypoint.sh"]
|
||||
13
docker/coolify-realtime/package.json
Normal file
13
docker/coolify-realtime/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"cookie": "^0.6.0",
|
||||
"axios": "1.7.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"node-pty": "^1.0.0",
|
||||
"ws": "^8.17.0"
|
||||
}
|
||||
}
|
||||
27
docker/coolify-realtime/soketi-entrypoint.sh
Normal file
27
docker/coolify-realtime/soketi-entrypoint.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Function to timestamp logs
|
||||
timestamp() {
|
||||
date "+%Y-%m-%d %H:%M:%S"
|
||||
}
|
||||
|
||||
# Start the terminal server in the background with logging
|
||||
node /terminal/terminal-server.js > >(while read line; do echo "$(timestamp) [TERMINAL] $line"; done) 2>&1 &
|
||||
TERMINAL_PID=$!
|
||||
|
||||
# Start the Soketi process in the background with logging
|
||||
node /app/bin/server.js start > >(while read line; do echo "$(timestamp) [SOKETI] $line"; done) 2>&1 &
|
||||
SOKETI_PID=$!
|
||||
|
||||
# Function to forward signals to child processes
|
||||
forward_signal() {
|
||||
kill -$1 $TERMINAL_PID $SOKETI_PID
|
||||
}
|
||||
|
||||
# Forward SIGTERM to child processes
|
||||
trap 'forward_signal TERM' TERM
|
||||
|
||||
# Wait for any process to exit
|
||||
wait -n
|
||||
|
||||
# Exit with status of process that exited first
|
||||
exit $?
|
||||
229
docker/coolify-realtime/terminal-server.js
Executable file
229
docker/coolify-realtime/terminal-server.js
Executable file
@@ -0,0 +1,229 @@
|
||||
import { WebSocketServer } from 'ws';
|
||||
import http from 'http';
|
||||
import pty from 'node-pty';
|
||||
import axios from 'axios';
|
||||
import cookie from 'cookie';
|
||||
import 'dotenv/config'
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.url === '/ready') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('OK');
|
||||
} else {
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.end('Not Found');
|
||||
}
|
||||
});
|
||||
|
||||
const verifyClient = async (info, callback) => {
|
||||
const cookies = cookie.parse(info.req.headers.cookie || '');
|
||||
// const origin = new URL(info.origin);
|
||||
// const protocol = origin.protocol;
|
||||
const xsrfToken = cookies['XSRF-TOKEN'];
|
||||
|
||||
// Generate session cookie name based on APP_NAME
|
||||
const appName = process.env.APP_NAME || 'laravel';
|
||||
const sessionCookieName = `${appName.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}_session`;
|
||||
const laravelSession = cookies[sessionCookieName];
|
||||
|
||||
// Verify presence of required tokens
|
||||
if (!laravelSession || !xsrfToken) {
|
||||
return callback(false, 401, 'Unauthorized: Missing required tokens');
|
||||
}
|
||||
|
||||
try {
|
||||
// Authenticate with Laravel backend
|
||||
const response = await axios.post(`http://coolify/terminal/auth`, null, {
|
||||
headers: {
|
||||
'Cookie': `${sessionCookieName}=${laravelSession}`,
|
||||
'X-XSRF-TOKEN': xsrfToken
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
// Authentication successful
|
||||
callback(true);
|
||||
} else {
|
||||
callback(false, 401, 'Unauthorized: Invalid credentials');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error.message);
|
||||
callback(false, 500, 'Internal Server Error');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const wss = new WebSocketServer({ server, path: '/terminal/ws', verifyClient: verifyClient });
|
||||
const userSessions = new Map();
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
const userId = generateUserId();
|
||||
const userSession = { ws, userId, ptyProcess: null, isActive: false };
|
||||
userSessions.set(userId, userSession);
|
||||
|
||||
ws.on('message', (message) => handleMessage(userSession, message));
|
||||
ws.on('error', (err) => handleError(err, userId));
|
||||
ws.on('close', () => handleClose(userId));
|
||||
});
|
||||
|
||||
const messageHandlers = {
|
||||
message: (session, data) => session.ptyProcess.write(data),
|
||||
resize: (session, { cols, rows }) => session.ptyProcess.resize(cols, rows),
|
||||
pause: (session) => session.ptyProcess.pause(),
|
||||
resume: (session) => session.ptyProcess.resume(),
|
||||
checkActive: (session, data) => {
|
||||
if (data === 'force' && session.isActive) {
|
||||
killPtyProcess(session.userId);
|
||||
} else {
|
||||
session.ws.send(session.isActive);
|
||||
}
|
||||
},
|
||||
command: (session, data) => handleCommand(session.ws, data, session.userId)
|
||||
};
|
||||
|
||||
function handleMessage(userSession, message) {
|
||||
const parsed = parseMessage(message);
|
||||
if (!parsed) return;
|
||||
|
||||
Object.entries(parsed).forEach(([key, value]) => {
|
||||
const handler = messageHandlers[key];
|
||||
if (handler && (userSession.isActive || key === 'checkActive' || key === 'command')) {
|
||||
handler(userSession, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function parseMessage(message) {
|
||||
try {
|
||||
return JSON.parse(message);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse message:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCommand(ws, command, userId) {
|
||||
const userSession = userSessions.get(userId);
|
||||
|
||||
if (userSession && userSession.isActive) {
|
||||
const result = await killPtyProcess(userId);
|
||||
if (!result) {
|
||||
// if terminal is still active, even after we tried to kill it, dont continue and show error
|
||||
ws.send('unprocessable');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const commandString = command[0].split('\n').join(' ');
|
||||
const timeout = extractTimeout(commandString);
|
||||
const sshArgs = extractSshArgs(commandString);
|
||||
const hereDocContent = extractHereDocContent(commandString);
|
||||
const options = {
|
||||
name: 'xterm-color',
|
||||
cols: 80,
|
||||
rows: 30,
|
||||
cwd: process.env.HOME,
|
||||
};
|
||||
|
||||
// NOTE: - Initiates a process within the Terminal container
|
||||
// Establishes an SSH connection to root@coolify with RequestTTY enabled
|
||||
// Executes the 'docker exec' command to connect to a specific container
|
||||
// If the user types 'exit', it terminates the container connection and reverts to the server.
|
||||
const ptyProcess = pty.spawn('ssh', sshArgs.concat(['bash']), options);
|
||||
userSession.ptyProcess = ptyProcess;
|
||||
userSession.isActive = true;
|
||||
ptyProcess.write(hereDocContent + '\n');
|
||||
// clear the terminal if the user has clear command
|
||||
ptyProcess.write('command -v clear >/dev/null 2>&1 && clear\n');
|
||||
|
||||
ws.send('pty-ready');
|
||||
|
||||
ptyProcess.onData((data) => ws.send(data));
|
||||
|
||||
ptyProcess.onExit(({ exitCode, signal }) => {
|
||||
console.error(`Process exited with code ${exitCode} and signal ${signal}`);
|
||||
userSession.isActive = false;
|
||||
});
|
||||
|
||||
if (timeout) {
|
||||
setTimeout(async () => {
|
||||
await killPtyProcess(userId);
|
||||
}, timeout * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleError(err, userId) {
|
||||
console.error('WebSocket error:', err);
|
||||
await killPtyProcess(userId);
|
||||
}
|
||||
|
||||
async function handleClose(userId) {
|
||||
await killPtyProcess(userId);
|
||||
userSessions.delete(userId);
|
||||
}
|
||||
|
||||
async function killPtyProcess(userId) {
|
||||
const session = userSessions.get(userId);
|
||||
if (!session?.ptyProcess) return false;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// Loop to ensure terminal is killed before continuing
|
||||
let killAttempts = 0;
|
||||
const maxAttempts = 5;
|
||||
|
||||
const attemptKill = () => {
|
||||
killAttempts++;
|
||||
|
||||
// session.ptyProcess.kill() wont work here because of https://github.com/moby/moby/issues/9098
|
||||
// patch with https://github.com/moby/moby/issues/9098#issuecomment-189743947
|
||||
session.ptyProcess.write('kill -TERM -$$ && exit\n');
|
||||
|
||||
setTimeout(() => {
|
||||
if (!session.isActive || !session.ptyProcess) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (killAttempts < maxAttempts) {
|
||||
attemptKill();
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
|
||||
attemptKill();
|
||||
});
|
||||
}
|
||||
|
||||
function generateUserId() {
|
||||
return Math.random().toString(36).substring(2, 11);
|
||||
}
|
||||
|
||||
function extractTimeout(commandString) {
|
||||
const timeoutMatch = commandString.match(/timeout (\d+)/);
|
||||
return timeoutMatch ? parseInt(timeoutMatch[1], 10) : null;
|
||||
}
|
||||
|
||||
function extractSshArgs(commandString) {
|
||||
const sshCommandMatch = commandString.match(/ssh (.+?) 'bash -se'/);
|
||||
let sshArgs = sshCommandMatch ? sshCommandMatch[1].split(' ') : [];
|
||||
sshArgs = sshArgs.map(arg => arg === 'RequestTTY=no' ? 'RequestTTY=yes' : arg);
|
||||
if (!sshArgs.includes('RequestTTY=yes')) {
|
||||
sshArgs.push('-o', 'RequestTTY=yes');
|
||||
}
|
||||
return sshArgs;
|
||||
}
|
||||
|
||||
function extractHereDocContent(commandString) {
|
||||
const delimiterMatch = commandString.match(/<< (\S+)/);
|
||||
const delimiter = delimiterMatch ? delimiterMatch[1] : null;
|
||||
const escapedDelimiter = delimiter.slice(1).trim().replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
const hereDocRegex = new RegExp(`<< \\\\${escapedDelimiter}([\\s\\S\\.]*?)${escapedDelimiter}`);
|
||||
const hereDocMatch = commandString.match(hereDocRegex);
|
||||
return hereDocMatch ? hereDocMatch[1] : '';
|
||||
}
|
||||
|
||||
server.listen(6002, () => {
|
||||
console.log('Server listening on port 6002');
|
||||
});
|
||||
@@ -37,6 +37,9 @@ RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
|
||||
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
||||
;fi"
|
||||
|
||||
COPY --from=minio/mc:RELEASE.2024-09-09T07-53-10Z /usr/bin/mc /usr/bin/mc
|
||||
RUN chmod +x /usr/bin/mc
|
||||
|
||||
RUN { \
|
||||
echo 'upload_max_filesize=256M'; \
|
||||
echo 'post_max_size=256M'; \
|
||||
|
||||
@@ -68,3 +68,6 @@ RUN { \
|
||||
echo 'upload_max_filesize=256M'; \
|
||||
echo 'post_max_size=256M'; \
|
||||
} > /etc/php/current_version/cli/conf.d/upload-limits.ini
|
||||
|
||||
COPY --from=minio/mc:RELEASE.2024-09-09T07-53-10Z /usr/bin/mc /usr/bin/mc
|
||||
RUN chmod +x /usr/bin/mc
|
||||
|
||||
420
openapi.yaml
420
openapi.yaml
@@ -13,7 +13,7 @@ paths:
|
||||
- Applications
|
||||
summary: List
|
||||
description: 'List all applications.'
|
||||
operationId: 02978e79fc0b54d573b2359f2a1f7d86
|
||||
operationId: list-applications
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get all applications.'
|
||||
@@ -36,7 +36,7 @@ paths:
|
||||
- Applications
|
||||
summary: 'Create (Public)'
|
||||
description: 'Create new application based on a public git repository.'
|
||||
operationId: cb56324ad19693469b4461d3f6065a5b
|
||||
operationId: create-public-application
|
||||
requestBody:
|
||||
description: 'Application object that needs to be created.'
|
||||
required: true
|
||||
@@ -253,7 +253,7 @@ paths:
|
||||
- Applications
|
||||
summary: 'Create (Private - GH App)'
|
||||
description: 'Create new application based on a private repository through a Github App.'
|
||||
operationId: 8b7af9c9a509385963bf3e72eeeea786
|
||||
operationId: create-private-github-app-application
|
||||
requestBody:
|
||||
description: 'Application object that needs to be created.'
|
||||
required: true
|
||||
@@ -474,7 +474,7 @@ paths:
|
||||
- Applications
|
||||
summary: 'Create (Private - Deploy Key)'
|
||||
description: 'Create new application based on a private repository through a Deploy Key.'
|
||||
operationId: e3eaa989ffb05366247a00cdfd551efa
|
||||
operationId: create-private-deploy-key-application
|
||||
requestBody:
|
||||
description: 'Application object that needs to be created.'
|
||||
required: true
|
||||
@@ -695,7 +695,7 @@ paths:
|
||||
- Applications
|
||||
summary: 'Create (Dockerfile)'
|
||||
description: 'Create new application based on a simple Dockerfile.'
|
||||
operationId: 2b433ad6f5d259eb7f4f3b5af9913708
|
||||
operationId: create-dockerfile-application
|
||||
requestBody:
|
||||
description: 'Application object that needs to be created.'
|
||||
required: true
|
||||
@@ -867,7 +867,7 @@ paths:
|
||||
- Applications
|
||||
summary: 'Create (Docker Image)'
|
||||
description: 'Create new application based on a prebuilt docker image'
|
||||
operationId: e9a2d6dd9404acf880dc3053f09477fc
|
||||
operationId: create-dockerimage-application
|
||||
requestBody:
|
||||
description: 'Application object that needs to be created.'
|
||||
required: true
|
||||
@@ -1030,7 +1030,7 @@ paths:
|
||||
- Applications
|
||||
summary: 'Create (Docker Compose)'
|
||||
description: 'Create new application based on a docker-compose file.'
|
||||
operationId: 3731add8226c2d664455978cac46c242
|
||||
operationId: create-dockercompose-application
|
||||
requestBody:
|
||||
description: 'Application object that needs to be created.'
|
||||
required: true
|
||||
@@ -1084,7 +1084,7 @@ paths:
|
||||
- Applications
|
||||
summary: Get
|
||||
description: 'Get application by UUID.'
|
||||
operationId: 3630b62c28e7358e7f0087c1d8fe1845
|
||||
operationId: get-application-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1115,7 +1115,7 @@ paths:
|
||||
- Applications
|
||||
summary: Delete
|
||||
description: 'Delete application by UUID.'
|
||||
operationId: 1e110b190a1045d34f3e1c61608a8702
|
||||
operationId: delete-application-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1156,7 +1156,7 @@ paths:
|
||||
- Applications
|
||||
summary: Update
|
||||
description: 'Update application by UUID.'
|
||||
operationId: 62a3b1775e8cba5d39a236ebb69830b7
|
||||
operationId: update-application-by-uuid
|
||||
requestBody:
|
||||
description: 'Application updated.'
|
||||
required: true
|
||||
@@ -1376,7 +1376,7 @@ paths:
|
||||
- Applications
|
||||
summary: 'List Envs'
|
||||
description: 'List all envs by application UUID.'
|
||||
operationId: 7c8e0c286870e23294a075cc0584df2f
|
||||
operationId: list-envs-by-application-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1409,7 +1409,7 @@ paths:
|
||||
- Applications
|
||||
summary: 'Create Env'
|
||||
description: 'Create env by application UUID.'
|
||||
operationId: 4699ffbb7d6e58581fd0b0a14f36ffc2
|
||||
operationId: create-env-by-application-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1471,7 +1471,7 @@ paths:
|
||||
- Applications
|
||||
summary: 'Update Env'
|
||||
description: 'Update env by application UUID.'
|
||||
operationId: 3d70a2d569f395be220b3f09ad36674b
|
||||
operationId: update-env-by-application-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1537,7 +1537,7 @@ paths:
|
||||
- Applications
|
||||
summary: 'Update Envs (Bulk)'
|
||||
description: 'Update multiple envs by application UUID.'
|
||||
operationId: ae96f0f585ed158b2abd2d9ba40f3cf9
|
||||
operationId: update-envs-by-application-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1584,7 +1584,7 @@ paths:
|
||||
- Applications
|
||||
summary: 'Delete Env'
|
||||
description: 'Delete env by UUID.'
|
||||
operationId: 96097c5cfc7dc0e7a3de229645f630c7
|
||||
operationId: delete-env-by-application-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1626,7 +1626,7 @@ paths:
|
||||
- Applications
|
||||
summary: Start
|
||||
description: 'Start application. `Post` request is also accepted.'
|
||||
operationId: dc87c2061ab303757a0e061f87900c4c
|
||||
operationId: start-application-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1675,7 +1675,7 @@ paths:
|
||||
- Applications
|
||||
summary: Stop
|
||||
description: 'Stop application. `Post` request is also accepted.'
|
||||
operationId: 133ef3c7bd5043901f24bb5002a536eb
|
||||
operationId: stop-application-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1709,7 +1709,7 @@ paths:
|
||||
- Applications
|
||||
summary: Restart
|
||||
description: 'Restart application. `Post` request is also accepted.'
|
||||
operationId: b231ae7baab9ef47f0627be820e735bc
|
||||
operationId: restart-application-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1744,7 +1744,7 @@ paths:
|
||||
- Databases
|
||||
summary: List
|
||||
description: 'List all databases.'
|
||||
operationId: ecd0ee1e46e4c854c18e6c9daa3d37f3
|
||||
operationId: list-databases
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get all databases'
|
||||
@@ -1766,7 +1766,7 @@ paths:
|
||||
- Databases
|
||||
summary: Get
|
||||
description: 'Get database by UUID.'
|
||||
operationId: b49cb2d3e8f34c4e80cdffd8a201031d
|
||||
operationId: get-database-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1798,7 +1798,7 @@ paths:
|
||||
- Databases
|
||||
summary: Delete
|
||||
description: 'Delete database by UUID.'
|
||||
operationId: 20610931b2bae8aba34eee68624ab673
|
||||
operationId: delete-database-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1839,7 +1839,7 @@ paths:
|
||||
- Databases
|
||||
summary: Update
|
||||
description: 'Update database by UUID.'
|
||||
operationId: 5ba459ed390a721711a1708760e9de3b
|
||||
operationId: update-database-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -1989,7 +1989,7 @@ paths:
|
||||
- Databases
|
||||
summary: 'Create (PostgreSQL)'
|
||||
description: 'Create a new PostgreSQL database.'
|
||||
operationId: 8f7f491ddc46a9fa065b4424512231cd
|
||||
operationId: create-database-postgresql
|
||||
requestBody:
|
||||
description: 'Database data'
|
||||
required: true
|
||||
@@ -2087,7 +2087,7 @@ paths:
|
||||
- Databases
|
||||
summary: 'Create (Clickhouse)'
|
||||
description: 'Create a new Clickhouse database.'
|
||||
operationId: a1189fa7f956f238f0e95c9150ff57f6
|
||||
operationId: create-database-clickhouse
|
||||
requestBody:
|
||||
description: 'Database data'
|
||||
required: true
|
||||
@@ -2173,7 +2173,7 @@ paths:
|
||||
- Databases
|
||||
summary: 'Create (DragonFly)'
|
||||
description: 'Create a new DragonFly database.'
|
||||
operationId: e73f7de1c8eee4219e5ec98c4b9b7efe
|
||||
operationId: create-database-dragonfly
|
||||
requestBody:
|
||||
description: 'Database data'
|
||||
required: true
|
||||
@@ -2256,7 +2256,7 @@ paths:
|
||||
- Databases
|
||||
summary: 'Create (Redis)'
|
||||
description: 'Create a new Redis database.'
|
||||
operationId: 4d352d13544ee2953fd48ad7b0651098
|
||||
operationId: create-database-redis
|
||||
requestBody:
|
||||
description: 'Database data'
|
||||
required: true
|
||||
@@ -2342,7 +2342,7 @@ paths:
|
||||
- Databases
|
||||
summary: 'Create (KeyDB)'
|
||||
description: 'Create a new KeyDB database.'
|
||||
operationId: b908f3929c371c217d489638e0a21ff6
|
||||
operationId: create-database-keydb
|
||||
requestBody:
|
||||
description: 'Database data'
|
||||
required: true
|
||||
@@ -2428,7 +2428,7 @@ paths:
|
||||
- Databases
|
||||
summary: 'Create (MariaDB)'
|
||||
description: 'Create a new MariaDB database.'
|
||||
operationId: 6bea521ddcd738dcbb5f3783a7308acf
|
||||
operationId: create-database-mariadb
|
||||
requestBody:
|
||||
description: 'Database data'
|
||||
required: true
|
||||
@@ -2523,7 +2523,7 @@ paths:
|
||||
- Databases
|
||||
summary: 'Create (MySQL)'
|
||||
description: 'Create a new MySQL database.'
|
||||
operationId: 0a1158cf759c4493cbb1e30024c60623
|
||||
operationId: create-database-mysql
|
||||
requestBody:
|
||||
description: 'Database data'
|
||||
required: true
|
||||
@@ -2615,7 +2615,7 @@ paths:
|
||||
- Databases
|
||||
summary: 'Create (MongoDB)'
|
||||
description: 'Create a new MongoDB database.'
|
||||
operationId: fdba3de84d02519bb37599fea34b115d
|
||||
operationId: create-database-mongodb
|
||||
requestBody:
|
||||
description: 'Database data'
|
||||
required: true
|
||||
@@ -2701,7 +2701,7 @@ paths:
|
||||
- Databases
|
||||
summary: Start
|
||||
description: 'Start database. `Post` request is also accepted.'
|
||||
operationId: 4c6eb21e734d411e2b3388578761123d
|
||||
operationId: start-database-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -2735,7 +2735,7 @@ paths:
|
||||
- Databases
|
||||
summary: Stop
|
||||
description: 'Stop database. `Post` request is also accepted.'
|
||||
operationId: cb6d983c2679aff841c7501ce612a372
|
||||
operationId: stop-database-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -2769,7 +2769,7 @@ paths:
|
||||
- Databases
|
||||
summary: Restart
|
||||
description: 'Restart database. `Post` request is also accepted.'
|
||||
operationId: 04c7a5e4752b4a00036addb433f3f218
|
||||
operationId: restart-database-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -2803,7 +2803,7 @@ paths:
|
||||
- Deployments
|
||||
summary: List
|
||||
description: 'List currently running deployments'
|
||||
operationId: a2c05736269191ad0d99cadfd4708986
|
||||
operationId: list-deployments
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get all currently running deployments.'
|
||||
@@ -2826,12 +2826,12 @@ paths:
|
||||
- Deployments
|
||||
summary: Get
|
||||
description: 'Get deployment by UUID.'
|
||||
operationId: ccf9856174c115a1430d952ccbd36aea
|
||||
operationId: get-deployment-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: 'Deployment Uuid'
|
||||
description: 'Deployment UUID'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
@@ -2857,7 +2857,7 @@ paths:
|
||||
- Deployments
|
||||
summary: Deploy
|
||||
description: 'Deploy by tag or uuid. `Post` request also accepted.'
|
||||
operationId: 700eb6e51f4c9e86d722f600c65ed1d4
|
||||
operationId: deploy-by-tag-or-uuid
|
||||
parameters:
|
||||
-
|
||||
name: tag
|
||||
@@ -2879,7 +2879,7 @@ paths:
|
||||
type: boolean
|
||||
responses:
|
||||
'200':
|
||||
description: "Get deployment(s) Uuid's"
|
||||
description: "Get deployment(s) UUID's"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -2897,7 +2897,7 @@ paths:
|
||||
get:
|
||||
summary: Version
|
||||
description: 'Get Coolify version.'
|
||||
operationId: 187b37139844731110757711ee71c215
|
||||
operationId: version
|
||||
responses:
|
||||
'200':
|
||||
description: 'Returns the version of the application'
|
||||
@@ -2917,7 +2917,7 @@ paths:
|
||||
get:
|
||||
summary: 'Enable API'
|
||||
description: 'Enable API (only with root permissions).'
|
||||
operationId: 595019bae03d08277def667609779ff3
|
||||
operationId: enable-api
|
||||
responses:
|
||||
'200':
|
||||
description: 'Enable API.'
|
||||
@@ -2946,7 +2946,7 @@ paths:
|
||||
get:
|
||||
summary: 'Disable API'
|
||||
description: 'Disable API (only with root permissions).'
|
||||
operationId: 50e2486a2d196a996b24a284a283bcdb
|
||||
operationId: disable-api
|
||||
responses:
|
||||
'200':
|
||||
description: 'Disable API.'
|
||||
@@ -2975,7 +2975,7 @@ paths:
|
||||
get:
|
||||
summary: Healthcheck
|
||||
description: 'Healthcheck endpoint.'
|
||||
operationId: 64db893135e686704bb88c3c238022c1
|
||||
operationId: healthcheck
|
||||
responses:
|
||||
'200':
|
||||
description: 'Healthcheck endpoint.'
|
||||
@@ -2993,8 +2993,8 @@ paths:
|
||||
tags:
|
||||
- Projects
|
||||
summary: List
|
||||
description: 'list projects.'
|
||||
operationId: 762788f00f2dabb981df9adbc948d3f6
|
||||
description: 'List projects.'
|
||||
operationId: list-projects
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get all projects.'
|
||||
@@ -3016,7 +3016,7 @@ paths:
|
||||
- Projects
|
||||
summary: Create
|
||||
description: 'Create Project.'
|
||||
operationId: cf067eb7cf18216cda3239329a2eeadb
|
||||
operationId: create-project
|
||||
requestBody:
|
||||
description: 'Project created.'
|
||||
required: true
|
||||
@@ -3024,7 +3024,7 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
uuid:
|
||||
name:
|
||||
type: string
|
||||
description: 'The name of the project.'
|
||||
description:
|
||||
@@ -3054,8 +3054,8 @@ paths:
|
||||
tags:
|
||||
- Projects
|
||||
summary: Get
|
||||
description: 'Get project by Uuid.'
|
||||
operationId: 63bf8b6a68fbb757f09ab519331f6298
|
||||
description: 'Get project by UUID.'
|
||||
operationId: get-project-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -3085,7 +3085,7 @@ paths:
|
||||
- Projects
|
||||
summary: Delete
|
||||
description: 'Delete project by UUID.'
|
||||
operationId: f668a936f505b4401948c74b6a663029
|
||||
operationId: delete-project-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -3118,7 +3118,7 @@ paths:
|
||||
- Projects
|
||||
summary: Update
|
||||
description: 'Update Project.'
|
||||
operationId: 2db343bd6fc14c658cb51a2b73b2f842
|
||||
operationId: update-project-by-uuid
|
||||
requestBody:
|
||||
description: 'Project updated.'
|
||||
required: true
|
||||
@@ -3159,7 +3159,7 @@ paths:
|
||||
- Projects
|
||||
summary: Environment
|
||||
description: 'Get environment by name.'
|
||||
operationId: 7e44845dce5aa47ed7b0daf5595ad2e1
|
||||
operationId: get-environment-by-name
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -3197,7 +3197,7 @@ paths:
|
||||
- Resources
|
||||
summary: List
|
||||
description: 'Get all resources.'
|
||||
operationId: c399903694eb1314596832e49f7c66d7
|
||||
operationId: list-resources
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get all resources'
|
||||
@@ -3219,7 +3219,7 @@ paths:
|
||||
- 'Private Keys'
|
||||
summary: List
|
||||
description: 'List all private keys.'
|
||||
operationId: 8a5d8d3ccbbcef54ed0e913a27faea9d
|
||||
operationId: list-private-keys
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get all private keys.'
|
||||
@@ -3241,7 +3241,7 @@ paths:
|
||||
- 'Private Keys'
|
||||
summary: Create
|
||||
description: 'Create a new private key.'
|
||||
operationId: eb4780acaa990c594cdbe8ffa80b4fb0
|
||||
operationId: create-private-key
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -3279,7 +3279,7 @@ paths:
|
||||
- 'Private Keys'
|
||||
summary: Update
|
||||
description: 'Update a private key.'
|
||||
operationId: 371fd26b8949a070c26a264231fe233f
|
||||
operationId: update-private-key
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -3318,12 +3318,12 @@ paths:
|
||||
- 'Private Keys'
|
||||
summary: Get
|
||||
description: 'Get key by UUID.'
|
||||
operationId: 2f743a85eb65d5ddb8cd5b362bb3d26a
|
||||
operationId: get-private-key-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: 'Private Key Uuid'
|
||||
description: 'Private Key UUID'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
@@ -3350,12 +3350,12 @@ paths:
|
||||
- 'Private Keys'
|
||||
summary: Delete
|
||||
description: 'Delete a private key.'
|
||||
operationId: 8faa0bb399142f0084dfc3e003c42cf6
|
||||
operationId: delete-private-key-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: 'Private Key Uuid'
|
||||
description: 'Private Key UUID'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
@@ -3383,7 +3383,7 @@ paths:
|
||||
- Servers
|
||||
summary: List
|
||||
description: 'List all servers.'
|
||||
operationId: 787448df856cefd2d9a313566be30d34
|
||||
operationId: list-servers
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get all servers.'
|
||||
@@ -3405,7 +3405,7 @@ paths:
|
||||
- Servers
|
||||
summary: Create
|
||||
description: 'Create Server.'
|
||||
operationId: fa44b42490379e428ba5b8747716a8d9
|
||||
operationId: create-server
|
||||
requestBody:
|
||||
description: 'Server created.'
|
||||
required: true
|
||||
@@ -3470,12 +3470,12 @@ paths:
|
||||
- Servers
|
||||
summary: Get
|
||||
description: 'Get server by UUID.'
|
||||
operationId: 5baf04bddb8302c7e07f5b4c41aad10c
|
||||
operationId: get-server-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: "Server's Uuid"
|
||||
description: "Server's UUID"
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
@@ -3500,7 +3500,7 @@ paths:
|
||||
- Servers
|
||||
summary: Delete
|
||||
description: 'Delete server by UUID.'
|
||||
operationId: 0231fe0134f0306b21f006ce51b0a3dc
|
||||
operationId: delete-server-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -3533,7 +3533,7 @@ paths:
|
||||
- Servers
|
||||
summary: Update
|
||||
description: 'Update Server.'
|
||||
operationId: 41bbdaf79eb1938592494fc5494442a0
|
||||
operationId: update-server-by-uuid
|
||||
requestBody:
|
||||
description: 'Server updated.'
|
||||
required: true
|
||||
@@ -3590,12 +3590,12 @@ paths:
|
||||
- Servers
|
||||
summary: Resources
|
||||
description: 'Get resources by server.'
|
||||
operationId: cef26c059941b44fbd8de3a7a58c10a5
|
||||
operationId: get-resources-by-server-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: "Server's Uuid"
|
||||
description: "Server's UUID"
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
@@ -3622,12 +3622,12 @@ paths:
|
||||
- Servers
|
||||
summary: Domains
|
||||
description: 'Get domains by server.'
|
||||
operationId: 1ee227755be848d572f412272f53dd93
|
||||
operationId: get-domains-by-server-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: "Server's Uuid"
|
||||
description: "Server's UUID"
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
@@ -3654,7 +3654,7 @@ paths:
|
||||
- Servers
|
||||
summary: Validate
|
||||
description: 'Validate server by UUID.'
|
||||
operationId: a543a12ef2cbc7a3dd22c3dbe6cbee89
|
||||
operationId: validate-server-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -3687,7 +3687,7 @@ paths:
|
||||
- Services
|
||||
summary: List
|
||||
description: 'List all services.'
|
||||
operationId: 5d014ac25d33391b8f4c2316060ba452
|
||||
operationId: list-services
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get all services'
|
||||
@@ -3709,7 +3709,7 @@ paths:
|
||||
- Services
|
||||
summary: Create
|
||||
description: 'Create a one-click service'
|
||||
operationId: 3d6cbfb54d919b53ba3984a113e837d7
|
||||
operationId: create-service
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -3773,7 +3773,7 @@ paths:
|
||||
- Services
|
||||
summary: Get
|
||||
description: 'Get service by UUID.'
|
||||
operationId: 895d39ee2cb3994285de57256c2d428d
|
||||
operationId: get-service-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -3784,7 +3784,7 @@ paths:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 'Get a service by Uuid.'
|
||||
description: 'Get a service by UUID.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -3803,7 +3803,7 @@ paths:
|
||||
- Services
|
||||
summary: Delete
|
||||
description: 'Delete service by UUID.'
|
||||
operationId: 6e1a61e4fddaa9d95bb9fc66dfaf0442
|
||||
operationId: delete-service-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -3814,7 +3814,7 @@ paths:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 'Delete a service by Uuid'
|
||||
description: 'Delete a service by UUID'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -3830,13 +3830,263 @@ paths:
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
'/services/{uuid}/envs':
|
||||
get:
|
||||
tags:
|
||||
- Services
|
||||
summary: 'List Envs'
|
||||
description: 'List all envs by service UUID.'
|
||||
operationId: list-envs-by-service-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: 'UUID of the service.'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'All environment variables by service UUID.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/EnvironmentVariable'
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'404':
|
||||
$ref: '#/components/responses/404'
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
post:
|
||||
tags:
|
||||
- Services
|
||||
summary: 'Create Env'
|
||||
description: 'Create env by service UUID.'
|
||||
operationId: create-env-by-service-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: 'UUID of the service.'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Env created.'
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
description: 'The key of the environment variable.'
|
||||
value:
|
||||
type: string
|
||||
description: 'The value of the environment variable.'
|
||||
is_preview:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the environment variable is used in preview deployments.'
|
||||
is_build_time:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the environment variable is used in build time.'
|
||||
is_literal:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the environment variable is a literal, nothing espaced.'
|
||||
is_multiline:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the environment variable is multiline.'
|
||||
is_shown_once:
|
||||
type: boolean
|
||||
description: "The flag to indicate if the environment variable's value is shown on the UI."
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
description: 'Environment variable created.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
uuid: { type: string, example: nc0k04gk8g0cgsk440g0koko }
|
||||
type: object
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'404':
|
||||
$ref: '#/components/responses/404'
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
patch:
|
||||
tags:
|
||||
- Services
|
||||
summary: 'Update Env'
|
||||
description: 'Update env by service UUID.'
|
||||
operationId: update-env-by-service-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: 'UUID of the service.'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Env updated.'
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- key
|
||||
- value
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
description: 'The key of the environment variable.'
|
||||
value:
|
||||
type: string
|
||||
description: 'The value of the environment variable.'
|
||||
is_preview:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the environment variable is used in preview deployments.'
|
||||
is_build_time:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the environment variable is used in build time.'
|
||||
is_literal:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the environment variable is a literal, nothing espaced.'
|
||||
is_multiline:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the environment variable is multiline.'
|
||||
is_shown_once:
|
||||
type: boolean
|
||||
description: "The flag to indicate if the environment variable's value is shown on the UI."
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
description: 'Environment variable updated.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Environment variable updated.' }
|
||||
type: object
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'404':
|
||||
$ref: '#/components/responses/404'
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
'/services/{uuid}/envs/bulk':
|
||||
patch:
|
||||
tags:
|
||||
- Services
|
||||
summary: 'Update Envs (Bulk)'
|
||||
description: 'Update multiple envs by service UUID.'
|
||||
operationId: update-envs-by-service-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: 'UUID of the service.'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
description: 'Bulk envs updated.'
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- data
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items: { properties: { key: { type: string, description: 'The key of the environment variable.' }, value: { type: string, description: 'The value of the environment variable.' }, is_preview: { type: boolean, description: 'The flag to indicate if the environment variable is used in preview deployments.' }, is_build_time: { type: boolean, description: 'The flag to indicate if the environment variable is used in build time.' }, is_literal: { type: boolean, description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' }, is_multiline: { type: boolean, description: 'The flag to indicate if the environment variable is multiline.' }, is_shown_once: { type: boolean, description: "The flag to indicate if the environment variable's value is shown on the UI." } }, type: object }
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
description: 'Environment variables updated.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Environment variables updated.' }
|
||||
type: object
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'404':
|
||||
$ref: '#/components/responses/404'
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
'/services/{uuid}/envs/{env_uuid}':
|
||||
delete:
|
||||
tags:
|
||||
- Services
|
||||
summary: 'Delete Env'
|
||||
description: 'Delete env by UUID.'
|
||||
operationId: delete-env-by-service-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: 'UUID of the service.'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
-
|
||||
name: env_uuid
|
||||
in: path
|
||||
description: 'UUID of the environment variable.'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: 'Environment variable deleted.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Environment variable deleted.' }
|
||||
type: object
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'404':
|
||||
$ref: '#/components/responses/404'
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
'/services/{uuid}/start':
|
||||
get:
|
||||
tags:
|
||||
- Services
|
||||
summary: Start
|
||||
description: 'Start service. `Post` request is also accepted.'
|
||||
operationId: d2ddd9c028d123fbdec830dc4b25b4cb
|
||||
operationId: start-service-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -3870,7 +4120,7 @@ paths:
|
||||
- Services
|
||||
summary: Stop
|
||||
description: 'Stop service. `Post` request is also accepted.'
|
||||
operationId: 87399d34758ce16830740c68626614db
|
||||
operationId: stop-service-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -3904,7 +4154,7 @@ paths:
|
||||
- Services
|
||||
summary: Restart
|
||||
description: 'Restart service. `Post` request is also accepted.'
|
||||
operationId: 836645faa615b75052759dae78639469
|
||||
operationId: restart-service-by-uuid
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
@@ -3938,7 +4188,7 @@ paths:
|
||||
- Teams
|
||||
summary: List
|
||||
description: 'Get all teams.'
|
||||
operationId: f9c530b5b25df9601cb87d6a58646f0a
|
||||
operationId: list-teams
|
||||
responses:
|
||||
'200':
|
||||
description: 'List of teams.'
|
||||
@@ -3961,7 +4211,7 @@ paths:
|
||||
- Teams
|
||||
summary: Get
|
||||
description: 'Get team by TeamId.'
|
||||
operationId: ac57ff546c002032cef44602c46a4e76
|
||||
operationId: get-team-by-id
|
||||
parameters:
|
||||
-
|
||||
name: id
|
||||
@@ -3992,7 +4242,7 @@ paths:
|
||||
- Teams
|
||||
summary: Members
|
||||
description: 'Get members by TeamId.'
|
||||
operationId: 7858f5a45d9ea55184c182852a7f0f6c
|
||||
operationId: get-members-by-team-id
|
||||
parameters:
|
||||
-
|
||||
name: id
|
||||
@@ -4025,7 +4275,7 @@ paths:
|
||||
- Teams
|
||||
summary: 'Authenticated Team'
|
||||
description: 'Get currently authenticated team.'
|
||||
operationId: 6a4ec9fed1aad7b0b38356c47d7ac509
|
||||
operationId: get-current-team
|
||||
responses:
|
||||
'200':
|
||||
description: 'Current Team.'
|
||||
@@ -4046,7 +4296,7 @@ paths:
|
||||
- Teams
|
||||
summary: 'Authenticated Team Members'
|
||||
description: 'Get currently authenticated team members.'
|
||||
operationId: 97e636a5796dbe71afb0bbcf1eec6e41
|
||||
operationId: get-current-team-members
|
||||
responses:
|
||||
'200':
|
||||
description: 'Currently authenticated team members.'
|
||||
@@ -4480,6 +4730,8 @@ components:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
environments:
|
||||
description: 'The environments of the project.'
|
||||
type: array
|
||||
|
||||
BIN
other/logos/glueops.webp
Normal file
BIN
other/logos/glueops.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
BIN
other/logos/juxtdigital.png
Normal file
BIN
other/logos/juxtdigital.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
20
other/logos/massivegrid.svg
Normal file
20
other/logos/massivegrid.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="236.333" height="33.468" viewBox="0 0 236.333 33.468">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1{fill:#ffb300}.cls-2{fill:#fff}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Group_448" transform="translate(-58.267 -97.906)">
|
||||
<path id="Path_1278" d="M65.043 128.721h-6.776v-21.977h5.265l.779 2.746a11.819 11.819 0 0 1 3.205-2.38 8.572 8.572 0 0 1 3.891-.871q4.716 0 6.455 3.343a12.009 12.009 0 0 1 3.32-2.427 8.83 8.83 0 0 1 3.96-.916 6.683 6.683 0 0 1 7.371 7.463v15.018h-6.776v-14.331a3.188 3.188 0 0 0-.732-2.2 2.507 2.507 0 0 0-1.969-.824 5.464 5.464 0 0 0-2.29.55 5.036 5.036 0 0 0-1.969 1.6v15.2H72v-14.326a3.188 3.188 0 0 0-.732-2.2 2.507 2.507 0 0 0-1.969-.824 4.829 4.829 0 0 0-2.29.642 5.886 5.886 0 0 0-1.969 1.693z" class="cls-1" transform="translate(0 2.15)"/>
|
||||
<path id="Path_1279" d="M88.567 108.667A18 18 0 0 1 92.8 106.9a17.737 17.737 0 0 1 4.831-.664 10.206 10.206 0 0 1 6.547 1.969q2.472 1.97 2.472 6.272v14.24h-5.173l-.6-2.173h-.177a6.671 6.671 0 0 1-2.541 1.927 9.076 9.076 0 0 1-3.823.749 8.916 8.916 0 0 1-3.273-.55 6.682 6.682 0 0 1-2.312-1.464 6.122 6.122 0 0 1-1.4-2.175 7.464 7.464 0 0 1-.481-2.678 6.149 6.149 0 0 1 .848-3.3 6.713 6.713 0 0 1 2.288-2.243 10.924 10.924 0 0 1 3.388-1.282 20 20 0 0 1 4.144-.411h2.29v-1.191a2.248 2.248 0 0 0-.871-1.9 4.011 4.011 0 0 0-2.472-.664 9.033 9.033 0 0 0-2.7.413 14.957 14.957 0 0 0-2.931 1.327zm11.308 10.576-1.511.045a9.971 9.971 0 0 0-2.151.278 4.732 4.732 0 0 0-1.4.577 2.043 2.043 0 0 0-.756.832 2.29 2.29 0 0 0-.229.993 2.134 2.134 0 0 0 .847 1.964 3.593 3.593 0 0 0 1.946.531 3.312 3.312 0 0 0 1.991-.647 3.768 3.768 0 0 0 1.259-1.433z" class="cls-1" transform="translate(7.381 2.15)"/>
|
||||
<path id="Path_1280" d="M118.836 112.375a13.63 13.63 0 0 0-2.609-.938 10.94 10.94 0 0 0-2.656-.343 3.914 3.914 0 0 0-1.924.411 1.269 1.269 0 0 0-.732 1.145 1.575 1.575 0 0 0 .71 1.306 9.708 9.708 0 0 0 2.815 1.121l1.374.413a8.88 8.88 0 0 1 4.167 2.4 5.828 5.828 0 0 1 1.419 4.051 6.515 6.515 0 0 1-.687 3 6.722 6.722 0 0 1-1.945 2.312 9.031 9.031 0 0 1-3.022 1.466 14.282 14.282 0 0 1-3.915.5 18.715 18.715 0 0 1-4.144-.413 14.845 14.845 0 0 1-3.365-1.19l1.466-4.9a16.229 16.229 0 0 0 2.77 1.122 10.186 10.186 0 0 0 2.953.434 4.407 4.407 0 0 0 2.449-.5 1.446 1.446 0 0 0 .71-1.19 1.474 1.474 0 0 0-.16-.71 1.69 1.69 0 0 0-.572-.572 5.015 5.015 0 0 0-1.167-.527q-.755-.251-1.946-.618l-1.419-.413a8.078 8.078 0 0 1-3.938-2.4 6.69 6.69 0 0 1 1.077-9.112 8.134 8.134 0 0 1 2.746-1.464 12.1 12.1 0 0 1 3.731-.527 18.629 18.629 0 0 1 3.915.39 15.257 15.257 0 0 1 3.273 1.076z" class="cls-1" transform="translate(11.86 2.15)"/>
|
||||
<path id="Path_1281" d="M133.176 112.375a13.641 13.641 0 0 0-2.61-.938 10.94 10.94 0 0 0-2.656-.343 3.908 3.908 0 0 0-1.922.411 1.266 1.266 0 0 0-.732 1.145 1.572 1.572 0 0 0 .71 1.306 9.708 9.708 0 0 0 2.815 1.121l1.374.413a8.882 8.882 0 0 1 4.165 2.4 5.829 5.829 0 0 1 1.42 4.051 6.515 6.515 0 0 1-.687 3 6.725 6.725 0 0 1-1.946 2.312 9.007 9.007 0 0 1-3.022 1.466 14.269 14.269 0 0 1-3.915.5 18.7 18.7 0 0 1-4.143-.413 14.8 14.8 0 0 1-3.365-1.19l1.466-4.9a16.216 16.216 0 0 0 2.769 1.122 10.2 10.2 0 0 0 2.954.434 4.41 4.41 0 0 0 2.449-.5 1.446 1.446 0 0 0 .71-1.19 1.474 1.474 0 0 0-.161-.71 1.685 1.685 0 0 0-.571-.572 5.04 5.04 0 0 0-1.167-.527q-.757-.251-1.946-.618l-1.419-.413a8.078 8.078 0 0 1-3.938-2.4 6.689 6.689 0 0 1 1.076-9.112 8.128 8.128 0 0 1 2.748-1.464 12.1 12.1 0 0 1 3.731-.527 18.617 18.617 0 0 1 3.914.39 15.211 15.211 0 0 1 3.273 1.076z" class="cls-1" transform="translate(15.56 2.15)"/>
|
||||
<path id="Path_1282" d="M137.571 104.591a3.448 3.448 0 0 1-2.656-.985 3.339 3.339 0 0 1-.915-2.358 3.293 3.293 0 0 1 .938-2.358 3.46 3.46 0 0 1 2.632-.985 3.51 3.51 0 0 1 2.633.962 3.274 3.274 0 0 1 .938 2.38 3.318 3.318 0 0 1-.916 2.38 3.5 3.5 0 0 1-2.654.964zm-3.388 26.28v-21.976h6.776v21.976z" class="cls-1" transform="translate(19.541)"/>
|
||||
<path id="Path_1283" d="M156.439 106.641h7.188l-7.693 21.976h-8.058l-7.325-21.976h7.234l2.656 9.614 1.511 7.646h.184l1.511-7.646z" class="cls-1" transform="translate(21.231 2.254)"/>
|
||||
<path id="Path_1284" d="M179.227 119.7h-14.24v.184a3.591 3.591 0 0 0 1.786 3.341 8.7 8.7 0 0 0 4.486 1.053 13.226 13.226 0 0 0 3.3-.366 11.2 11.2 0 0 0 2.38-.869l1.237 4.67a15.866 15.866 0 0 1-3.136 1.053 20.217 20.217 0 0 1-4.647.458 16.3 16.3 0 0 1-4.694-.664 11.015 11.015 0 0 1-3.891-2.037 9.781 9.781 0 0 1-2.656-3.48 11.806 11.806 0 0 1-.984-5.036 13.713 13.713 0 0 1 .8-4.786 10.828 10.828 0 0 1 2.271-3.731 10.069 10.069 0 0 1 3.511-2.4 11.674 11.674 0 0 1 4.519-.848 9.719 9.719 0 0 1 7.25 2.748q2.706 2.747 2.707 7.829zm-6.959-4.624a6.22 6.22 0 0 0-.184-1.511 4.068 4.068 0 0 0-.572-1.3 2.911 2.911 0 0 0-1.006-.916 3.026 3.026 0 0 0-1.488-.343 3.488 3.488 0 0 0-2.656 1.122 5 5 0 0 0-1.237 3.044z" class="cls-1" transform="translate(25.776 2.15)"/>
|
||||
<path id="Path_1285" d="M191.155 100.781a23.589 23.589 0 0 1 4.876.454 17.467 17.467 0 0 1 3.777 1.224l-1.419 4.549a13 13 0 0 0-3.273-1.122 16.923 16.923 0 0 0-3.32-.343 9.792 9.792 0 0 0-3.433.6 7.5 7.5 0 0 0-2.817 1.854 8.763 8.763 0 0 0-1.9 3.183 13.637 13.637 0 0 0-.687 4.578q0 4.853 2.129 7.44a7.465 7.465 0 0 0 6.113 2.587 14.871 14.871 0 0 0 1.74-.092 4.269 4.269 0 0 0 1.19-.274v-6.272H189.6v-5.128h11.218v14.833a21.548 21.548 0 0 1-4.212 1.283 27.009 27.009 0 0 1-5.448.5 18.853 18.853 0 0 1-6.113-.96 14.022 14.022 0 0 1-4.921-2.857 13.2 13.2 0 0 1-3.3-4.663 16 16 0 0 1-1.19-6.377 16.34 16.34 0 0 1 1.19-6.422 13.1 13.1 0 0 1 3.3-4.709 14.287 14.287 0 0 1 4.921-2.88 18.423 18.423 0 0 1 6.11-.986z" class="cls-2" transform="translate(30.283 .742)"/>
|
||||
<path id="Path_1286" d="M197.979 101.182h11.447a12.451 12.451 0 0 1 4.372.71 8.855 8.855 0 0 1 3.159 1.945 8.15 8.15 0 0 1 1.9 2.885 9.664 9.664 0 0 1 .64 3.526 8.374 8.374 0 0 1-1.877 5.494 8.179 8.179 0 0 1-1.785 1.648 12.184 12.184 0 0 1-1.786 1.008l6.914 11.629h-8.012l-5.128-10.394h-2.885v10.394h-6.959zm6.959 13.185h2.106a6.91 6.91 0 0 0 3.846-.961 3.446 3.446 0 0 0 1.466-3.114 3.606 3.606 0 0 0-1.19-2.839 5.037 5.037 0 0 0-3.434-1.053h-2.793z" class="cls-2" transform="translate(36.049 .845)"/>
|
||||
<path id="Path_1287" d="M224.336 101.182v28.844h-6.959v-28.844z" class="cls-2" transform="translate(41.054 .845)"/>
|
||||
<path id="Path_1288" d="M236.195 101.182a20.883 20.883 0 0 1 6.226.869 13.074 13.074 0 0 1 4.784 2.61 11.526 11.526 0 0 1 3.068 4.326 15.616 15.616 0 0 1 1.076 6.02 15.974 15.974 0 0 1-1.282 6.662 13.019 13.019 0 0 1-3.5 4.67 14.816 14.816 0 0 1-5.2 2.77 21.383 21.383 0 0 1-6.318.916h-9.157v-28.843zm-3.343 23.533h1.785a13.7 13.7 0 0 0 3.549-.458 7.351 7.351 0 0 0 3-1.58 7.944 7.944 0 0 0 2.061-3 12.252 12.252 0 0 0 .779-4.671 8.247 8.247 0 0 0-2.335-6.34 8.908 8.908 0 0 0-6.319-2.175h-2.517z" class="cls-2" transform="translate(43.251 .845)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user