mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-05 05:02:06 +00:00
Compare commits
129 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
3f3bf075fa | ||
|
|
6498e5b6f0 | ||
|
|
a3caad239c | ||
|
|
117fbeb07c | ||
|
|
33e9c9b0f9 | ||
|
|
2b8c9920d8 | ||
|
|
548fc21e40 | ||
|
|
c2ea8996ee |
@@ -6,13 +6,7 @@ APP_KEY=
|
|||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
APP_PORT=8000
|
APP_PORT=8000
|
||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
MUX_ENABLED=false
|
SSH_MUX_ENABLED=false
|
||||||
|
|
||||||
# Enable Laravel Telescope for debugging
|
|
||||||
TELESCOPE_ENABLED=false
|
|
||||||
|
|
||||||
# Selenium Driver URL for Dusk
|
|
||||||
DUSK_DRIVER_URL=http://selenium:4444
|
|
||||||
|
|
||||||
# PostgreSQL Database Configuration
|
# PostgreSQL Database Configuration
|
||||||
DB_DATABASE=coolify
|
DB_DATABASE=coolify
|
||||||
@@ -27,6 +21,16 @@ RAY_ENABLED=false
|
|||||||
# Set custom ray port
|
# Set custom ray port
|
||||||
RAY_PORT=
|
RAY_PORT=
|
||||||
|
|
||||||
|
# Clockwork Configuration
|
||||||
|
CLOCKWORK_ENABLED=false
|
||||||
|
CLOCKWORK_QUEUE_COLLECT=true
|
||||||
|
|
||||||
|
# Enable Laravel Telescope for debugging
|
||||||
|
TELESCOPE_ENABLED=false
|
||||||
|
|
||||||
|
# Selenium Driver URL for Dusk
|
||||||
|
DUSK_DRIVER_URL=http://selenium:4444
|
||||||
|
|
||||||
# Special Keys for Andras
|
# Special Keys for Andras
|
||||||
# For cache purging
|
# For cache purging
|
||||||
BUNNY_API_KEY=
|
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
|
name: 🐞 Bug Report
|
||||||
description: "Create a new bug report."
|
description: "File a new bug report."
|
||||||
title: "[Bug]: "
|
title: "[Bug]: "
|
||||||
|
labels: ["🐛 Bug", "🔍 Triage"]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: >-
|
value: |
|
||||||
# 💎 Bounty program (with
|
> [!IMPORTANT]
|
||||||
[algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
> **Please ensure you are using the latest version of Coolify before submitting an issue, as the bug may have already been fixed in a recent update.** (Of course, if you're experiencing an issue on the latest version that wasn't present in a previous version, please let us know.)
|
||||||
|
|
||||||
|
# 💎 Bounty Program (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
||||||
|
- If you would like to prioritize the issue resolution, consider adding a bounty to this issue through our [Bounty Program](https://console.algora.io/org/coollabsio/bounties/new).
|
||||||
|
|
||||||
If you would like to prioritize the issue resolution, you can add bounty
|
|
||||||
to this issue.
|
|
||||||
|
|
||||||
|
|
||||||
Click [here](https://console.algora.io/org/coollabsio/bounties/new) to
|
|
||||||
get started.
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Error Message and Logs
|
||||||
description: A clear and concise description of the problem
|
description: Provide a detailed description of the error or exception you encountered, along with any relevant log output.
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Minimal Reproduction (if possible, example repository)
|
|
||||||
description: Please provide a step by step guide to reproduce the issue.
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Exception or Error
|
label: Steps to Reproduce
|
||||||
description: Please provide error logs if possible.
|
description: Please provide a step-by-step guide to reproduce the issue. Be as detailed as possible, otherwise we may not be able to assist you.
|
||||||
|
value: |
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Example Repository URL
|
||||||
description: Coolify's version (see top of your screen).
|
description: If applicable, provide a URL to a repository demonstrating the issue.
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Coolify Version
|
||||||
|
description: Please provide the Coolify version you are using. This can be found in the top left corner of your Coolify dashboard.
|
||||||
|
placeholder: "v4.0.0-beta.335"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: checkboxes
|
|
||||||
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: Cloud?
|
label: Are you using Coolify Cloud?
|
||||||
description: "Are you using the cloud version of Coolify?"
|
|
||||||
options:
|
options:
|
||||||
- label: 'Yes'
|
- "No (self-hosted)"
|
||||||
required: false
|
- "Yes (Coolify Cloud)"
|
||||||
- label: 'No'
|
validations:
|
||||||
required: false
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Operating System and Version (self-hosted)
|
||||||
|
description: Run `cat /etc/os-release` or `lsb_release -a` in your terminal and provide the operating system and version.
|
||||||
|
placeholder: "Ubuntu 22.04"
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional Information
|
||||||
|
description: Any other relevant details about the issue.
|
||||||
|
|||||||
31
.github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml
vendored
Normal file
31
.github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: 💎 Enhancement Bounty
|
||||||
|
description: "Propose a new feature, service, or improvement with an attached bounty."
|
||||||
|
title: "[Enhancement]: "
|
||||||
|
labels: ["✨ Enhancement", "🔍 Triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **This issue template is exclusively for proposing new features, services, or improvements with an attached bounty.** Enhancements without a bounty can be discussed in the appropriate category of [Github Discussions](https://github.com/coollabsio/coolify/discussions).
|
||||||
|
|
||||||
|
# 💎 Add a Bounty (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
||||||
|
- [Click here to add the required bounty](https://console.algora.io/org/coollabsio/bounties/new)
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Request Type
|
||||||
|
description: Select the type of request you are making.
|
||||||
|
options:
|
||||||
|
- New Feature
|
||||||
|
- New Service
|
||||||
|
- Improvement
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: Provide a detailed description of the feature, improvement, or service you are proposing.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
20
.github/ISSUE_TEMPLATE/config.yml
vendored
20
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,18 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
|
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: 🤔 Community Support (Chat)
|
- name: 🤔 Questions and Community Support
|
||||||
url: https://coollabs.io/discord
|
url: https://coollabs.io/discord
|
||||||
about: Reach out to us on Discord.
|
about: If you have any questions, reach out to us on Discord inside the "#support" channel.
|
||||||
- name: 🙋♂️ Feature Requests
|
|
||||||
url: https://github.com/coollabsio/coolify/discussions/categories/new-features
|
- name: 💡 Feature Request
|
||||||
about: All feature requests will be discussed here.
|
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests
|
||||||
|
about: Suggest a new feature for Coolify.
|
||||||
|
|
||||||
|
- name: ⚙️ Service Request
|
||||||
|
url: https://github.com/coollabsio/coolify/discussions/categories/service-requests
|
||||||
|
about: Request a new service integration for Coolify.
|
||||||
|
|
||||||
|
- name: 🔧 Improvements
|
||||||
|
url: https://github.com/coollabsio/coolify/discussions/categories/improvements
|
||||||
|
about: Suggest improvements to existing features for Coolify.
|
||||||
|
|||||||
2
.github/workflows/coolify-helper.yml
vendored
2
.github/workflows/coolify-helper.yml
vendored
@@ -2,7 +2,7 @@ name: Coolify Helper Image (v4)
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "next" ]
|
branches: [ "main" ]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/coolify-helper.yml
|
- .github/workflows/coolify-helper.yml
|
||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
|
|||||||
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:
|
jobs:
|
||||||
amd64:
|
amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
@@ -37,6 +43,9 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
@@ -58,6 +67,9 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
actions: write
|
||||||
needs: [amd64, aarch64]
|
needs: [amd64, aarch64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
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)
|
> "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.
|
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:
|
Follow the steps below for your operating system:
|
||||||
|
|
||||||
### Windows
|
<details>
|
||||||
|
<summary><strong>Windows</strong></summary>
|
||||||
|
|
||||||
1. Install `docker-ce`, Docker Desktop (or similar):
|
1. Install `docker-ce`, Docker Desktop (or similar):
|
||||||
- Docker CE (recommended):
|
- Docker CE (recommended):
|
||||||
@@ -25,7 +35,10 @@ Follow the steps below for your operating system:
|
|||||||
2. Install Spin:
|
2. Install Spin:
|
||||||
- Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2)
|
- Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2)
|
||||||
|
|
||||||
### MacOS
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>MacOS</strong></summary>
|
||||||
|
|
||||||
1. Install Orbstack, Docker Desktop (or similar):
|
1. Install Orbstack, Docker Desktop (or similar):
|
||||||
- Orbstack (recommended, as it is a faster and lighter alternative to Docker Desktop):
|
- Orbstack (recommended, as it is a faster and lighter alternative to Docker Desktop):
|
||||||
@@ -36,7 +49,10 @@ Follow the steps below for your operating system:
|
|||||||
2. Install Spin:
|
2. Install Spin:
|
||||||
- Follow the instructions to install Spin on MacOS from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-macos/#download-and-install-spin)
|
- Follow the instructions to install Spin on MacOS from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-macos/#download-and-install-spin)
|
||||||
|
|
||||||
### Linux
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Linux</strong></summary>
|
||||||
|
|
||||||
1. Install Docker Engine, Docker Desktop (or similar):
|
1. Install Docker Engine, Docker Desktop (or similar):
|
||||||
- Docker Engine (recommended, as there is no VM overhead):
|
- Docker Engine (recommended, as there is no VM overhead):
|
||||||
@@ -47,8 +63,9 @@ Follow the steps below for your operating system:
|
|||||||
2. Install Spin:
|
2. Install Spin:
|
||||||
- Follow the instructions to install Spin on Linux from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-linux#configure-docker-permissions)
|
- Follow the instructions to install Spin on Linux from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-linux#configure-docker-permissions)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## 2. Verify installation (optional)
|
## 2. Verify Installation (Optional)
|
||||||
|
|
||||||
After installing Docker (or Orbstack) and Spin, verify the installation:
|
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.
|
You should see version information for both Docker and Spin.
|
||||||
|
|
||||||
|
## 3. Fork and Setup Local Repository
|
||||||
## 3. Fork the Coolify repository and setup your local repository
|
|
||||||
|
|
||||||
1. Fork the [Coolify](https://github.com/coollabsio/coolify) repository to your GitHub account.
|
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):
|
| Editor | Platform | Download Link |
|
||||||
- Windows/macOS/Linux: Download and install from [https://code.visualstudio.com/download](https://code.visualstudio.com/download)
|
|--------|----------|---------------|
|
||||||
|
| Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download) |
|
||||||
- Cursor (recommended but paid for getting the full benefits):
|
| Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/) |
|
||||||
- Windows/macOS/Linux: Download and install from [https://www.cursor.com/](https://www.cursor.com/)
|
| Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download) |
|
||||||
|
|
||||||
- Zed (very fast code editor):
|
|
||||||
- macOS/Linux: Download and install from [https://zed.dev/download](https://zed.dev/download)
|
|
||||||
- Windows: Not available yet
|
|
||||||
|
|
||||||
3. Clone the Coolify Repository from your fork to your local machine
|
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):
|
- Use GitHub Desktop (recommended):
|
||||||
- Download and install from [https://desktop.github.com/](https://desktop.github.com/)
|
- Download and install from [https://desktop.github.com/](https://desktop.github.com/)
|
||||||
- Open GitHub Desktop and login with your GitHub account
|
- 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. Open the cloned Coolify Repository in your chosen code editor.
|
||||||
|
|
||||||
|
|
||||||
## 4. Set up Environment Variables
|
## 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.
|
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`.
|
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.
|
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`.
|
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. Save the changes to your `.env` file.
|
||||||
|
|
||||||
|
|
||||||
## 5. Start Coolify
|
## 5. Start Coolify
|
||||||
|
|
||||||
1. Open a terminal in the local Coolify directory.
|
1. Open a terminal in the local Coolify directory.
|
||||||
|
|
||||||
2. Run the following command in the terminal (leave that terminal open):
|
2. Run the following command in the terminal (leave that terminal open):
|
||||||
```
|
```bash
|
||||||
spin up
|
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:
|
3. If you encounter permission errors, especially on macOS, use:
|
||||||
```
|
```bash
|
||||||
sudo spin up
|
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
|
## 6. Start Development
|
||||||
|
|
||||||
@@ -126,15 +133,17 @@ Note: If you change environment variables afterwards or anything seems broken, p
|
|||||||
- Password: `password`
|
- Password: `password`
|
||||||
|
|
||||||
2. Additional development tools:
|
2. Additional development tools:
|
||||||
- Laravel Horizon (scheduler): `http://localhost:8000/horizon`
|
| Tool | URL | Note |
|
||||||
Note: Only accessible when logged in as root user
|
|------|-----|------|
|
||||||
- Mailpit (email catcher): `http://localhost:8025`
|
| Laravel Horizon (scheduler) | `http://localhost:8000/horizon` | Only accessible when logged in as root user |
|
||||||
- Telescope (debugging tool): `http://localhost:8000/telescope`
|
| Mailpit (email catcher) | `http://localhost:8025` | |
|
||||||
Note: Disabled by default (so the database is not overloaded), enable by adding the following environment variable to your `.env` file:
|
| Telescope (debugging tool) | `http://localhost:8000/telescope` | Disabled by default |
|
||||||
```env
|
|
||||||
TELESCOPE_ENABLED=true
|
|
||||||
```
|
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> To enable Telescope, add the following to your `.env` file:
|
||||||
|
> ```env
|
||||||
|
> TELESCOPE_ENABLED=true
|
||||||
|
> ```
|
||||||
|
|
||||||
## 7. Development Notes
|
## 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
|
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. Create a Pull Request
|
||||||
## 8. Contributing a New Service
|
|
||||||
|
|
||||||
To add a new service to Coolify, please refer to our documentation:
|
|
||||||
[Adding a New Service](https://coolify.io/docs/knowledge-base/add-a-service)
|
|
||||||
|
|
||||||
|
|
||||||
## 9. Create a Pull Request
|
|
||||||
|
|
||||||
1. After making changes or adding a new service:
|
1. After making changes or adding a new service:
|
||||||
- Commit your changes to your forked repository.
|
- 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.
|
- In the description, explain the changes you've made.
|
||||||
- Reference any related issues by using keywords like "Fixes #123" or "Closes #456".
|
- Reference any related issues by using keywords like "Fixes #123" or "Closes #456".
|
||||||
|
|
||||||
4. Important note:
|
> [!IMPORTANT]
|
||||||
Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
|
> 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.
|
- Review your changes one last time.
|
||||||
- Click "Create pull request" to submit.
|
- 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.
|
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)
|
||||||
|
|||||||
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;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Jobs\PullHelperImageJob;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -55,6 +56,13 @@ class UpdateCoolify
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$all_servers = Server::all();
|
||||||
|
$servers = $all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
PullHelperImageJob::dispatch($server);
|
||||||
|
}
|
||||||
|
|
||||||
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
|
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
|
||||||
|
|
||||||
remote_process([
|
remote_process([
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class StartService
|
|||||||
$service->saveComposeConfigs();
|
$service->saveComposeConfigs();
|
||||||
$commands[] = 'cd '.$service->workdir();
|
$commands[] = 'cd '.$service->workdir();
|
||||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||||
if($service->networks()->count() > 0){
|
if ($service->networks()->count() > 0) {
|
||||||
$commands[] = "echo 'Creating Docker network.'";
|
$commands[] = "echo 'Creating Docker network.'";
|
||||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ class StartService
|
|||||||
$network = $service->destination->network;
|
$network = $service->destination->network;
|
||||||
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
||||||
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
||||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
|
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} >/dev/null 2>&1 || true";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
'ignore_errors' => true,
|
'ignore_errors' => true,
|
||||||
], [
|
], [
|
||||||
"docker network connect {$networkId} coolify-proxy || true",
|
"docker network connect {$networkId} coolify-proxy >/dev/null 2>&1 || true",
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
'ignore_errors' => true,
|
'ignore_errors' => true,
|
||||||
]);
|
]);
|
||||||
@@ -919,10 +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->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -978,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->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2049,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"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
@@ -2068,6 +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"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
@@ -2110,6 +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"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
@@ -2129,6 +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"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
@@ -2157,6 +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"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
@@ -2176,6 +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"),
|
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
|
||||||
|
'hidden' => true,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
|
||||||
'hidden' => true,
|
'hidden' => true,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Jobs;
|
|||||||
|
|
||||||
use App\Actions\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
use App\Events\BackupCreated;
|
use App\Events\BackupCreated;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\S3Storage;
|
use App\Models\S3Storage;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
use App\Models\ScheduledDatabaseBackupExecution;
|
||||||
@@ -25,7 +26,7 @@ use Illuminate\Queue\InteractsWithQueue;
|
|||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\Models\InstanceSettings;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -399,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";
|
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
||||||
|
|
||||||
$commands[] = $backupCommand;
|
$commands[] = $backupCommand;
|
||||||
|
ray($commands);
|
||||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
$this->backup_output = trim($this->backup_output);
|
$this->backup_output = trim($this->backup_output);
|
||||||
if ($this->backup_output === '') {
|
if ($this->backup_output === '') {
|
||||||
@@ -477,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
|
private function upload_to_s3(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -518,7 +548,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
$imageExists = $this->checkImageExists($fullImageName);
|
$imageExists = $this->checkImageExists($fullImageName);
|
||||||
|
|
||||||
if (!$imageExists) {
|
if (! $imageExists) {
|
||||||
$this->pullHelperImage($fullImageName);
|
$this->pullHelperImage($fullImageName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -526,6 +556,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
private function checkImageExists(string $fullImageName): bool
|
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);
|
$result = instant_remote_process(["docker image inspect {$fullImageName} >/dev/null 2>&1 && echo 'exists' || echo 'not exists'"], $this->server, false);
|
||||||
|
|
||||||
return trim($result) === 'exists';
|
return trim($result) === 'exists';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,7 +565,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
try {
|
try {
|
||||||
instant_remote_process(["docker pull {$fullImageName}"], $this->server);
|
instant_remote_process(["docker pull {$fullImageName}"], $this->server);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$errorMessage = "Failed to pull helper image: " . $e->getMessage();
|
$errorMessage = 'Failed to pull helper image: '.$e->getMessage();
|
||||||
$this->add_to_backup_output($errorMessage);
|
$this->add_to_backup_output($errorMessage);
|
||||||
throw new \RuntimeException($errorMessage);
|
throw new \RuntimeException($errorMessage);
|
||||||
}
|
}
|
||||||
@@ -545,6 +576,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
$helperImage = config('coolify.helper_image');
|
$helperImage = config('coolify.helper_image');
|
||||||
$latestVersion = $settings->helper_version;
|
$latestVersion = $settings->helper_version;
|
||||||
|
|
||||||
return "{$helperImage}:{$latestVersion}";
|
return "{$helperImage}:{$latestVersion}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
public function validateServer()
|
public function validateServer()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
config()->set('coolify.mux_enabled', false);
|
config()->set('constants.ssh.mux_enabled', false);
|
||||||
|
|
||||||
// EC2 does not have `uptime` command, lol
|
// EC2 does not have `uptime` command, lol
|
||||||
instant_remote_process(['ls /'], $this->createdServer, true);
|
instant_remote_process(['ls /'], $this->createdServer, true);
|
||||||
|
|||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,8 @@ class Navbar extends Component
|
|||||||
|
|
||||||
public $isDeploymentProgress = false;
|
public $isDeploymentProgress = false;
|
||||||
|
|
||||||
|
public $title = 'Configuration';
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
|
if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
|
||||||
|
|||||||
@@ -2,18 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project\Shared;
|
namespace App\Livewire\Project\Shared;
|
||||||
|
|
||||||
use App\Actions\Server\RunCommand;
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Livewire\Attributes\On;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class ExecuteContainerCommand extends Component
|
class ExecuteContainerCommand extends Component
|
||||||
{
|
{
|
||||||
public string $command;
|
public $container;
|
||||||
|
|
||||||
public string $container;
|
|
||||||
|
|
||||||
public Collection $containers;
|
public Collection $containers;
|
||||||
|
|
||||||
@@ -23,8 +21,6 @@ class ExecuteContainerCommand extends Component
|
|||||||
|
|
||||||
public string $type;
|
public string $type;
|
||||||
|
|
||||||
public string $workDir = '';
|
|
||||||
|
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
public Collection $servers;
|
public Collection $servers;
|
||||||
@@ -33,11 +29,13 @@ class ExecuteContainerCommand extends Component
|
|||||||
'server' => 'required',
|
'server' => 'required',
|
||||||
'container' => 'required',
|
'container' => 'required',
|
||||||
'command' => 'required',
|
'command' => 'required',
|
||||||
'workDir' => 'nullable',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
if (! auth()->user()->isAdmin()) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
$this->containers = collect();
|
$this->containers = collect();
|
||||||
$this->servers = collect();
|
$this->servers = collect();
|
||||||
@@ -62,24 +60,13 @@ class ExecuteContainerCommand extends Component
|
|||||||
if ($this->resource->destination->server->isFunctional()) {
|
if ($this->resource->destination->server->isFunctional()) {
|
||||||
$this->servers = $this->servers->push($this->resource->destination->server);
|
$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')) {
|
} elseif (data_get($this->parameters, 'service_uuid')) {
|
||||||
$this->type = 'service';
|
$this->type = 'service';
|
||||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
$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()) {
|
if ($this->resource->server->isFunctional()) {
|
||||||
$this->servers = $this->servers->push($this->resource->server);
|
$this->servers = $this->servers->push($this->resource->server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->containers->count() > 0) {
|
|
||||||
$this->container = $this->containers->first();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadContainers()
|
public function loadContainers()
|
||||||
@@ -102,44 +89,65 @@ class ExecuteContainerCommand extends Component
|
|||||||
];
|
];
|
||||||
$this->containers = $this->containers->push($payload);
|
$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 ($this->containers->count() > 0) {
|
||||||
if (data_get($this->parameters, 'application_uuid')) {
|
$this->container = $this->containers->first();
|
||||||
$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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function runCommand()
|
#[On('connectToContainer')]
|
||||||
|
public function connectToContainer()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (data_get($this->parameters, 'application_uuid')) {
|
$container_name = data_get($this->container, 'container.Names');
|
||||||
$container = $this->containers->where('container.Names', $this->container)->first();
|
if (is_null($container_name)) {
|
||||||
$container_name = data_get($container, 'container.Names');
|
throw new \RuntimeException('Container not found.');
|
||||||
if (is_null($container)) {
|
|
||||||
throw new \RuntimeException('Container not found.');
|
|
||||||
}
|
|
||||||
$server = data_get($container, 'server');
|
|
||||||
} else {
|
|
||||||
$container_name = $this->container;
|
|
||||||
$server = $this->servers->first();
|
|
||||||
}
|
}
|
||||||
|
$server = data_get($this->container, 'server');
|
||||||
|
|
||||||
if ($server->isForceDisabled()) {
|
if ($server->isForceDisabled()) {
|
||||||
throw new \RuntimeException('Server is disabled.');
|
throw new \RuntimeException('Server is disabled.');
|
||||||
}
|
}
|
||||||
$cmd = "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; ".str_replace("'", "'\''", $this->command)."'";
|
|
||||||
if (! empty($this->workDir)) {
|
$this->dispatch('send-terminal-command',
|
||||||
$exec = "docker exec -w {$this->workDir} {$container_name} {$cmd}";
|
true,
|
||||||
} else {
|
$container_name,
|
||||||
$exec = "docker exec {$container_name} {$cmd}";
|
$server->uuid,
|
||||||
}
|
);
|
||||||
$activity = RunCommand::run(server: $server, command: $exec);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
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;
|
public $timezones;
|
||||||
|
|
||||||
protected $listeners = ['serverInstalled', 'revalidate' => '$refresh'];
|
protected $listeners = [
|
||||||
|
'serverInstalled',
|
||||||
|
'refreshServerShow' => 'serverInstalled',
|
||||||
|
'revalidate' => '$refresh',
|
||||||
|
];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'server.name' => 'required',
|
'server.name' => 'required',
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Server\New;
|
namespace App\Livewire\Server\New;
|
||||||
|
|
||||||
use App\Enums\ProxyStatus;
|
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class ByIp extends Component
|
class ByIp extends Component
|
||||||
@@ -40,7 +40,7 @@ class ByIp extends Component
|
|||||||
|
|
||||||
public bool $is_build_server = false;
|
public bool $is_build_server = false;
|
||||||
|
|
||||||
public $swarm_managers = [];
|
public Collection $swarm_managers;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'name' => 'required|string',
|
'name' => 'required|string',
|
||||||
@@ -102,11 +102,6 @@ class ByIp extends Component
|
|||||||
'port' => $this->port,
|
'port' => $this->port,
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
'private_key_id' => $this->private_key_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) {
|
if ($this->is_swarm_worker) {
|
||||||
$payload['swarm_cluster'] = $this->selected_swarm_cluster;
|
$payload['swarm_cluster'] = $this->selected_swarm_cluster;
|
||||||
@@ -115,6 +110,9 @@ class ByIp extends Component
|
|||||||
data_forget($payload, 'proxy');
|
data_forget($payload, 'proxy');
|
||||||
}
|
}
|
||||||
$server = Server::create($payload);
|
$server = Server::create($payload);
|
||||||
|
$server->proxy->set('status', 'exited');
|
||||||
|
$server->proxy->set('type', ProxyTypes::TRAEFIK->value);
|
||||||
|
$server->save();
|
||||||
if ($this->is_build_server) {
|
if ($this->is_build_server) {
|
||||||
$this->is_swarm_manager = false;
|
$this->is_swarm_manager = false;
|
||||||
$this->is_swarm_worker = false;
|
$this->is_swarm_worker = false;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class Show extends Component
|
|||||||
|
|
||||||
public $parameters = [];
|
public $parameters = [];
|
||||||
|
|
||||||
protected $listeners = ['refreshServerShow' => '$refresh'];
|
protected $listeners = ['refreshServerShow'];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
@@ -29,6 +29,12 @@ class Show extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function refreshServerShow()
|
||||||
|
{
|
||||||
|
$this->server->refresh();
|
||||||
|
$this->dispatch('$refresh');
|
||||||
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->dispatch('serverRefresh', false);
|
$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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -305,6 +305,13 @@ respond 404
|
|||||||
'service' => 'coolify-realtime',
|
'service' => 'coolify-realtime',
|
||||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||||
],
|
],
|
||||||
|
'coolify-terminal-ws' => [
|
||||||
|
'entryPoints' => [
|
||||||
|
0 => 'http',
|
||||||
|
],
|
||||||
|
'service' => 'coolify-terminal',
|
||||||
|
'rule' => "Host(`{$host}`) && PathPrefix(`/terminal/ws`)",
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'services' => [
|
'services' => [
|
||||||
'coolify' => [
|
'coolify' => [
|
||||||
@@ -325,6 +332,15 @@ respond 404
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'coolify-terminal' => [
|
||||||
|
'loadBalancer' => [
|
||||||
|
'servers' => [
|
||||||
|
0 => [
|
||||||
|
'url' => 'http://coolify-realtime:6002',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -354,6 +370,16 @@ respond 404
|
|||||||
'certresolver' => 'letsencrypt',
|
'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 = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||||
$yaml =
|
$yaml =
|
||||||
@@ -387,6 +413,9 @@ $schema://$host {
|
|||||||
handle /app/* {
|
handle /app/* {
|
||||||
reverse_proxy coolify-realtime:6001
|
reverse_proxy coolify-realtime:6001
|
||||||
}
|
}
|
||||||
|
handle /terminal/ws/* {
|
||||||
|
reverse_proxy coolify-realtime:6002
|
||||||
|
}
|
||||||
reverse_proxy coolify:80
|
reverse_proxy coolify:80
|
||||||
}";
|
}";
|
||||||
$base64 = base64_encode($caddy_file);
|
$base64 = base64_encode($caddy_file);
|
||||||
@@ -746,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
|
public function loadUnmanagedContainers(): Collection
|
||||||
{
|
{
|
||||||
if ($this->isFunctional()) {
|
if ($this->isFunctional()) {
|
||||||
@@ -967,7 +1008,7 @@ $schema://$host {
|
|||||||
|
|
||||||
public function validateConnection()
|
public function validateConnection()
|
||||||
{
|
{
|
||||||
config()->set('coolify.mux_enabled', false);
|
config()->set('constants.ssh.mux_enabled', false);
|
||||||
|
|
||||||
$server = Server::find($this->id);
|
$server = Server::find($this->id);
|
||||||
if (! $server) {
|
if (! $server) {
|
||||||
|
|||||||
@@ -667,7 +667,7 @@ class Service extends BaseModel
|
|||||||
}
|
}
|
||||||
$data = $data->merge([
|
$data = $data->merge([
|
||||||
'Root User' => [
|
'Root User' => [
|
||||||
'key' => 'N/A',
|
'key' => 'GITLAB_ROOT_USER',
|
||||||
'value' => 'root',
|
'value' => 'root',
|
||||||
'rules' => 'required',
|
'rules' => 'required',
|
||||||
'isPassword' => true,
|
'isPassword' => true,
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ class ServiceApplication extends BaseModel
|
|||||||
return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
|
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()
|
public function isLogDrainEnabled()
|
||||||
{
|
{
|
||||||
return data_get($this, 'is_log_drain_enabled', false);
|
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);
|
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()
|
public function isLogDrainEnabled()
|
||||||
{
|
{
|
||||||
return data_get($this, 'is_log_drain_enabled', false);
|
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()
|
public function isExited()
|
||||||
{
|
{
|
||||||
return (bool) str($this->status)->startsWith('exited');
|
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()
|
public function isExited()
|
||||||
{
|
{
|
||||||
return (bool) str($this->status)->startsWith('exited');
|
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()
|
public function isExited()
|
||||||
{
|
{
|
||||||
return (bool) str($this->status)->startsWith('exited');
|
return (bool) str($this->status)->startsWith('exited');
|
||||||
@@ -209,7 +214,7 @@ class StandaloneKeydb extends BaseModel
|
|||||||
protected function internalDbUrl(): Attribute
|
protected function internalDbUrl(): Attribute
|
||||||
{
|
{
|
||||||
return new 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(
|
return new Attribute(
|
||||||
get: function () {
|
get: function () {
|
||||||
if ($this->is_public && $this->public_port) {
|
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;
|
return null;
|
||||||
|
|||||||
@@ -75,6 +75,11 @@ class StandaloneMariadb extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isRunning()
|
||||||
|
{
|
||||||
|
return (bool) str($this->status)->contains('running');
|
||||||
|
}
|
||||||
|
|
||||||
public function isExited()
|
public function isExited()
|
||||||
{
|
{
|
||||||
return (bool) str($this->status)->startsWith('exited');
|
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()
|
public function isExited()
|
||||||
{
|
{
|
||||||
return (bool) str($this->status)->startsWith('exited');
|
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()
|
public function isExited()
|
||||||
{
|
{
|
||||||
return (bool) str($this->status)->startsWith('exited');
|
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()
|
public function isExited()
|
||||||
{
|
{
|
||||||
return (bool) str($this->status)->startsWith('exited');
|
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()
|
public function isExited()
|
||||||
{
|
{
|
||||||
return (bool) str($this->status)->startsWith('exited');
|
return (bool) str($this->status)->startsWith('exited');
|
||||||
|
|||||||
@@ -40,6 +40,20 @@ function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pul
|
|||||||
return $containers;
|
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
|
function format_docker_command_output_to_json($rawOutput): Collection
|
||||||
{
|
{
|
||||||
$outputLines = explode(PHP_EOL, $rawOutput);
|
$outputLines = explode(PHP_EOL, $rawOutput);
|
||||||
@@ -215,12 +229,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
|||||||
}
|
}
|
||||||
if (is_null($MINIO_BROWSER_REDIRECT_URL?->value)) {
|
if (is_null($MINIO_BROWSER_REDIRECT_URL?->value)) {
|
||||||
$MINIO_BROWSER_REDIRECT_URL?->update([
|
$MINIO_BROWSER_REDIRECT_URL?->update([
|
||||||
'value' => generateFqdn($server, 'console-'.$uuid),
|
'value' => generateFqdn($server, 'console-'.$uuid, true),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (is_null($MINIO_SERVER_URL?->value)) {
|
if (is_null($MINIO_SERVER_URL?->value)) {
|
||||||
$MINIO_SERVER_URL?->update([
|
$MINIO_SERVER_URL?->update([
|
||||||
'value' => generateFqdn($server, 'minio-'.$uuid),
|
'value' => generateFqdn($server, 'minio-'.$uuid, true),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$payload = collect([
|
$payload = collect([
|
||||||
|
|||||||
@@ -98,12 +98,10 @@ function generateScpCommand(Server $server, string $source, string $dest)
|
|||||||
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||||
|
|
||||||
$scp_command = "timeout $timeout scp ";
|
$scp_command = "timeout $timeout scp ";
|
||||||
// Check if multiplexing is enabled
|
$muxEnabled = config('constants.ssh.mux_enabled', true) && config('coolify.is_windows_docker_desktop') == false;
|
||||||
$muxEnabled = config('constants.ssh.mux_enabled', true);
|
|
||||||
// ray('SSH Multiplexing Enabled:', $muxEnabled)->blue();
|
// ray('SSH Multiplexing Enabled:', $muxEnabled)->blue();
|
||||||
|
|
||||||
if ($muxEnabled) {
|
if ($muxEnabled) {
|
||||||
// Always use multiplexing when enabled
|
|
||||||
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
|
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
|
||||||
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||||
ensureMultiplexedConnection($server);
|
ensureMultiplexedConnection($server);
|
||||||
@@ -163,10 +161,8 @@ function generateSshCommand(Server $server, string $command)
|
|||||||
|
|
||||||
$ssh_command = "timeout $timeout ssh ";
|
$ssh_command = "timeout $timeout ssh ";
|
||||||
|
|
||||||
// Check if multiplexing is enabled
|
$muxEnabled = config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false;
|
||||||
$muxEnabled = config('constants.ssh.mux_enabled', true);
|
|
||||||
// ray('SSH Multiplexing Enabled:', $muxEnabled)->blue();
|
// ray('SSH Multiplexing Enabled:', $muxEnabled)->blue();
|
||||||
|
|
||||||
if ($muxEnabled) {
|
if ($muxEnabled) {
|
||||||
// Always use multiplexing when enabled
|
// Always use multiplexing when enabled
|
||||||
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
|
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
|
||||||
@@ -201,6 +197,10 @@ function generateSshCommand(Server $server, string $command)
|
|||||||
|
|
||||||
function ensureMultiplexedConnection(Server $server)
|
function ensureMultiplexedConnection(Server $server)
|
||||||
{
|
{
|
||||||
|
if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static $ensuredConnections = [];
|
static $ensuredConnections = [];
|
||||||
|
|
||||||
if (isset($ensuredConnections[$server->id])) {
|
if (isset($ensuredConnections[$server->id])) {
|
||||||
@@ -212,7 +212,11 @@ function ensureMultiplexedConnection(Server $server)
|
|||||||
}
|
}
|
||||||
|
|
||||||
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
|
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}";
|
||||||
$checkCommand = "ssh -O check -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null";
|
$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);
|
$process = Process::run($checkCommand);
|
||||||
|
|
||||||
@@ -233,8 +237,12 @@ function ensureMultiplexedConnection(Server $server)
|
|||||||
$serverInterval = config('constants.ssh.server_interval');
|
$serverInterval = config('constants.ssh.server_interval');
|
||||||
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
$muxPersistTime = config('constants.ssh.mux_persist_time');
|
||||||
|
|
||||||
$establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "
|
$establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
|
||||||
."-i {$privateKeyLocation} "
|
|
||||||
|
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 StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
||||||
.'-o PasswordAuthentication=no '
|
.'-o PasswordAuthentication=no '
|
||||||
."-o ConnectTimeout=$connectionTimeout "
|
."-o ConnectTimeout=$connectionTimeout "
|
||||||
@@ -260,6 +268,10 @@ function ensureMultiplexedConnection(Server $server)
|
|||||||
|
|
||||||
function shouldResetMultiplexedConnection(Server $server)
|
function shouldResetMultiplexedConnection(Server $server)
|
||||||
{
|
{
|
||||||
|
if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static $ensuredConnections = [];
|
static $ensuredConnections = [];
|
||||||
|
|
||||||
if (! isset($ensuredConnections[$server->id])) {
|
if (! isset($ensuredConnections[$server->id])) {
|
||||||
@@ -275,6 +287,10 @@ function shouldResetMultiplexedConnection(Server $server)
|
|||||||
|
|
||||||
function resetMultiplexedConnection(Server $server)
|
function resetMultiplexedConnection(Server $server)
|
||||||
{
|
{
|
||||||
|
if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static $ensuredConnections = [];
|
static $ensuredConnections = [];
|
||||||
|
|
||||||
if (isset($ensuredConnections[$server->id])) {
|
if (isset($ensuredConnections[$server->id])) {
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ function data_get_str($data, $key, $default = null): Stringable
|
|||||||
return str($str);
|
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');
|
$wildcard = data_get($server, 'settings.wildcard_domain');
|
||||||
if (is_null($wildcard) || $wildcard === '') {
|
if (is_null($wildcard) || $wildcard === '') {
|
||||||
@@ -488,6 +488,9 @@ function generateFqdn(Server $server, string $random): string
|
|||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
$path = $url->getPath() === '/' ? '' : $url->getPath();
|
$path = $url->getPath() === '/' ? '' : $url->getPath();
|
||||||
$scheme = $url->getScheme();
|
$scheme = $url->getScheme();
|
||||||
|
if ($forceHttps) {
|
||||||
|
$scheme = 'https';
|
||||||
|
}
|
||||||
$finalFqdn = "$scheme://{$random}.$host$path";
|
$finalFqdn = "$scheme://{$random}.$host$path";
|
||||||
|
|
||||||
return $finalFqdn;
|
return $finalFqdn;
|
||||||
@@ -786,7 +789,7 @@ function replaceLocalSource(Stringable $source, Stringable $replacedWith)
|
|||||||
if ($source->startsWith('..')) {
|
if ($source->startsWith('..')) {
|
||||||
$source = $source->replaceFirst('..', $replacedWith->value());
|
$source = $source->replaceFirst('..', $replacedWith->value());
|
||||||
}
|
}
|
||||||
if ($source->endsWith('/')) {
|
if ($source->endsWith('/') && $source->value() !== '/') {
|
||||||
$source = $source->replaceLast('/', '');
|
$source = $source->replaceLast('/', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2100,16 +2103,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
|
|
||||||
// TODO: move this in a shared function
|
// TODO: move this in a shared function
|
||||||
if (! $parsedServiceVariables->has('COOLIFY_APP_NAME')) {
|
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')) {
|
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')) {
|
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')) {
|
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) {
|
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
|
||||||
@@ -3229,7 +3232,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
if ($isApplication && $isPullRequest) {
|
if ($isApplication && $isPullRequest) {
|
||||||
$source = $source."-pr-$pullRequestId";
|
$source = $source."-pr-$pullRequestId";
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalFileVolume::updateOrCreate(
|
LocalFileVolume::updateOrCreate(
|
||||||
[
|
[
|
||||||
'mount_path' => $target,
|
'mount_path' => $target,
|
||||||
@@ -3469,13 +3471,13 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
$branch = "pull/{$pullRequestId}/head";
|
$branch = "pull/{$pullRequestId}/head";
|
||||||
}
|
}
|
||||||
if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
$coolifyEnvironments->put('COOLIFY_BRANCH', $branch);
|
$coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add COOLIFY_CONTAINER_NAME to environment
|
// Add COOLIFY_CONTAINER_NAME to environment
|
||||||
if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
$coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', $containerName);
|
$coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "\"{$containerName}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isApplication) {
|
if ($isApplication) {
|
||||||
@@ -3548,7 +3550,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
if ($isApplication) {
|
if ($isApplication) {
|
||||||
$shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels;
|
$shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels;
|
||||||
$uuid = $resource->uuid;
|
$uuid = $resource->uuid;
|
||||||
$network = $resource->destination->network;
|
$network = data_get($resource, 'destination.network');
|
||||||
if ($isPullRequest) {
|
if ($isPullRequest) {
|
||||||
$uuid = "{$resource->uuid}-{$pullRequestId}";
|
$uuid = "{$resource->uuid}-{$pullRequestId}";
|
||||||
}
|
}
|
||||||
@@ -3558,7 +3560,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
} else {
|
} else {
|
||||||
$shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels;
|
$shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels;
|
||||||
$uuid = $resource->uuid;
|
$uuid = $resource->uuid;
|
||||||
$network = $resource->destination->network;
|
$network = data_get($resource, 'destination.network');
|
||||||
}
|
}
|
||||||
if ($shouldGenerateLabelsExactly) {
|
if ($shouldGenerateLabelsExactly) {
|
||||||
switch ($server->proxyType()) {
|
switch ($server->proxyType()) {
|
||||||
@@ -3723,30 +3725,30 @@ function add_coolify_default_environment_variables(StandaloneRedis|StandalonePos
|
|||||||
}
|
}
|
||||||
if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_APP_NAME')->isEmpty()) {
|
if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_APP_NAME')->isEmpty()) {
|
||||||
if ($isAssociativeArray) {
|
if ($isAssociativeArray) {
|
||||||
$where_to_add->put('COOLIFY_APP_NAME', $resource->name);
|
$where_to_add->put('COOLIFY_APP_NAME', "\"{$resource->name}\"");
|
||||||
} else {
|
} 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 ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_SERVER_IP')->isEmpty()) {
|
||||||
if ($isAssociativeArray) {
|
if ($isAssociativeArray) {
|
||||||
$where_to_add->put('COOLIFY_SERVER_IP', $ip);
|
$where_to_add->put('COOLIFY_SERVER_IP', "\"{$ip}\"");
|
||||||
} else {
|
} 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 ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_ENVIRONMENT_NAME')->isEmpty()) {
|
||||||
if ($isAssociativeArray) {
|
if ($isAssociativeArray) {
|
||||||
$where_to_add->put('COOLIFY_ENVIRONMENT_NAME', $resource->environment->name);
|
$where_to_add->put('COOLIFY_ENVIRONMENT_NAME', "\"{$resource->environment->name}\"");
|
||||||
} else {
|
} 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 ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_PROJECT_NAME')->isEmpty()) {
|
||||||
if ($isAssociativeArray) {
|
if ($isAssociativeArray) {
|
||||||
$where_to_add->put('COOLIFY_PROJECT_NAME', $resource->project()->name);
|
$where_to_add->put('COOLIFY_PROJECT_NAME', "\"{$resource->project()->name}\"");
|
||||||
} else {
|
} else {
|
||||||
$where_to_add->push("COOLIFY_PROJECT_NAME={$resource->project()->name}");
|
$where_to_add->push("COOLIFY_PROJECT_NAME=\"{$resource->project()->name}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,4 +110,4 @@
|
|||||||
},
|
},
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"prefer-stable": true
|
"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,8 @@ return [
|
|||||||
'contact' => 'https://coolify.io/docs/contact',
|
'contact' => 'https://coolify.io/docs/contact',
|
||||||
],
|
],
|
||||||
'ssh' => [
|
'ssh' => [
|
||||||
'mux_enabled' => env('SSH_MUX_ENABLED', true),
|
// Using MUX
|
||||||
|
'mux_enabled' => env('MUX_ENABLED', env('SSH_MUX_ENABLED', true), true),
|
||||||
'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1h'),
|
'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1h'),
|
||||||
'connection_timeout' => 10,
|
'connection_timeout' => 10,
|
||||||
'server_interval' => 20,
|
'server_interval' => 20,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ return [
|
|||||||
'self_hosted' => env('SELF_HOSTED', true),
|
'self_hosted' => env('SELF_HOSTED', true),
|
||||||
'waitlist' => env('WAITLIST', false),
|
'waitlist' => env('WAITLIST', false),
|
||||||
'license_url' => 'https://licenses.coollabs.io',
|
'license_url' => 'https://licenses.coollabs.io',
|
||||||
'mux_enabled' => env('MUX_ENABLED', true),
|
|
||||||
'dev_webhook' => env('SERVEO_URL'),
|
'dev_webhook' => env('SERVEO_URL'),
|
||||||
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
|
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
|
||||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.333',
|
'release' => '4.0.0-beta.339',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.333';
|
return '4.0.0-beta.339';
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ services:
|
|||||||
PUSHER_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
PUSHER_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/var/www/html/:cached
|
- .:/var/www/html/:cached
|
||||||
|
- /data/coolify/backups/:/var/www/html/storage/app/backups
|
||||||
postgres:
|
postgres:
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
ports:
|
ports:
|
||||||
@@ -43,10 +44,17 @@ services:
|
|||||||
- /data/coolify/_volumes/redis/:/data
|
- /data/coolify/_volumes/redis/:/data
|
||||||
# - coolify-redis-data-dev:/data
|
# - coolify-redis-data-dev:/data
|
||||||
soketi:
|
soketi:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./docker/coolify-realtime/Dockerfile
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
ports:
|
ports:
|
||||||
- "${FORWARD_SOKETI_PORT:-6001}:6001"
|
- "${FORWARD_SOKETI_PORT:-6001}:6001"
|
||||||
|
- "6002:6002"
|
||||||
|
volumes:
|
||||||
|
- ./storage:/var/www/html/storage
|
||||||
|
- ./docker/coolify-realtime/terminal-server.js:/terminal/terminal-server.js
|
||||||
environment:
|
environment:
|
||||||
SOKETI_DEBUG: "false"
|
SOKETI_DEBUG: "false"
|
||||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ services:
|
|||||||
- PUSHER_APP_SECRET
|
- PUSHER_APP_SECRET
|
||||||
- AUTOUPDATE
|
- AUTOUPDATE
|
||||||
- SELF_HOSTED
|
- SELF_HOSTED
|
||||||
|
- SSH_MUX_ENABLED
|
||||||
- SSH_MUX_PERSIST_TIME
|
- SSH_MUX_PERSIST_TIME
|
||||||
- FEEDBACK_DISCORD_WEBHOOK
|
- FEEDBACK_DISCORD_WEBHOOK
|
||||||
- WAITLIST
|
- WAITLIST
|
||||||
@@ -109,18 +110,24 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
|
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.1'
|
||||||
ports:
|
ports:
|
||||||
- "${SOKETI_PORT:-6001}:6001"
|
- "${SOKETI_PORT:-6001}:6001"
|
||||||
|
- "6002:6002"
|
||||||
|
volumes:
|
||||||
|
- /data/coolify/ssh:/var/www/html/storage/app/ssh
|
||||||
environment:
|
environment:
|
||||||
|
APP_NAME: "${APP_NAME:-Coolify}"
|
||||||
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
|
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
|
||||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
|
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
|
||||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
||||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
||||||
healthcheck:
|
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
|
interval: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
coolify-db:
|
coolify-db:
|
||||||
name: coolify-db
|
name: coolify-db
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ services:
|
|||||||
- PUSHER_APP_SECRET
|
- PUSHER_APP_SECRET
|
||||||
- AUTOUPDATE=true
|
- AUTOUPDATE=true
|
||||||
- SELF_HOSTED=true
|
- SELF_HOSTED=true
|
||||||
- MUX_ENABLED=false
|
- SSH_MUX_ENABLED=false
|
||||||
- IS_WINDOWS_DOCKER_DESKTOP=true
|
- IS_WINDOWS_DOCKER_DESKTOP=true
|
||||||
ports:
|
ports:
|
||||||
- "${APP_PORT:-8000}:80"
|
- "${APP_PORT:-8000}:80"
|
||||||
@@ -103,7 +103,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
image: 'quay.io/soketi/soketi:1.6-16-alpine'
|
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.0'
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
container_name: coolify-realtime
|
container_name: coolify-realtime
|
||||||
restart: always
|
restart: always
|
||||||
@@ -111,16 +111,21 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
ports:
|
ports:
|
||||||
- "${SOKETI_PORT:-6001}:6001"
|
- "${SOKETI_PORT:-6001}:6001"
|
||||||
|
- "6002:6002"
|
||||||
|
volumes:
|
||||||
|
- ./ssh:/var/www/html/storage/app/ssh
|
||||||
environment:
|
environment:
|
||||||
|
APP_NAME: "${APP_NAME:-Coolify}"
|
||||||
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
|
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
|
||||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
|
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
|
||||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
||||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
||||||
healthcheck:
|
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
|
interval: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
coolify-db:
|
coolify-db:
|
||||||
name: coolify-db
|
name: coolify-db
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- coolify
|
- coolify
|
||||||
soketi:
|
soketi:
|
||||||
image: 'quay.io/soketi/soketi:1.6-16-alpine'
|
|
||||||
container_name: coolify-realtime
|
container_name: coolify-realtime
|
||||||
|
extra_hosts:
|
||||||
|
- 'host.docker.internal:host-gateway'
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
- coolify
|
- coolify
|
||||||
|
|||||||
@@ -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 \
|
chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack /root/.docker/cli-plugins/docker-buildx \
|
||||||
;fi
|
;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
|
RUN chmod +x /usr/bin/mc
|
||||||
|
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
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 \
|
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"
|
;fi"
|
||||||
|
|
||||||
|
COPY --from=minio/mc:RELEASE.2024-09-09T07-53-10Z /usr/bin/mc /usr/bin/mc
|
||||||
|
RUN chmod +x /usr/bin/mc
|
||||||
|
|
||||||
RUN { \
|
RUN { \
|
||||||
echo 'upload_max_filesize=256M'; \
|
echo 'upload_max_filesize=256M'; \
|
||||||
echo 'post_max_size=256M'; \
|
echo 'post_max_size=256M'; \
|
||||||
|
|||||||
@@ -68,3 +68,6 @@ RUN { \
|
|||||||
echo 'upload_max_filesize=256M'; \
|
echo 'upload_max_filesize=256M'; \
|
||||||
echo 'post_max_size=256M'; \
|
echo 'post_max_size=256M'; \
|
||||||
} > /etc/php/current_version/cli/conf.d/upload-limits.ini
|
} > /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
|
||||||
|
|||||||
@@ -6,13 +6,7 @@ APP_KEY=
|
|||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
APP_PORT=8000
|
APP_PORT=8000
|
||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
MUX_ENABLED=false
|
SSH_MUX_ENABLED=false
|
||||||
|
|
||||||
# Enable Laravel Telescope for debugging
|
|
||||||
TELESCOPE_ENABLED=false
|
|
||||||
|
|
||||||
# Selenium Driver URL for Dusk
|
|
||||||
DUSK_DRIVER_URL=http://selenium:4444
|
|
||||||
|
|
||||||
# PostgreSQL Database Configuration
|
# PostgreSQL Database Configuration
|
||||||
DB_DATABASE=coolify
|
DB_DATABASE=coolify
|
||||||
@@ -21,9 +15,22 @@ DB_PASSWORD=password
|
|||||||
DB_HOST=host.docker.internal
|
DB_HOST=host.docker.internal
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
|
|
||||||
#Set custom ray port
|
# Ray Configuration
|
||||||
|
# Set to true to enable Ray
|
||||||
|
RAY_ENABLED=false
|
||||||
|
# Set custom ray port
|
||||||
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
|
# Special Keys for Andras
|
||||||
# For cache purging
|
# For cache purging
|
||||||
BUNNY_API_KEY=
|
BUNNY_API_KEY=
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ services:
|
|||||||
- PUSHER_APP_SECRET
|
- PUSHER_APP_SECRET
|
||||||
- AUTOUPDATE
|
- AUTOUPDATE
|
||||||
- SELF_HOSTED
|
- SELF_HOSTED
|
||||||
|
- SSH_MUX_ENABLED
|
||||||
- SSH_MUX_PERSIST_TIME
|
- SSH_MUX_PERSIST_TIME
|
||||||
- FEEDBACK_DISCORD_WEBHOOK
|
- FEEDBACK_DISCORD_WEBHOOK
|
||||||
- WAITLIST
|
- WAITLIST
|
||||||
@@ -109,18 +110,24 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
|
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.1'
|
||||||
ports:
|
ports:
|
||||||
- "${SOKETI_PORT:-6001}:6001"
|
- "${SOKETI_PORT:-6001}:6001"
|
||||||
|
- "6002:6002"
|
||||||
|
volumes:
|
||||||
|
- /data/coolify/ssh:/var/www/html/storage/app/ssh
|
||||||
environment:
|
environment:
|
||||||
|
APP_NAME: "${APP_NAME:-Coolify}"
|
||||||
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
|
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
|
||||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
|
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
|
||||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
||||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
||||||
healthcheck:
|
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
|
interval: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
coolify-db:
|
coolify-db:
|
||||||
name: coolify-db
|
name: coolify-db
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ services:
|
|||||||
- PUSHER_APP_SECRET
|
- PUSHER_APP_SECRET
|
||||||
- AUTOUPDATE=true
|
- AUTOUPDATE=true
|
||||||
- SELF_HOSTED=true
|
- SELF_HOSTED=true
|
||||||
- MUX_ENABLED=false
|
- SSH_MUX_ENABLED=false
|
||||||
- IS_WINDOWS_DOCKER_DESKTOP=true
|
- IS_WINDOWS_DOCKER_DESKTOP=true
|
||||||
ports:
|
ports:
|
||||||
- "${APP_PORT:-8000}:80"
|
- "${APP_PORT:-8000}:80"
|
||||||
@@ -103,7 +103,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
image: 'quay.io/soketi/soketi:1.6-16-alpine'
|
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.0'
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
container_name: coolify-realtime
|
container_name: coolify-realtime
|
||||||
restart: always
|
restart: always
|
||||||
@@ -111,16 +111,21 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
ports:
|
ports:
|
||||||
- "${SOKETI_PORT:-6001}:6001"
|
- "${SOKETI_PORT:-6001}:6001"
|
||||||
|
- "6002:6002"
|
||||||
|
volumes:
|
||||||
|
- ./ssh:/var/www/html/storage/app/ssh
|
||||||
environment:
|
environment:
|
||||||
|
APP_NAME: "${APP_NAME:-Coolify}"
|
||||||
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
|
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
|
||||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
|
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
|
||||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
||||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
||||||
healthcheck:
|
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
|
interval: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
coolify-db:
|
coolify-db:
|
||||||
name: coolify-db
|
name: coolify-db
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- coolify
|
- coolify
|
||||||
soketi:
|
soketi:
|
||||||
image: 'quay.io/soketi/soketi:1.6-16-alpine'
|
|
||||||
container_name: coolify-realtime
|
container_name: coolify-realtime
|
||||||
|
extra_hosts:
|
||||||
|
- 'host.docker.internal:host-gateway'
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
- coolify
|
- coolify
|
||||||
|
|||||||
@@ -5,11 +5,30 @@ set -e # Exit immediately if a command exits with a non-zero status
|
|||||||
## $1 could be empty, so we need to disable this check
|
## $1 could be empty, so we need to disable this check
|
||||||
#set -u # Treat unset variables as an error and exit
|
#set -u # Treat unset variables as an error and exit
|
||||||
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
|
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
|
||||||
|
CDN="https://cdn.coollabs.io/coolify-nightly"
|
||||||
|
DATE=$(date +"%Y%m%d-%H%M%S")
|
||||||
|
|
||||||
VERSION="1.4"
|
VERSION="1.5"
|
||||||
DOCKER_VERSION="26.0"
|
DOCKER_VERSION="26.0"
|
||||||
|
|
||||||
CDN="https://cdn.coollabs.io/coolify-nightly"
|
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs}
|
||||||
|
mkdir -p /data/coolify/ssh/{keys,mux}
|
||||||
|
mkdir -p /data/coolify/proxy/dynamic
|
||||||
|
|
||||||
|
chown -R 9999:root /data/coolify
|
||||||
|
chmod -R 700 /data/coolify
|
||||||
|
|
||||||
|
INSTALLATION_LOG_WITH_DATE="/data/coolify/source/installation-${DATE}.log"
|
||||||
|
|
||||||
|
exec > >(tee -a $INSTALLATION_LOG_WITH_DATE) 2>&1
|
||||||
|
|
||||||
|
getAJoke() {
|
||||||
|
JOKES=$(curl -s --max-time 2 https://v2.jokeapi.dev/joke/Programming?format=txt&type=single&amount=1 || true)
|
||||||
|
if [ "$JOKES" != "" ]; then
|
||||||
|
echo -e " - Until then, here's a joke for you:\n"
|
||||||
|
echo -e "$JOKES\n"
|
||||||
|
fi
|
||||||
|
}
|
||||||
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
|
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
|
||||||
ENV_FILE="/data/coolify/source/.env"
|
ENV_FILE="/data/coolify/source/.env"
|
||||||
|
|
||||||
@@ -46,12 +65,16 @@ fi
|
|||||||
|
|
||||||
LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $2}' | tr -d ',')
|
LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $2}' | tr -d ',')
|
||||||
LATEST_HELPER_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $6}' | tr -d ',')
|
LATEST_HELPER_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $6}' | tr -d ',')
|
||||||
|
LATEST_REALTIME_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $8}' | tr -d ',')
|
||||||
|
|
||||||
if [ -z "$LATEST_HELPER_VERSION" ]; then
|
if [ -z "$LATEST_HELPER_VERSION" ]; then
|
||||||
LATEST_HELPER_VERSION=latest
|
LATEST_HELPER_VERSION=latest
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DATE=$(date +"%Y%m%d-%H%M%S")
|
if [ -z "$LATEST_REALTIME_VERSION" ]; then
|
||||||
|
LATEST_REALTIME_VERSION=latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
if [ $EUID != 0 ]; then
|
if [ $EUID != 0 ]; then
|
||||||
echo "Please run as root"
|
echo "Please run as root"
|
||||||
@@ -73,18 +96,29 @@ if [ "$1" != "" ]; then
|
|||||||
LATEST_VERSION="${LATEST_VERSION#v}"
|
LATEST_VERSION="${LATEST_VERSION#v}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "-------------"
|
echo -e "\033[0;35m"
|
||||||
echo -e "Welcome to Coolify v4 beta installer!"
|
cat << "EOF"
|
||||||
echo -e "This script will install everything for you."
|
_____ _ _ __
|
||||||
|
/ ____| | (_)/ _|
|
||||||
|
| | ___ ___ | |_| |_ _ _
|
||||||
|
| | / _ \ / _ \| | | _| | | |
|
||||||
|
| |___| (_) | (_) | | | | | |_| |
|
||||||
|
\_____\___/ \___/|_|_|_| \__, |
|
||||||
|
__/ |
|
||||||
|
|___/
|
||||||
|
EOF
|
||||||
|
echo -e "\033[0m"
|
||||||
|
echo -e "Welcome to Coolify Installer!"
|
||||||
|
echo -e "This script will install everything for you. Sit back and relax."
|
||||||
echo -e "Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh\n"
|
echo -e "Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh\n"
|
||||||
echo -e "-------------"
|
echo -e "---------------------------------------------"
|
||||||
|
echo "| Operating System | $OS_TYPE $OS_VERSION"
|
||||||
echo "OS: $OS_TYPE $OS_VERSION"
|
echo "| Docker | $DOCKER_VERSION"
|
||||||
echo "Coolify version: $LATEST_VERSION"
|
echo "| Coolify | $LATEST_VERSION"
|
||||||
echo "Helper version: $LATEST_HELPER_VERSION"
|
echo "| Helper | $LATEST_HELPER_VERSION"
|
||||||
|
echo "| Realtime | $LATEST_REALTIME_VERSION"
|
||||||
echo -e "-------------"
|
echo -e "---------------------------------------------\n"
|
||||||
echo "Installing required packages..."
|
echo -e "1. Installing required packages (curl, wget, git, jq). "
|
||||||
|
|
||||||
case "$OS_TYPE" in
|
case "$OS_TYPE" in
|
||||||
arch)
|
arch)
|
||||||
@@ -122,24 +156,26 @@ sles | opensuse-leap | opensuse-tumbleweed)
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
echo -e "2. Check OpenSSH server configuration. "
|
||||||
|
|
||||||
# Detect OpenSSH server
|
# Detect OpenSSH server
|
||||||
SSH_DETECTED=false
|
SSH_DETECTED=false
|
||||||
if [ -x "$(command -v systemctl)" ]; then
|
if [ -x "$(command -v systemctl)" ]; then
|
||||||
if systemctl status sshd >/dev/null 2>&1; then
|
if systemctl status sshd >/dev/null 2>&1; then
|
||||||
echo "OpenSSH server is installed."
|
echo " - OpenSSH server is installed."
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
fi
|
elif systemctl status ssh >/dev/null 2>&1; then
|
||||||
if systemctl status ssh >/dev/null 2>&1; then
|
echo " - OpenSSH server is installed."
|
||||||
echo "OpenSSH server is installed."
|
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
fi
|
fi
|
||||||
elif [ -x "$(command -v service)" ]; then
|
elif [ -x "$(command -v service)" ]; then
|
||||||
if service sshd status >/dev/null 2>&1; then
|
if service sshd status >/dev/null 2>&1; then
|
||||||
echo "OpenSSH server is installed."
|
echo " - OpenSSH server is installed."
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
fi
|
elif service ssh status >/dev/null 2>&1; then
|
||||||
if service ssh status >/dev/null 2>&1; then
|
echo " - OpenSSH server is installed."
|
||||||
echo "OpenSSH server is installed."
|
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -151,104 +187,91 @@ if [ "$SSH_DETECTED" = "false" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect SSH PermitRootLogin
|
# Detect SSH PermitRootLogin
|
||||||
SSH_PERMIT_ROOT_LOGIN=false
|
SSH_PERMIT_ROOT_LOGIN=$(sshd -T | grep -i "permitrootlogin" | awk '{print $2}') || true
|
||||||
SSH_PERMIT_ROOT_LOGIN_CONFIG=$(grep "^PermitRootLogin" /etc/ssh/sshd_config | awk '{print $2}') || SSH_PERMIT_ROOT_LOGIN_CONFIG="N/A (commented out or not found at all)"
|
if [ "$SSH_PERMIT_ROOT_LOGIN" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "without-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "prohibit-password" ]; then
|
||||||
if [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "prohibit-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "without-password" ]; then
|
echo " - SSH PermitRootLogin is enabled."
|
||||||
echo "PermitRootLogin is enabled."
|
else
|
||||||
SSH_PERMIT_ROOT_LOGIN=true
|
echo " - SSH PermitRootLogin is disabled."
|
||||||
fi
|
echo " If you have problems with SSH, please read this: https://coolify.io/docs/knowledge-base/server/openssh"
|
||||||
|
|
||||||
if [ "$SSH_PERMIT_ROOT_LOGIN" != "true" ]; then
|
|
||||||
echo "###############################################################################"
|
|
||||||
echo "WARNING: PermitRootLogin is not enabled in /etc/ssh/sshd_config."
|
|
||||||
echo -e "It is set to $SSH_PERMIT_ROOT_LOGIN_CONFIG. Should be prohibit-password, yes or without-password.\n"
|
|
||||||
echo -e "Please make sure it is set, otherwise Coolify cannot connect to the host system. \n"
|
|
||||||
echo "###############################################################################"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect if docker is installed via snap
|
# Detect if docker is installed via snap
|
||||||
if [ -x "$(command -v snap)" ]; then
|
if [ -x "$(command -v snap)" ]; then
|
||||||
if snap list | grep -q docker; then
|
SNAP_DOCKER_INSTALLED=$(snap list docker >/dev/null 2>&1 && echo "true" || echo "false")
|
||||||
echo "Docker is installed via snap."
|
if [ "$SNAP_DOCKER_INSTALLED" = "true" ]; then
|
||||||
echo "Please note that Coolify does not support Docker installed via snap."
|
echo " - Docker is installed via snap."
|
||||||
echo "Please remove Docker with snap (snap remove docker) and reexecute this script."
|
echo " Please note that Coolify does not support Docker installed via snap."
|
||||||
|
echo " Please remove Docker with snap (snap remove docker) and reexecute this script."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo -e "3. Check Docker Installation. "
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
|
echo " - Docker is not installed. Installing Docker. It may take a while."
|
||||||
|
getAJoke
|
||||||
case "$OS_TYPE" in
|
case "$OS_TYPE" in
|
||||||
"almalinux")
|
"almalinux")
|
||||||
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
|
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1
|
||||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo "Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
systemctl start docker
|
systemctl start docker >/dev/null 2>&1
|
||||||
systemctl enable docker
|
systemctl enable docker >/dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
"alpine")
|
"alpine")
|
||||||
apk add docker docker-cli-compose
|
apk add docker docker-cli-compose >/dev/null 2>&1
|
||||||
rc-update add docker default
|
rc-update add docker default >/dev/null 2>&1
|
||||||
service docker start
|
service docker start >/dev/null 2>&1
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo "Docker installed successfully."
|
echo " - Failed to install Docker with apk. Try to install it manually."
|
||||||
else
|
echo " Please visit https://wiki.alpinelinux.org/wiki/Docker for more information."
|
||||||
echo "Failed to install Docker with apk. Try to install it manually."
|
exit 1
|
||||||
echo "Please visit https://wiki.alpinelinux.org/wiki/Docker for more information."
|
|
||||||
exit
|
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"arch")
|
"arch")
|
||||||
pacman -Sy docker docker-compose --noconfirm
|
pacman -Sy docker docker-compose --noconfirm >/dev/null 2>&1
|
||||||
systemctl enable docker.service
|
systemctl enable docker.service >/dev/null 2>&1
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo "Docker installed successfully."
|
echo " - Failed to install Docker with pacman. Try to install it manually."
|
||||||
else
|
echo " Please visit https://wiki.archlinux.org/title/docker for more information."
|
||||||
echo "Failed to install Docker with pacman. Try to install it manually."
|
exit 1
|
||||||
echo "Please visit https://wiki.archlinux.org/title/docker for more information."
|
|
||||||
exit
|
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"amzn")
|
"amzn")
|
||||||
dnf install docker -y
|
dnf install docker -y >/dev/null 2>&1
|
||||||
DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker}
|
DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker}
|
||||||
mkdir -p $DOCKER_CONFIG/cli-plugins
|
mkdir -p $DOCKER_CONFIG/cli-plugins >/dev/null 2>&1
|
||||||
curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose
|
curl -sL https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1
|
||||||
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1
|
||||||
systemctl start docker
|
systemctl start docker >/dev/null 2>&1
|
||||||
systemctl enable docker
|
systemctl enable docker >/dev/null 2>&1
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo "Docker installed successfully."
|
echo " - Failed to install Docker with dnf. Try to install it manually."
|
||||||
else
|
echo " Please visit https://www.cyberciti.biz/faq/how-to-install-docker-on-amazon-linux-2/ for more information."
|
||||||
echo "Failed to install Docker with dnf. Try to install it manually."
|
exit 1
|
||||||
echo "Please visit https://www.cyberciti.biz/faq/how-to-install-docker-on-amazon-linux-2/ for more information."
|
|
||||||
exit
|
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# Automated Docker installation
|
curl -s https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh >/dev/null 2>&1
|
||||||
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
if [ -x "$(command -v docker)" ]; then
|
curl -s https://get.docker.com | sh -s -- --version ${DOCKER_VERSION} >/dev/null 2>&1
|
||||||
echo "Docker installed successfully."
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
else
|
echo " - Docker installation failed."
|
||||||
echo "Docker installation failed with Rancher script. Trying with official script."
|
echo " Maybe your OS is not supported?"
|
||||||
curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION}
|
echo " - Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
||||||
if [ -x "$(command -v docker)" ]; then
|
|
||||||
echo "Docker installed successfully."
|
|
||||||
else
|
|
||||||
echo "Docker installation failed with official script."
|
|
||||||
echo "Maybe your OS is not supported?"
|
|
||||||
echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
esac
|
esac
|
||||||
|
echo " - Docker installed successfully."
|
||||||
|
else
|
||||||
|
echo " - Docker is installed."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "-------------"
|
echo -e "4. Check Docker Configuration. "
|
||||||
echo -e "Check Docker Configuration..."
|
|
||||||
mkdir -p /etc/docker
|
mkdir -p /etc/docker
|
||||||
# shellcheck disable=SC2015
|
# shellcheck disable=SC2015
|
||||||
test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE" || cat >/etc/docker/daemon.json <<EOL
|
test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE" || cat >/etc/docker/daemon.json <<EOL
|
||||||
@@ -277,34 +300,33 @@ fi
|
|||||||
mv "$TEMP_FILE" /etc/docker/daemon.json
|
mv "$TEMP_FILE" /etc/docker/daemon.json
|
||||||
|
|
||||||
restart_docker_service() {
|
restart_docker_service() {
|
||||||
|
|
||||||
# Check if systemctl is available
|
# Check if systemctl is available
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
echo "Using systemctl to restart Docker..."
|
echo " - Using systemctl to restart Docker."
|
||||||
systemctl restart docker
|
systemctl restart docker
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "Docker restarted successfully using systemctl."
|
echo " - Docker restarted successfully using systemctl."
|
||||||
else
|
else
|
||||||
echo "Failed to restart Docker using systemctl."
|
echo " - Failed to restart Docker using systemctl."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if service command is available
|
# Check if service command is available
|
||||||
elif command -v service >/dev/null 2>&1; then
|
elif command -v service >/dev/null 2>&1; then
|
||||||
echo "Using service command to restart Docker..."
|
echo " - Using service command to restart Docker."
|
||||||
service docker restart
|
service docker restart
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "Docker restarted successfully using service."
|
echo " - Docker restarted successfully using service."
|
||||||
else
|
else
|
||||||
echo "Failed to restart Docker using service."
|
echo " - Failed to restart Docker using service."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If neither systemctl nor service is available
|
# If neither systemctl nor service is available
|
||||||
else
|
else
|
||||||
echo "Neither systemctl nor service command is available on this system."
|
echo " - Neither systemctl nor service command is available on this system."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -312,40 +334,30 @@ restart_docker_service() {
|
|||||||
if [ -s /etc/docker/daemon.json.original-"$DATE" ]; then
|
if [ -s /etc/docker/daemon.json.original-"$DATE" ]; then
|
||||||
DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-"$DATE"))
|
DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-"$DATE"))
|
||||||
if [ "$DIFF" != "" ]; then
|
if [ "$DIFF" != "" ]; then
|
||||||
echo "Docker configuration updated, restart docker daemon..."
|
echo " - Docker configuration updated, restart docker daemon..."
|
||||||
restart_docker_service
|
restart_docker_service
|
||||||
else
|
else
|
||||||
echo "Docker configuration is up to date."
|
echo " - Docker configuration is up to date."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Docker configuration updated, restart docker daemon..."
|
echo " - Docker configuration updated, restart docker daemon..."
|
||||||
restart_docker_service
|
restart_docker_service
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "-------------"
|
echo -e "5. Download required files from CDN. "
|
||||||
|
|
||||||
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs}
|
|
||||||
mkdir -p /data/coolify/ssh/{keys,mux}
|
|
||||||
mkdir -p /data/coolify/proxy/dynamic
|
|
||||||
|
|
||||||
chown -R 9999:root /data/coolify
|
|
||||||
chmod -R 700 /data/coolify
|
|
||||||
|
|
||||||
echo "Downloading required files from CDN..."
|
|
||||||
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
||||||
curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
||||||
curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
|
curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
|
||||||
curl -fsSL $CDN/upgrade.sh -o /data/coolify/source/upgrade.sh
|
curl -fsSL $CDN/upgrade.sh -o /data/coolify/source/upgrade.sh
|
||||||
|
|
||||||
|
echo -e "6. Make backup of .env to .env-$DATE"
|
||||||
|
|
||||||
# Copy .env.example if .env does not exist
|
# Copy .env.example if .env does not exist
|
||||||
if [ -f $ENV_FILE ]; then
|
if [ -f $ENV_FILE ]; then
|
||||||
echo "File exists: $ENV_FILE"
|
|
||||||
cat $ENV_FILE
|
|
||||||
echo "Copying .env to .env-$DATE"
|
|
||||||
cp $ENV_FILE $ENV_FILE-$DATE
|
cp $ENV_FILE $ENV_FILE-$DATE
|
||||||
else
|
else
|
||||||
echo "File does not exist: $ENV_FILE"
|
echo " - File does not exist: $ENV_FILE"
|
||||||
echo "Copying .env.production to .env-$DATE"
|
echo " - Copying .env.production to .env-$DATE"
|
||||||
cp /data/coolify/source/.env.production $ENV_FILE-$DATE
|
cp /data/coolify/source/.env.production $ENV_FILE-$DATE
|
||||||
# Generate a secure APP_ID and APP_KEY
|
# Generate a secure APP_ID and APP_KEY
|
||||||
sed -i "s|^APP_ID=.*|APP_ID=$(openssl rand -hex 16)|" "$ENV_FILE-$DATE"
|
sed -i "s|^APP_ID=.*|APP_ID=$(openssl rand -hex 16)|" "$ENV_FILE-$DATE"
|
||||||
@@ -366,6 +378,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Merge .env and .env.production. New values will be added to .env
|
# Merge .env and .env.production. New values will be added to .env
|
||||||
|
echo -e "7. Propagating .env with new values - if necessary."
|
||||||
awk -F '=' '!seen[$1]++' "$ENV_FILE-$DATE" /data/coolify/source/.env.production > $ENV_FILE
|
awk -F '=' '!seen[$1]++' "$ENV_FILE-$DATE" /data/coolify/source/.env.production > $ENV_FILE
|
||||||
|
|
||||||
if [ "$AUTOUPDATE" = "false" ]; then
|
if [ "$AUTOUPDATE" = "false" ]; then
|
||||||
@@ -375,33 +388,122 @@ if [ "$AUTOUPDATE" = "false" ]; then
|
|||||||
sed -i "s|AUTOUPDATE=.*|AUTOUPDATE=false|g" /data/coolify/source/.env
|
sed -i "s|AUTOUPDATE=.*|AUTOUPDATE=false|g" /data/coolify/source/.env
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
echo -e "8. Checking for SSH key for localhost access."
|
||||||
# Generate an ssh key (ed25519) at /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
if [ ! -f /data/coolify/ssh/keys/id.root@host.docker.internal ]; then
|
|
||||||
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.root@host.docker.internal -q -N "" -C root@coolify
|
|
||||||
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
fi
|
|
||||||
|
|
||||||
addSshKey() {
|
|
||||||
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >>~/.ssh/authorized_keys
|
|
||||||
chmod 600 ~/.ssh/authorized_keys
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ ! -f ~/.ssh/authorized_keys ]; then
|
if [ ! -f ~/.ssh/authorized_keys ]; then
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
chmod 700 ~/.ssh
|
chmod 700 ~/.ssh
|
||||||
touch ~/.ssh/authorized_keys
|
touch ~/.ssh/authorized_keys
|
||||||
addSshKey
|
chmod 600 ~/.ssh/authorized_keys
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! grep -qw "root@coolify" ~/.ssh/authorized_keys; then
|
checkSshKeyInAuthorizedKeys() {
|
||||||
addSshKey
|
grep -qw "root@coolify" ~/.ssh/authorized_keys
|
||||||
fi
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}"
|
checkSshKeyInCoolifyData() {
|
||||||
|
[ -s /data/coolify/ssh/keys/id.root@host.docker.internal ]
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
generateAuthorizedKeys() {
|
||||||
|
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
||||||
|
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
||||||
|
rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
||||||
|
}
|
||||||
|
generateSshKey() {
|
||||||
|
echo " - Generating SSH key."
|
||||||
|
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.root@host.docker.internal -q -N "" -C root@coolify
|
||||||
|
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
generateAuthorizedKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
syncSshKeys() {
|
||||||
|
DB_RUNNING=$(docker inspect coolify-db --format '{{ .State.Status }}' 2>/dev/null)
|
||||||
|
# Check if SSH key exists in Coolify data but not in authorized_keys
|
||||||
|
if checkSshKeyInCoolifyData && ! checkSshKeyInAuthorizedKeys; then
|
||||||
|
# Add the existing Coolify SSH key to authorized_keys
|
||||||
|
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
||||||
|
# Check if SSH key exists in authorized_keys but not in Coolify data
|
||||||
|
elif checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then
|
||||||
|
# Ensure Coolify DB is running before proceeding
|
||||||
|
if [ "$DB_RUNNING" = "running" ]; then
|
||||||
|
# Retrieve DB user and SSH key from Coolify database
|
||||||
|
DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+')
|
||||||
|
DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t)
|
||||||
|
|
||||||
|
if [ -z "$DB_SSH_KEY" ]; then
|
||||||
|
# If no key found in DB, generate a new one
|
||||||
|
echo " - SSH key not found in database. Generating new key."
|
||||||
|
generateSshKey
|
||||||
|
else
|
||||||
|
# If key found in DB, save it and update authorized_keys
|
||||||
|
echo " - SSH key found in database. Saving to file."
|
||||||
|
echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
|
||||||
|
# Generate public key from private key and update authorized_keys
|
||||||
|
ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
||||||
|
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
||||||
|
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
||||||
|
rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
||||||
|
chmod 600 ~/.ssh/authorized_keys
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# If SSH key doesn't exist in either location
|
||||||
|
elif ! checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then
|
||||||
|
# Ensure Coolify DB is running before proceeding
|
||||||
|
if [ "$DB_RUNNING" = "running" ]; then
|
||||||
|
# Retrieve DB user and SSH key from Coolify database
|
||||||
|
DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+')
|
||||||
|
DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t)
|
||||||
|
if [ -z "$DB_SSH_KEY" ]; then
|
||||||
|
# If no key found in DB, generate a new one
|
||||||
|
echo " - SSH key not found in database. Generating new key."
|
||||||
|
generateSshKey
|
||||||
|
else
|
||||||
|
# If key found in DB, save it and update authorized_keys
|
||||||
|
echo " - SSH key found in database. Saving to file."
|
||||||
|
echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
||||||
|
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
||||||
|
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
generateSshKey
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
syncSshKeys || true
|
||||||
|
|
||||||
|
chown -R 9999:root /data/coolify
|
||||||
|
chmod -R 700 /data/coolify
|
||||||
|
|
||||||
|
echo -e "9. Installing Coolify ($LATEST_VERSION)"
|
||||||
|
echo -e " - It could take a while based on your server's performance, network speed, stars, etc."
|
||||||
|
echo -e " - Please wait."
|
||||||
|
getAJoke
|
||||||
|
|
||||||
|
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" >/dev/null 2>&1
|
||||||
|
echo " - Coolify installed successfully."
|
||||||
rm -f $ENV_FILE-$DATE
|
rm -f $ENV_FILE-$DATE
|
||||||
echo "Waiting for 20 seconds for Coolify to be ready..."
|
|
||||||
|
echo " - Waiting for 20 seconds for Coolify (database migrations) to be ready."
|
||||||
|
getAJoke
|
||||||
|
|
||||||
sleep 20
|
sleep 20
|
||||||
echo "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started."
|
echo -e "\033[0;35m
|
||||||
echo -e "\nCongratulations! Your Coolify instance is ready to use.\n"
|
____ _ _ _ _ _
|
||||||
|
/ ___|___ _ __ __ _ _ __ __ _| |_ _ _| | __ _| |_(_) ___ _ __ ___| |
|
||||||
|
| | / _ \| '_ \ / _\` | '__/ _\` | __| | | | |/ _\` | __| |/ _ \| '_ \/ __| |
|
||||||
|
| |__| (_) | | | | (_| | | | (_| | |_| |_| | | (_| | |_| | (_) | | | \__ \_|
|
||||||
|
\____\___/|_| |_|\__, |_| \__,_|\__|\__,_|_|\__,_|\__|_|\___/|_| |_|___(_)
|
||||||
|
|___/
|
||||||
|
\033[0m"
|
||||||
|
echo -e "\nYour instance is ready to use."
|
||||||
|
echo -e "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started.\n"
|
||||||
|
echo -e "WARNING: We recommend you to backup your /data/coolify/source/.env file to a safe location, outside of this server."
|
||||||
|
cp /data/coolify/source/.env /data/coolify/source/.env.backup
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
|
|||||||
|
|
||||||
# Merge .env and .env.production. New values will be added to .env
|
# Merge .env and .env.production. New values will be added to .env
|
||||||
awk -F '=' '!seen[$1]++' /data/coolify/source/.env /data/coolify/source/.env.production > /data/coolify/source/.env.tmp && mv /data/coolify/source/.env.tmp /data/coolify/source/.env
|
awk -F '=' '!seen[$1]++' /data/coolify/source/.env /data/coolify/source/.env.production > /data/coolify/source/.env.tmp && mv /data/coolify/source/.env.tmp /data/coolify/source/.env
|
||||||
|
|
||||||
# Check if PUSHER_APP_ID or PUSHER_APP_KEY or PUSHER_APP_SECRET is empty in /data/coolify/source/.env
|
# Check if PUSHER_APP_ID or PUSHER_APP_KEY or PUSHER_APP_SECRET is empty in /data/coolify/source/.env
|
||||||
if grep -q "PUSHER_APP_ID=$" /data/coolify/source/.env; then
|
if grep -q "PUSHER_APP_ID=$" /data/coolify/source/.env; then
|
||||||
sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env
|
sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.330"
|
"version": "4.0.0-beta.339"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.331"
|
"version": "4.0.0-beta.340"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
|
},
|
||||||
|
"realtime": {
|
||||||
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
76
package-lock.json
generated
76
package-lock.json
generated
@@ -7,9 +7,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/forms": "0.5.7",
|
"@tailwindcss/forms": "0.5.7",
|
||||||
"@tailwindcss/typography": "0.5.13",
|
"@tailwindcss/typography": "0.5.13",
|
||||||
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
|
"@xterm/xterm": "^5.5.0",
|
||||||
"alpinejs": "3.14.0",
|
"alpinejs": "3.14.0",
|
||||||
|
"cookie": "^0.6.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"ioredis": "5.4.1",
|
"ioredis": "5.4.1",
|
||||||
"tailwindcss-scrollbar": "0.1.0"
|
"node-pty": "^1.0.0",
|
||||||
|
"tailwindcss-scrollbar": "0.1.0",
|
||||||
|
"ws": "^8.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "4.5.1",
|
"@vitejs/plugin-vue": "4.5.1",
|
||||||
@@ -692,6 +698,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
|
||||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
|
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@xterm/addon-fit": {
|
||||||
|
"version": "0.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz",
|
||||||
|
"integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@xterm/xterm": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@xterm/xterm": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="
|
||||||
|
},
|
||||||
"node_modules/alpinejs": {
|
"node_modules/alpinejs": {
|
||||||
"version": "3.14.0",
|
"version": "3.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.0.tgz",
|
||||||
@@ -940,6 +959,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@@ -1000,6 +1028,18 @@
|
|||||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||||
|
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.692",
|
"version": "1.4.692",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz",
|
||||||
@@ -1475,6 +1515,11 @@
|
|||||||
"thenify-all": "^1.0.0"
|
"thenify-all": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nan": {
|
||||||
|
"version": "2.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
|
||||||
|
"integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw=="
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.7",
|
"version": "3.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||||
@@ -1492,6 +1537,15 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-pty": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"nan": "^2.17.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.14",
|
"version": "2.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||||
@@ -2124,6 +2178,26 @@
|
|||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
|
||||||
|
|||||||
@@ -20,8 +20,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/forms": "0.5.7",
|
"@tailwindcss/forms": "0.5.7",
|
||||||
"@tailwindcss/typography": "0.5.13",
|
"@tailwindcss/typography": "0.5.13",
|
||||||
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
|
"@xterm/xterm": "^5.5.0",
|
||||||
"alpinejs": "3.14.0",
|
"alpinejs": "3.14.0",
|
||||||
|
"cookie": "^0.6.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"ioredis": "5.4.1",
|
"ioredis": "5.4.1",
|
||||||
"tailwindcss-scrollbar": "0.1.0"
|
"node-pty": "^1.0.0",
|
||||||
|
"tailwindcss-scrollbar": "0.1.0",
|
||||||
|
"ws": "^8.17.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,3 +4,18 @@
|
|||||||
// const app = createApp({});
|
// const app = createApp({});
|
||||||
// app.component("magic-bar", MagicBar);
|
// app.component("magic-bar", MagicBar);
|
||||||
// app.mount("#vue");
|
// app.mount("#vue");
|
||||||
|
|
||||||
|
import { Terminal } from '@xterm/xterm';
|
||||||
|
import '@xterm/xterm/css/xterm.css';
|
||||||
|
import { FitAddon } from '@xterm/addon-fit';
|
||||||
|
|
||||||
|
if (!window.term) {
|
||||||
|
window.term = new Terminal({
|
||||||
|
cols: 80,
|
||||||
|
rows: 30,
|
||||||
|
fontFamily: '"Fira Code", courier-new, courier, monospace, "Powerline Extra Symbols"',
|
||||||
|
cursorBlink: true,
|
||||||
|
});
|
||||||
|
window.fitAddon = new FitAddon();
|
||||||
|
window.term.loadAddon(window.fitAddon);
|
||||||
|
}
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ const magicActions = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 19,
|
id: 19,
|
||||||
name: 'Goto: Command Center',
|
name: 'Goto: Terminal',
|
||||||
icon: 'goto',
|
icon: 'goto',
|
||||||
sequence: ['main', 'redirect']
|
sequence: ['main', 'redirect']
|
||||||
},
|
},
|
||||||
@@ -653,7 +653,7 @@ async function redirect() {
|
|||||||
targetUrl.pathname = `/settings`
|
targetUrl.pathname = `/settings`
|
||||||
break;
|
break;
|
||||||
case 19:
|
case 19:
|
||||||
targetUrl.pathname = `/command-center`
|
targetUrl.pathname = `/terminal`
|
||||||
break;
|
break;
|
||||||
case 20:
|
case 20:
|
||||||
targetUrl.pathname = `/team/notifications`
|
targetUrl.pathname = `/team/notifications`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
@if ($label)
|
@if ($label)
|
||||||
<label class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }}
|
<label class="flex gap-1 items-center mb-1 text-sm font-medium">{{ $label }}
|
||||||
@if ($required)
|
@if ($required)
|
||||||
<x-highlighted text="*" />
|
<x-highlighted text="*" />
|
||||||
@endif
|
@endif
|
||||||
@@ -9,7 +9,8 @@
|
|||||||
@endif
|
@endif
|
||||||
</label>
|
</label>
|
||||||
@endif
|
@endif
|
||||||
<select {{ $attributes->merge(['class' => $defaultClass]) }} @required($required) wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
<select {{ $attributes->merge(['class' => $defaultClass]) }} @required($required)
|
||||||
|
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
||||||
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled" name={{ $id }}
|
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled" name={{ $id }}
|
||||||
@if ($attributes->whereStartsWith('wire:model')->first()) {{ $attributes->whereStartsWith('wire:model')->first() }} @else wire:model={{ $id }} @endif>
|
@if ($attributes->whereStartsWith('wire:model')->first()) {{ $attributes->whereStartsWith('wire:model')->first() }} @else wire:model={{ $id }} @endif>
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<nav class="flex flex-col flex-1 bg-white border-r dark:border-coolgray-200 dark:bg-base" x-data="{
|
<nav class="flex flex-col flex-1 bg-white border-r dark:border-coolgray-200 dark:bg-base" x-data="{
|
||||||
switchWidth() {
|
switchWidth() {
|
||||||
if (this.full === 'full') {
|
if (this.full === 'full') {
|
||||||
localStorage.removeItem('pageWidth');
|
localStorage.setItem('pageWidth', 'center');
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem('pageWidth', 'full');
|
localStorage.setItem('pageWidth', 'full');
|
||||||
}
|
}
|
||||||
@@ -74,8 +74,10 @@
|
|||||||
<button @click="setTheme('light')" class="px-1 dropdown-item-no-padding">Light</button>
|
<button @click="setTheme('light')" class="px-1 dropdown-item-no-padding">Light</button>
|
||||||
<button @click="setTheme('system')" class="px-1 dropdown-item-no-padding">System</button>
|
<button @click="setTheme('system')" class="px-1 dropdown-item-no-padding">System</button>
|
||||||
<div class="my-1 font-bold border-b dark:border-coolgray-500 dark:text-white text-md">Width</div>
|
<div class="my-1 font-bold border-b dark:border-coolgray-500 dark:text-white text-md">Width</div>
|
||||||
<button @click="switchWidth()" class="px-1 dropdown-item-no-padding" x-show="full">Center</button>
|
<button @click="switchWidth()" class="px-1 dropdown-item-no-padding"
|
||||||
<button @click="switchWidth()" class="px-1 dropdown-item-no-padding" x-show="!full">Full</button>
|
x-show="full === 'full'">Center</button>
|
||||||
|
<button @click="switchWidth()" class="px-1 dropdown-item-no-padding"
|
||||||
|
x-show="full === 'center'">Full</button>
|
||||||
</div>
|
</div>
|
||||||
</x-dropdown>
|
</x-dropdown>
|
||||||
</div>
|
</div>
|
||||||
@@ -226,9 +228,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a title="Command Center"
|
<a title="Terminal"
|
||||||
class="{{ request()->is('command-center*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('terminal*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="{{ route('command-center') }}">
|
href="{{ route('terminal') }}">
|
||||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
stroke-linejoin="round">
|
stroke-linejoin="round">
|
||||||
@@ -236,7 +238,7 @@
|
|||||||
<path d="M5 7l5 5l-5 5" />
|
<path d="M5 7l5 5l-5 5" />
|
||||||
<path d="M12 19l7 0" />
|
<path d="M12 19l7 0" />
|
||||||
</svg>
|
</svg>
|
||||||
Command Center
|
Terminal
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
open: false,
|
open: false,
|
||||||
init() {
|
init() {
|
||||||
this.pageWidth = localStorage.getItem('pageWidth');
|
this.pageWidth = localStorage.getItem('pageWidth');
|
||||||
|
if (!this.pageWidth) {
|
||||||
|
this.pageWidth = 'full';
|
||||||
|
localStorage.setItem('pageWidth', 'full');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}" x-cloak class="mx-auto" :class="pageWidth === 'full' ? '' : 'max-w-7xl'">
|
}" x-cloak class="mx-auto" :class="pageWidth === 'full' ? '' : 'max-w-7xl'">
|
||||||
<div class="relative z-50 lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true">
|
<div class="relative z-50 lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true">
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
<div>
|
|
||||||
<x-slot:title>
|
|
||||||
Command Center | Coolify
|
|
||||||
</x-slot>
|
|
||||||
<h1>Command Center</h1>
|
|
||||||
<div class="subtitle">Execute commands on your servers without leaving the browser.</div>
|
|
||||||
@if ($servers->count() > 0)
|
|
||||||
<livewire:run-command :servers="$servers" />
|
|
||||||
@else
|
|
||||||
<div>
|
|
||||||
<div>No servers found. Without a server, you won't be able to do much.</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<nav wire:poll.10000ms="check_status">
|
<nav wire:poll.10000ms="check_status">
|
||||||
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :lastDeploymentInfo="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
|
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :lastDeploymentInfo="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<nav class="flex items-center flex-shrink-0 gap-6 scrollbar min-h-10 whitespace-nowrap">
|
<nav class="flex flex-shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
||||||
<a href="{{ route('project.application.configuration', $parameters) }}">
|
<a href="{{ route('project.application.configuration', $parameters) }}">
|
||||||
Configuration
|
Configuration
|
||||||
</a>
|
</a>
|
||||||
@@ -13,12 +13,12 @@
|
|||||||
</a>
|
</a>
|
||||||
@if (!$application->destination->server->isSwarm())
|
@if (!$application->destination->server->isSwarm())
|
||||||
<a href="{{ route('project.application.command', $parameters) }}">
|
<a href="{{ route('project.application.command', $parameters) }}">
|
||||||
<button>Command</button>
|
<button>Terminal</button>
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
<x-applications.links :application="$application" />
|
<x-applications.links :application="$application" />
|
||||||
</nav>
|
</nav>
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap gap-2 items-center">
|
||||||
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
||||||
<div>Please load a Compose file.</div>
|
<div>Please load a Compose file.</div>
|
||||||
@else
|
@else
|
||||||
|
|||||||
@@ -8,19 +8,20 @@
|
|||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<nav
|
<nav
|
||||||
class="flex items-center flex-shrink-0 gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
|
class="flex overflow-x-scroll flex-shrink-0 gap-6 items-center whitespace-nowrap sm:overflow-x-hidden scrollbar min-h-10">
|
||||||
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('project.database.configuration', $parameters) }}">
|
href="{{ route('project.database.configuration', $parameters) }}">
|
||||||
<button>Configuration</button>
|
<button>Configuration</button>
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('project.database.command', $parameters) }}">
|
|
||||||
<button>Execute Command</button>
|
|
||||||
</a>
|
|
||||||
<a class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('project.database.logs', $parameters) }}">
|
href="{{ route('project.database.logs', $parameters) }}">
|
||||||
<button>Logs</button>
|
<button>Logs</button>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
|
||||||
|
href="{{ route('project.database.command', $parameters) }}">
|
||||||
|
<button>Terminal</button>
|
||||||
|
</a>
|
||||||
@if (
|
@if (
|
||||||
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
|
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
|
||||||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
</nav>
|
</nav>
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap gap-2 items-center">
|
||||||
@if (!str($database->status)->startsWith('exited'))
|
@if (!str($database->status)->startsWith('exited'))
|
||||||
<x-modal-confirmation @click="$wire.dispatch('restartEvent')">
|
<x-modal-confirmation @click="$wire.dispatch('restartEvent')">
|
||||||
<x-slot:button-title>
|
<x-slot:button-title>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
{{ data_get_str($service, 'name')->limit(10) }} > Configuration | Coolify
|
{{ data_get_str($service, 'name')->limit(10) }} > Configuration | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
|
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
|
||||||
<div class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
|
<div class="flex flex-col gap-8 pt-6 h-full sm:flex-row">
|
||||||
<div class="flex flex-col items-start gap-2 min-w-fit">
|
<div class="flex flex-col gap-2 items-start min-w-fit">
|
||||||
<a class="menu-item sm:min-w-fit" target="_blank" href="{{ $service->documentation() }}">Documentation
|
<a class="menu-item sm:min-w-fit" target="_blank" href="{{ $service->documentation() }}">Documentation
|
||||||
<x-external-link /></a>
|
<x-external-link /></a>
|
||||||
<a class="menu-item sm:min-w-fit" :class="activeTab === 'service-stack' && 'menu-item-active'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'service-stack' && 'menu-item-active'"
|
||||||
@@ -23,10 +23,6 @@
|
|||||||
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
|
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
|
||||||
href="#">Scheduled Tasks
|
href="#">Scheduled Tasks
|
||||||
</a>
|
</a>
|
||||||
<a class="menu-item sm:min-w-fit" :class="activeTab === 'execute-command' && 'menu-item-active'"
|
|
||||||
@click.prevent="activeTab = 'execute-command';
|
|
||||||
window.location.hash = 'execute-command'"
|
|
||||||
href="#">Execute Command</a>
|
|
||||||
<a class="menu-item sm:min-w-fit" :class="activeTab === 'logs' && 'menu-item-active'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'logs' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'logs';
|
@click.prevent="activeTab = 'logs';
|
||||||
window.location.hash = 'logs'"
|
window.location.hash = 'logs'"
|
||||||
@@ -168,7 +164,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'storages'">
|
<div x-cloak x-show="activeTab === 'storages'">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex gap-2 items-center">
|
||||||
<h2>Storages</h2>
|
<h2>Storages</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
||||||
@@ -191,9 +187,6 @@
|
|||||||
<div x-cloak x-show="activeTab === 'logs'">
|
<div x-cloak x-show="activeTab === 'logs'">
|
||||||
<livewire:project.shared.logs :resource="$service" />
|
<livewire:project.shared.logs :resource="$service" />
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'execute-command'">
|
|
||||||
<livewire:project.shared.execute-container-command :resource="$service" />
|
|
||||||
</div>
|
|
||||||
<div x-cloak x-show="activeTab === 'environment-variables'">
|
<div x-cloak x-show="activeTab === 'environment-variables'">
|
||||||
<livewire:project.shared.environment-variable.all :resource="$service" />
|
<livewire:project.shared.environment-variable.all :resource="$service" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,17 +6,21 @@
|
|||||||
<livewire:activity-monitor header="Logs" showWaiting fullHeight />
|
<livewire:activity-monitor header="Logs" showWaiting fullHeight />
|
||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
<h1>Configuration</h1>
|
<h1>{{ $title }}</h1>
|
||||||
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
||||||
<div class="navbar-main" x-data>
|
<div class="navbar-main" x-data>
|
||||||
<nav class="flex items-center flex-shrink-0 gap-6 scrollbar min-h-10 whitespace-nowrap">
|
<nav class="flex flex-shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
||||||
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('project.service.configuration', $parameters) }}">
|
href="{{ route('project.service.configuration', $parameters) }}">
|
||||||
<button>Configuration</button>
|
<button>Configuration</button>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="{{ request()->routeIs('project.service.command') ? 'dark:text-white' : '' }}"
|
||||||
|
href="{{ route('project.service.command', $parameters) }}">
|
||||||
|
<button>Terminal</button>
|
||||||
|
</a>
|
||||||
<x-services.links :service="$service" />
|
<x-services.links :service="$service" />
|
||||||
</nav>
|
</nav>
|
||||||
<div class="flex flex-wrap items-center order-first gap-2 sm:order-last">
|
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
||||||
@if (str($service->status())->contains('running'))
|
@if (str($service->status())->contains('running'))
|
||||||
<button @click="$wire.dispatch('restartEvent')" class="gap-2 button">
|
<button @click="$wire.dispatch('restartEvent')" class="gap-2 button">
|
||||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -71,7 +75,7 @@
|
|||||||
</x-modal-confirmation>
|
</x-modal-confirmation>
|
||||||
@elseif (str($service->status())->contains('exited'))
|
@elseif (str($service->status())->contains('exited'))
|
||||||
<button wire:click='stop(true)' class="gap-2 button">
|
<button wire:click='stop(true)' class="gap-2 button">
|
||||||
<svg class="w-5 h-5 " viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
<svg class="w-5 h-5" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill="red" d="M26 20h-6v-2h6zm4 8h-6v-2h6zm-2-4h-6v-2h6z" />
|
<path fill="red" d="M26 20h-6v-2h6zm4 8h-6v-2h6zm-2-4h-6v-2h6z" />
|
||||||
<path fill="red"
|
<path fill="red"
|
||||||
d="M17.003 20a4.895 4.895 0 0 0-2.404-4.173L22 3l-1.73-1l-7.577 13.126a5.699 5.699 0 0 0-5.243 1.503C3.706 20.24 3.996 28.682 4.01 29.04a1 1 0 0 0 1 .96h14.991a1 1 0 0 0 .6-1.8c-3.54-2.656-3.598-8.146-3.598-8.2Zm-5.073-3.003A3.11 3.11 0 0 1 15.004 20c0 .038.002.208.017.469l-5.9-2.624a3.8 3.8 0 0 1 2.809-.848ZM15.45 28A5.2 5.2 0 0 1 14 25h-2a6.5 6.5 0 0 0 .968 3h-2.223A16.617 16.617 0 0 1 10 24H8a17.342 17.342 0 0 0 .665 4H6c.031-1.836.29-5.892 1.803-8.553l7.533 3.35A13.025 13.025 0 0 0 17.596 28Z" />
|
d="M17.003 20a4.895 4.895 0 0 0-2.404-4.173L22 3l-1.73-1l-7.577 13.126a5.699 5.699 0 0 0-5.243 1.503C3.706 20.24 3.996 28.682 4.01 29.04a1 1 0 0 0 1 .96h14.991a1 1 0 0 0 .6-1.8c-3.54-2.656-3.598-8.146-3.598-8.2Zm-5.073-3.003A3.11 3.11 0 0 1 15.004 20c0 .038.002.208.017.469l-5.9-2.624a3.8 3.8 0 0 1 2.809-.848ZM15.45 28A5.2 5.2 0 0 1 14 25h-2a6.5 6.5 0 0 0 .968 3h-2.223A16.617 16.617 0 0 1 10 24H8a17.342 17.342 0 0 0 .665 4H6c.031-1.836.29-5.892 1.803-8.553l7.533 3.35A13.025 13.025 0 0 0 17.596 28Z" />
|
||||||
|
|||||||
@@ -4,57 +4,39 @@
|
|||||||
</x-slot>
|
</x-slot>
|
||||||
<livewire:project.shared.configuration-checker :resource="$resource" />
|
<livewire:project.shared.configuration-checker :resource="$resource" />
|
||||||
@if ($type === 'application')
|
@if ($type === 'application')
|
||||||
<h1>Execute Command</h1>
|
<h1>Terminal</h1>
|
||||||
<livewire:project.application.heading :application="$resource" />
|
<livewire:project.application.heading :application="$resource" />
|
||||||
<h2 class="pt-4">Command</h2>
|
|
||||||
<div class="pb-2">Run any one-shot command inside a container.</div>
|
|
||||||
@elseif ($type === 'database')
|
@elseif ($type === 'database')
|
||||||
<h1>Execute Command</h1>
|
<h1>Terminal</h1>
|
||||||
<livewire:project.database.heading :database="$resource" />
|
<livewire:project.database.heading :database="$resource" />
|
||||||
<h2 class="pt-4">Command</h2>
|
|
||||||
<div class="pb-2">Run any one-shot command inside a container.</div>
|
|
||||||
@elseif ($type === 'service')
|
@elseif ($type === 'service')
|
||||||
<h2>Execute Command</h2>
|
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" title="Terminal" />
|
||||||
@endif
|
@endif
|
||||||
<div x-init="$wire.loadContainers">
|
<div x-init="$wire.loadContainers">
|
||||||
<div class="pt-4" wire:loading wire:target='loadContainers'>
|
<div class="pt-4" wire:loading wire:target='loadContainers'>
|
||||||
Loading containers...
|
Loading resources...
|
||||||
</div>
|
</div>
|
||||||
<div wire:loading.remove wire:target='loadContainers'>
|
<div wire:loading.remove wire:target='loadContainers'>
|
||||||
@if (count($containers) > 0)
|
@if (count($containers) > 0)
|
||||||
<form class="flex flex-col gap-2 pt-4" wire:submit='runCommand'>
|
<form class="flex flex-col gap-2 justify-center pt-4 xl:items-end xl:flex-row"
|
||||||
<div class="flex gap-2">
|
wire:submit="$dispatchSelf('connectToContainer')">
|
||||||
<x-forms.input placeholder="ls -l" autofocus id="command" label="Command" required />
|
|
||||||
<x-forms.input id="workDir" label="Working directory" />
|
|
||||||
</div>
|
|
||||||
<x-forms.select label="Container" id="container" required>
|
<x-forms.select label="Container" id="container" required>
|
||||||
<option disabled selected>Select container</option>
|
<option disabled selected>Select container</option>
|
||||||
@if (data_get($this->parameters, 'application_uuid'))
|
@foreach ($containers as $container)
|
||||||
@foreach ($containers as $container)
|
<option value="{{ data_get($container, 'container.Names') }}">
|
||||||
<option value="{{ data_get($container, 'container.Names') }}">
|
{{ data_get($container, 'container.Names') }}
|
||||||
{{ data_get($container, 'container.Names') }} ({{ data_get($container, 'server.name') }})
|
({{ data_get($container, 'server.name') }})
|
||||||
</option>
|
|
||||||
@endforeach
|
|
||||||
@elseif(data_get($this->parameters, 'service_uuid'))
|
|
||||||
@foreach ($containers as $container)
|
|
||||||
<option value="{{ $container }}">
|
|
||||||
{{ $container }} ({{ data_get($servers, '0.name') }})
|
|
||||||
</option>
|
|
||||||
@endforeach
|
|
||||||
@else
|
|
||||||
<option value="{{ $container }}">
|
|
||||||
{{ $container }} ({{ data_get($servers, '0.name') }})
|
|
||||||
</option>
|
</option>
|
||||||
@endif
|
@endforeach
|
||||||
</x-forms.select>
|
</x-forms.select>
|
||||||
<x-forms.button type="submit">Run</x-forms.button>
|
<x-forms.button type="submit">Connect</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
@else
|
@else
|
||||||
<div class="pt-4">No containers are not running.</div>
|
<div class="pt-4">No containers are not running.</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full pt-10 mx-auto">
|
<div class="mx-auto w-full">
|
||||||
<livewire:activity-monitor header="Command output" />
|
<livewire:project.shared.terminal />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
225
resources/views/livewire/project/shared/terminal.blade.php
Normal file
225
resources/views/livewire/project/shared/terminal.blade.php
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
<div x-data="data()">
|
||||||
|
{{-- <div x-show="!terminalActive" class="flex items-center justify-center w-full py-4 mx-auto h-[510px]">
|
||||||
|
<div class="p-1 w-full h-full rounded border dark:bg-coolgray-100 dark:border-coolgray-300">
|
||||||
|
<span class="font-mono text-sm text-gray-500" x-text="message"></span>
|
||||||
|
</div>
|
||||||
|
</div> --}}
|
||||||
|
<div x-ref="terminalWrapper"
|
||||||
|
:class="fullscreen ? 'fullscreen' : 'relative w-full h-full py-4 mx-auto max-h-[510px]'">
|
||||||
|
<div id="terminal" wire:ignore></div>
|
||||||
|
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4 text-white" x-on:click="makeFullscreen"><svg
|
||||||
|
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
|
||||||
|
</svg></button>
|
||||||
|
<button title="Fullscreen" x-show="!fullscreen && terminalActive" class="absolute right-4 top-6 text-white"
|
||||||
|
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none">
|
||||||
|
<path
|
||||||
|
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
|
||||||
|
</g>
|
||||||
|
</svg></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@script
|
||||||
|
<script>
|
||||||
|
const MAX_PENDING_WRITES = 5;
|
||||||
|
let pendingWrites = 0;
|
||||||
|
let paused = false;
|
||||||
|
|
||||||
|
let socket;
|
||||||
|
let commandBuffer = '';
|
||||||
|
|
||||||
|
|
||||||
|
function keepAlive() {
|
||||||
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
ping: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const keepAliveInterval = setInterval(keepAlive, 30000);
|
||||||
|
|
||||||
|
// Clear the interval when the component is destroyed
|
||||||
|
document.addEventListener('livewire:navigating', () => {
|
||||||
|
clearInterval(keepAliveInterval);
|
||||||
|
});
|
||||||
|
|
||||||
|
function initializeWebSocket() {
|
||||||
|
if (!socket || socket.readyState === WebSocket.CLOSED) {
|
||||||
|
// Only use port if Coolify is used with ip (so it has a port in the url)
|
||||||
|
let postPath = ':6002/terminal/ws';
|
||||||
|
const port = window.location.port;
|
||||||
|
if (!port) {
|
||||||
|
postPath = '/terminal/ws';
|
||||||
|
}
|
||||||
|
let url = window.location.hostname;
|
||||||
|
// make sure the port is not included
|
||||||
|
url = url.split(':')[0];
|
||||||
|
socket = new WebSocket((window.location.protocol === 'https:' ? 'wss://' : 'ws://') +
|
||||||
|
url +
|
||||||
|
postPath);
|
||||||
|
|
||||||
|
socket.onmessage = handleSocketMessage;
|
||||||
|
socket.onerror = (e) => {
|
||||||
|
console.error('WebSocket error:', e);
|
||||||
|
};
|
||||||
|
socket.onclose = () => {
|
||||||
|
console.log('WebSocket connection closed');
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSocketMessage(event) {
|
||||||
|
$data.message = '(connection closed)';
|
||||||
|
// Initialize Terminal
|
||||||
|
if (event.data === 'pty-ready') {
|
||||||
|
term.open(document.getElementById('terminal'));
|
||||||
|
$data.terminalActive = true;
|
||||||
|
term.reset();
|
||||||
|
term.focus();
|
||||||
|
document.querySelector('.xterm-viewport').classList.add('scrollbar', 'rounded')
|
||||||
|
$data.resizeTerminal()
|
||||||
|
} else if (event.data === 'unprocessable') {
|
||||||
|
term.reset();
|
||||||
|
$data.terminalActive = false;
|
||||||
|
$data.message = '(sorry, something went wrong, please try again)';
|
||||||
|
} else {
|
||||||
|
pendingWrites++;
|
||||||
|
term.write(event.data, flowControlCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flowControlCallback() {
|
||||||
|
pendingWrites--;
|
||||||
|
if (pendingWrites > MAX_PENDING_WRITES && !paused) {
|
||||||
|
paused = true;
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
pause: true
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pendingWrites <= MAX_PENDING_WRITES && paused) {
|
||||||
|
paused = false;
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
resume: true
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
term.onData((data) => {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
message: data
|
||||||
|
}));
|
||||||
|
// Type CTRL + D or exit in the terminal
|
||||||
|
if (data === '\x04' || (data === '\r' && stripAnsiCommands(commandBuffer).trim().includes('exit'))) {
|
||||||
|
checkIfProcessIsRunningAndKillIt();
|
||||||
|
setTimeout(() => {
|
||||||
|
$data.terminalActive = false;
|
||||||
|
term.reset();
|
||||||
|
}, 500);
|
||||||
|
commandBuffer = '';
|
||||||
|
} else if (data === '\r') {
|
||||||
|
commandBuffer = '';
|
||||||
|
} else {
|
||||||
|
commandBuffer += data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function stripAnsiCommands(input) {
|
||||||
|
return input.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy and paste
|
||||||
|
// Enables ctrl + c and ctrl + v
|
||||||
|
// defaults otherwise to ctrl + insert, shift + insert
|
||||||
|
term.attachCustomKeyEventHandler((arg) => {
|
||||||
|
if (arg.ctrlKey && arg.code === "KeyV" && arg.type === "keydown") {
|
||||||
|
navigator.clipboard.readText()
|
||||||
|
.then(text => {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
message: text
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
if (arg.ctrlKey && arg.code === "KeyC" && arg.type === "keydown") {
|
||||||
|
const selection = term.getSelection();
|
||||||
|
if (selection) {
|
||||||
|
navigator.clipboard.writeText(selection);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
$wire.on('send-back-command', function(command) {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
command: command
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function(e) {
|
||||||
|
checkIfProcessIsRunningAndKillIt();
|
||||||
|
});
|
||||||
|
|
||||||
|
function checkIfProcessIsRunningAndKillIt() {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
checkActive: 'force'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onresize = function() {
|
||||||
|
$data.resizeTerminal()
|
||||||
|
};
|
||||||
|
|
||||||
|
Alpine.data('data', () => ({
|
||||||
|
fullscreen: false,
|
||||||
|
terminalActive: false,
|
||||||
|
message: '(connection closed)',
|
||||||
|
init() {
|
||||||
|
this.$watch('terminalActive', (value) => {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (value) {
|
||||||
|
$refs.terminalWrapper.style.display = 'block';
|
||||||
|
this.resizeTerminal();
|
||||||
|
} else {
|
||||||
|
$refs.terminalWrapper.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
makeFullscreen() {
|
||||||
|
this.fullscreen = !this.fullscreen;
|
||||||
|
$nextTick(() => {
|
||||||
|
this.resizeTerminal()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
resizeTerminal() {
|
||||||
|
if (!this.terminalActive) return;
|
||||||
|
|
||||||
|
fitAddon.fit();
|
||||||
|
const height = $refs.terminalWrapper.clientHeight;
|
||||||
|
const rows = height / term._core._renderService._charSizeService.height - 1;
|
||||||
|
var termWidth = term.cols;
|
||||||
|
var termHeight = parseInt(rows.toString(), 10);
|
||||||
|
term.resize(termWidth, termHeight);
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
resize: {
|
||||||
|
cols: termWidth,
|
||||||
|
rows: termHeight
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
initializeWebSocket();
|
||||||
|
</script>
|
||||||
|
@endscript
|
||||||
|
</div>
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<div>
|
|
||||||
<form class="flex flex-col justify-center gap-2 xl:items-end xl:flex-row" wire:submit='runCommand'>
|
|
||||||
<x-forms.input placeholder="ls -l" autofocus id="command" label="Command" required />
|
|
||||||
<x-forms.select label="Server" id="server" required>
|
|
||||||
@foreach ($servers as $server)
|
|
||||||
@if ($loop->first)
|
|
||||||
<option selected value="{{ $server->uuid }}">{{ $server->name }}</option>
|
|
||||||
@else
|
|
||||||
<option value="{{ $server->uuid }}">{{ $server->name }}</option>
|
|
||||||
@endif
|
|
||||||
@endforeach
|
|
||||||
</x-forms.select>
|
|
||||||
<x-forms.button type="submit">Execute Command
|
|
||||||
</x-forms.button>
|
|
||||||
</form>
|
|
||||||
<div class="w-full pt-10 mx-auto">
|
|
||||||
<livewire:activity-monitor header="Command output" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -194,7 +194,7 @@
|
|||||||
@if ($server->settings->force_docker_cleanup)
|
@if ($server->settings->force_docker_cleanup)
|
||||||
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
|
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
|
||||||
label="Docker cleanup frequency" required
|
label="Docker cleanup frequency" required
|
||||||
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every 10 minutes." />
|
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every night at midnight." />
|
||||||
@else
|
@else
|
||||||
<x-forms.input id="server.settings.docker_cleanup_threshold"
|
<x-forms.input id="server.settings.docker_cleanup_threshold"
|
||||||
label="Docker cleanup threshold (%)" required
|
label="Docker cleanup threshold (%)" required
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-4">
|
<div class="pt-4">
|
||||||
If you have any problem, please <a class="underline dark:text-white" href="{{ config('coolify.contact') }}"
|
If you have any problems, please <a class="underline dark:text-white" href="{{ config('coolify.contact') }}"
|
||||||
target="_blank">contact us.</a>
|
target="_blank">contact us.</a>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
34
resources/views/livewire/terminal/index.blade.php
Normal file
34
resources/views/livewire/terminal/index.blade.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<div>
|
||||||
|
<x-slot:title>
|
||||||
|
Terminal | Coolify
|
||||||
|
</x-slot>
|
||||||
|
<h1>Terminal</h1>
|
||||||
|
<div class="flex gap-2 items-end subtitle">
|
||||||
|
<div>Execute commands on your servers and containers without leaving the browser.</div>
|
||||||
|
<x-helper
|
||||||
|
helper="If you're having trouble connecting to your server, make sure that the port is open.<br><br><a class='underline' href='https://coolify.io/docs/knowledge-base/server/firewall/#terminal' target='_blank'>Documentation</a>"></x-helper>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<form class="flex flex-col gap-2 justify-center xl:items-end xl:flex-row"
|
||||||
|
wire:submit="$dispatchSelf('connectToContainer')">
|
||||||
|
<x-forms.select id="server" required wire:model.live="selected_uuid">
|
||||||
|
@foreach ($servers as $server)
|
||||||
|
@if ($loop->first)
|
||||||
|
<option disabled value="default">Select a server or container</option>
|
||||||
|
@endif
|
||||||
|
<option value="{{ $server->uuid }}">{{ $server->name }}</option>
|
||||||
|
@foreach ($containers as $container)
|
||||||
|
@if ($container['server_uuid'] == $server->uuid)
|
||||||
|
<option value="{{ $container['uuid'] }}">
|
||||||
|
{{ $server->name }} -> {{ $container['name'] }}
|
||||||
|
</option>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
|
</x-forms.select>
|
||||||
|
<x-forms.button type="submit">Connect</x-forms.button>
|
||||||
|
</form>
|
||||||
|
<livewire:project.shared.terminal />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -6,7 +6,6 @@ use App\Http\Controllers\OauthController;
|
|||||||
use App\Http\Controllers\UploadController;
|
use App\Http\Controllers\UploadController;
|
||||||
use App\Livewire\Admin\Index as AdminIndex;
|
use App\Livewire\Admin\Index as AdminIndex;
|
||||||
use App\Livewire\Boarding\Index as BoardingIndex;
|
use App\Livewire\Boarding\Index as BoardingIndex;
|
||||||
use App\Livewire\CommandCenter\Index as CommandCenterIndex;
|
|
||||||
use App\Livewire\Dashboard;
|
use App\Livewire\Dashboard;
|
||||||
use App\Livewire\Dev\Compose as Compose;
|
use App\Livewire\Dev\Compose as Compose;
|
||||||
use App\Livewire\ForcePasswordReset;
|
use App\Livewire\ForcePasswordReset;
|
||||||
@@ -64,6 +63,7 @@ use App\Livewire\Tags\Show as TagsShow;
|
|||||||
use App\Livewire\Team\AdminView as TeamAdminView;
|
use App\Livewire\Team\AdminView as TeamAdminView;
|
||||||
use App\Livewire\Team\Index as TeamIndex;
|
use App\Livewire\Team\Index as TeamIndex;
|
||||||
use App\Livewire\Team\Member\Index as TeamMemberIndex;
|
use App\Livewire\Team\Member\Index as TeamMemberIndex;
|
||||||
|
use App\Livewire\Terminal\Index as TerminalIndex;
|
||||||
use App\Livewire\Waitlist\Index as WaitlistIndex;
|
use App\Livewire\Waitlist\Index as WaitlistIndex;
|
||||||
use App\Models\GitlabApp;
|
use App\Models\GitlabApp;
|
||||||
use App\Models\PrivateKey;
|
use App\Models\PrivateKey;
|
||||||
@@ -153,7 +153,14 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
Route::get('/admin', TeamAdminView::class)->name('team.admin-view');
|
Route::get('/admin', TeamAdminView::class)->name('team.admin-view');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/command-center', CommandCenterIndex::class)->name('command-center');
|
Route::get('/terminal', TerminalIndex::class)->name('terminal');
|
||||||
|
Route::post('/terminal/auth', function () {
|
||||||
|
if (auth()->check()) {
|
||||||
|
return response()->json(['authenticated' => true], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['authenticated' => false], 401);
|
||||||
|
})->name('terminal.auth');
|
||||||
|
|
||||||
Route::prefix('invitations')->group(function () {
|
Route::prefix('invitations')->group(function () {
|
||||||
Route::get('/{uuid}', [Controller::class, 'accept_invitation'])->name('team.invitation.accept');
|
Route::get('/{uuid}', [Controller::class, 'accept_invitation'])->name('team.invitation.accept');
|
||||||
@@ -176,20 +183,20 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
Route::get('/deployment', DeploymentIndex::class)->name('project.application.deployment.index');
|
Route::get('/deployment', DeploymentIndex::class)->name('project.application.deployment.index');
|
||||||
Route::get('/deployment/{deployment_uuid}', DeploymentShow::class)->name('project.application.deployment.show');
|
Route::get('/deployment/{deployment_uuid}', DeploymentShow::class)->name('project.application.deployment.show');
|
||||||
Route::get('/logs', Logs::class)->name('project.application.logs');
|
Route::get('/logs', Logs::class)->name('project.application.logs');
|
||||||
Route::get('/command', ExecuteContainerCommand::class)->name('project.application.command');
|
Route::get('/terminal', ExecuteContainerCommand::class)->name('project.application.command');
|
||||||
Route::get('/tasks/{task_uuid}', ScheduledTaskShow::class)->name('project.application.scheduled-tasks');
|
Route::get('/tasks/{task_uuid}', ScheduledTaskShow::class)->name('project.application.scheduled-tasks');
|
||||||
});
|
});
|
||||||
Route::prefix('project/{project_uuid}/{environment_name}/database/{database_uuid}')->group(function () {
|
Route::prefix('project/{project_uuid}/{environment_name}/database/{database_uuid}')->group(function () {
|
||||||
Route::get('/', DatabaseConfiguration::class)->name('project.database.configuration');
|
Route::get('/', DatabaseConfiguration::class)->name('project.database.configuration');
|
||||||
Route::get('/logs', Logs::class)->name('project.database.logs');
|
Route::get('/logs', Logs::class)->name('project.database.logs');
|
||||||
Route::get('/command', ExecuteContainerCommand::class)->name('project.database.command');
|
Route::get('/terminal', ExecuteContainerCommand::class)->name('project.database.command');
|
||||||
Route::get('/backups', DatabaseBackupIndex::class)->name('project.database.backup.index');
|
Route::get('/backups', DatabaseBackupIndex::class)->name('project.database.backup.index');
|
||||||
Route::get('/backups/{backup_uuid}', DatabaseBackupExecution::class)->name('project.database.backup.execution');
|
Route::get('/backups/{backup_uuid}', DatabaseBackupExecution::class)->name('project.database.backup.execution');
|
||||||
});
|
});
|
||||||
Route::prefix('project/{project_uuid}/{environment_name}/service/{service_uuid}')->group(function () {
|
Route::prefix('project/{project_uuid}/{environment_name}/service/{service_uuid}')->group(function () {
|
||||||
Route::get('/', ServiceConfiguration::class)->name('project.service.configuration');
|
Route::get('/', ServiceConfiguration::class)->name('project.service.configuration');
|
||||||
|
Route::get('/terminal', ExecuteContainerCommand::class)->name('project.service.command');
|
||||||
Route::get('/{stack_service_uuid}', ServiceIndex::class)->name('project.service.index');
|
Route::get('/{stack_service_uuid}', ServiceIndex::class)->name('project.service.index');
|
||||||
Route::get('/command', ExecuteContainerCommand::class)->name('project.service.command');
|
|
||||||
Route::get('/tasks/{task_uuid}', ScheduledTaskShow::class)->name('project.service.scheduled-tasks');
|
Route::get('/tasks/{task_uuid}', ScheduledTaskShow::class)->name('project.service.scheduled-tasks');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,30 @@ set -e # Exit immediately if a command exits with a non-zero status
|
|||||||
## $1 could be empty, so we need to disable this check
|
## $1 could be empty, so we need to disable this check
|
||||||
#set -u # Treat unset variables as an error and exit
|
#set -u # Treat unset variables as an error and exit
|
||||||
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
|
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
|
||||||
|
CDN="https://cdn.coollabs.io/coolify"
|
||||||
|
DATE=$(date +"%Y%m%d-%H%M%S")
|
||||||
|
|
||||||
VERSION="1.4"
|
VERSION="1.5"
|
||||||
DOCKER_VERSION="26.0"
|
DOCKER_VERSION="26.0"
|
||||||
|
|
||||||
CDN="https://cdn.coollabs.io/coolify"
|
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs}
|
||||||
|
mkdir -p /data/coolify/ssh/{keys,mux}
|
||||||
|
mkdir -p /data/coolify/proxy/dynamic
|
||||||
|
|
||||||
|
chown -R 9999:root /data/coolify
|
||||||
|
chmod -R 700 /data/coolify
|
||||||
|
|
||||||
|
INSTALLATION_LOG_WITH_DATE="/data/coolify/source/installation-${DATE}.log"
|
||||||
|
|
||||||
|
exec > >(tee -a $INSTALLATION_LOG_WITH_DATE) 2>&1
|
||||||
|
|
||||||
|
getAJoke() {
|
||||||
|
JOKES=$(curl -s --max-time 2 https://v2.jokeapi.dev/joke/Programming?format=txt&type=single&amount=1 || true)
|
||||||
|
if [ "$JOKES" != "" ]; then
|
||||||
|
echo -e " - Until then, here's a joke for you:\n"
|
||||||
|
echo -e "$JOKES\n"
|
||||||
|
fi
|
||||||
|
}
|
||||||
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
|
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
|
||||||
ENV_FILE="/data/coolify/source/.env"
|
ENV_FILE="/data/coolify/source/.env"
|
||||||
|
|
||||||
@@ -46,12 +65,16 @@ fi
|
|||||||
|
|
||||||
LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $2}' | tr -d ',')
|
LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $2}' | tr -d ',')
|
||||||
LATEST_HELPER_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $6}' | tr -d ',')
|
LATEST_HELPER_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $6}' | tr -d ',')
|
||||||
|
LATEST_REALTIME_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $8}' | tr -d ',')
|
||||||
|
|
||||||
if [ -z "$LATEST_HELPER_VERSION" ]; then
|
if [ -z "$LATEST_HELPER_VERSION" ]; then
|
||||||
LATEST_HELPER_VERSION=latest
|
LATEST_HELPER_VERSION=latest
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DATE=$(date +"%Y%m%d-%H%M%S")
|
if [ -z "$LATEST_REALTIME_VERSION" ]; then
|
||||||
|
LATEST_REALTIME_VERSION=latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
if [ $EUID != 0 ]; then
|
if [ $EUID != 0 ]; then
|
||||||
echo "Please run as root"
|
echo "Please run as root"
|
||||||
@@ -73,18 +96,29 @@ if [ "$1" != "" ]; then
|
|||||||
LATEST_VERSION="${LATEST_VERSION#v}"
|
LATEST_VERSION="${LATEST_VERSION#v}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "-------------"
|
echo -e "\033[0;35m"
|
||||||
echo -e "Welcome to Coolify v4 beta installer!"
|
cat << "EOF"
|
||||||
echo -e "This script will install everything for you."
|
_____ _ _ __
|
||||||
|
/ ____| | (_)/ _|
|
||||||
|
| | ___ ___ | |_| |_ _ _
|
||||||
|
| | / _ \ / _ \| | | _| | | |
|
||||||
|
| |___| (_) | (_) | | | | | |_| |
|
||||||
|
\_____\___/ \___/|_|_|_| \__, |
|
||||||
|
__/ |
|
||||||
|
|___/
|
||||||
|
EOF
|
||||||
|
echo -e "\033[0m"
|
||||||
|
echo -e "Welcome to Coolify Installer!"
|
||||||
|
echo -e "This script will install everything for you. Sit back and relax."
|
||||||
echo -e "Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh\n"
|
echo -e "Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh\n"
|
||||||
echo -e "-------------"
|
echo -e "---------------------------------------------"
|
||||||
|
echo "| Operating System | $OS_TYPE $OS_VERSION"
|
||||||
echo "OS: $OS_TYPE $OS_VERSION"
|
echo "| Docker | $DOCKER_VERSION"
|
||||||
echo "Coolify version: $LATEST_VERSION"
|
echo "| Coolify | $LATEST_VERSION"
|
||||||
echo "Helper version: $LATEST_HELPER_VERSION"
|
echo "| Helper | $LATEST_HELPER_VERSION"
|
||||||
|
echo "| Realtime | $LATEST_REALTIME_VERSION"
|
||||||
echo -e "-------------"
|
echo -e "---------------------------------------------\n"
|
||||||
echo "Installing required packages..."
|
echo -e "1. Installing required packages (curl, wget, git, jq). "
|
||||||
|
|
||||||
case "$OS_TYPE" in
|
case "$OS_TYPE" in
|
||||||
arch)
|
arch)
|
||||||
@@ -122,24 +156,26 @@ sles | opensuse-leap | opensuse-tumbleweed)
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
echo -e "2. Check OpenSSH server configuration. "
|
||||||
|
|
||||||
# Detect OpenSSH server
|
# Detect OpenSSH server
|
||||||
SSH_DETECTED=false
|
SSH_DETECTED=false
|
||||||
if [ -x "$(command -v systemctl)" ]; then
|
if [ -x "$(command -v systemctl)" ]; then
|
||||||
if systemctl status sshd >/dev/null 2>&1; then
|
if systemctl status sshd >/dev/null 2>&1; then
|
||||||
echo "OpenSSH server is installed."
|
echo " - OpenSSH server is installed."
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
fi
|
elif systemctl status ssh >/dev/null 2>&1; then
|
||||||
if systemctl status ssh >/dev/null 2>&1; then
|
echo " - OpenSSH server is installed."
|
||||||
echo "OpenSSH server is installed."
|
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
fi
|
fi
|
||||||
elif [ -x "$(command -v service)" ]; then
|
elif [ -x "$(command -v service)" ]; then
|
||||||
if service sshd status >/dev/null 2>&1; then
|
if service sshd status >/dev/null 2>&1; then
|
||||||
echo "OpenSSH server is installed."
|
echo " - OpenSSH server is installed."
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
fi
|
elif service ssh status >/dev/null 2>&1; then
|
||||||
if service ssh status >/dev/null 2>&1; then
|
echo " - OpenSSH server is installed."
|
||||||
echo "OpenSSH server is installed."
|
|
||||||
SSH_DETECTED=true
|
SSH_DETECTED=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -151,104 +187,91 @@ if [ "$SSH_DETECTED" = "false" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect SSH PermitRootLogin
|
# Detect SSH PermitRootLogin
|
||||||
SSH_PERMIT_ROOT_LOGIN=false
|
SSH_PERMIT_ROOT_LOGIN=$(sshd -T | grep -i "permitrootlogin" | awk '{print $2}') || true
|
||||||
SSH_PERMIT_ROOT_LOGIN_CONFIG=$(grep "^PermitRootLogin" /etc/ssh/sshd_config | awk '{print $2}') || SSH_PERMIT_ROOT_LOGIN_CONFIG="N/A (commented out or not found at all)"
|
if [ "$SSH_PERMIT_ROOT_LOGIN" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "without-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "prohibit-password" ]; then
|
||||||
if [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "prohibit-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN_CONFIG" = "without-password" ]; then
|
echo " - SSH PermitRootLogin is enabled."
|
||||||
echo "PermitRootLogin is enabled."
|
else
|
||||||
SSH_PERMIT_ROOT_LOGIN=true
|
echo " - SSH PermitRootLogin is disabled."
|
||||||
fi
|
echo " If you have problems with SSH, please read this: https://coolify.io/docs/knowledge-base/server/openssh"
|
||||||
|
|
||||||
if [ "$SSH_PERMIT_ROOT_LOGIN" != "true" ]; then
|
|
||||||
echo "###############################################################################"
|
|
||||||
echo "WARNING: PermitRootLogin is not enabled in /etc/ssh/sshd_config."
|
|
||||||
echo -e "It is set to $SSH_PERMIT_ROOT_LOGIN_CONFIG. Should be prohibit-password, yes or without-password.\n"
|
|
||||||
echo -e "Please make sure it is set, otherwise Coolify cannot connect to the host system. \n"
|
|
||||||
echo "###############################################################################"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect if docker is installed via snap
|
# Detect if docker is installed via snap
|
||||||
if [ -x "$(command -v snap)" ]; then
|
if [ -x "$(command -v snap)" ]; then
|
||||||
if snap list | grep -q docker; then
|
SNAP_DOCKER_INSTALLED=$(snap list docker >/dev/null 2>&1 && echo "true" || echo "false")
|
||||||
echo "Docker is installed via snap."
|
if [ "$SNAP_DOCKER_INSTALLED" = "true" ]; then
|
||||||
echo "Please note that Coolify does not support Docker installed via snap."
|
echo " - Docker is installed via snap."
|
||||||
echo "Please remove Docker with snap (snap remove docker) and reexecute this script."
|
echo " Please note that Coolify does not support Docker installed via snap."
|
||||||
|
echo " Please remove Docker with snap (snap remove docker) and reexecute this script."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo -e "3. Check Docker Installation. "
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
|
echo " - Docker is not installed. Installing Docker. It may take a while."
|
||||||
|
getAJoke
|
||||||
case "$OS_TYPE" in
|
case "$OS_TYPE" in
|
||||||
"almalinux")
|
"almalinux")
|
||||||
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
|
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1
|
||||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo "Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
systemctl start docker
|
systemctl start docker >/dev/null 2>&1
|
||||||
systemctl enable docker
|
systemctl enable docker >/dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
"alpine")
|
"alpine")
|
||||||
apk add docker docker-cli-compose
|
apk add docker docker-cli-compose >/dev/null 2>&1
|
||||||
rc-update add docker default
|
rc-update add docker default >/dev/null 2>&1
|
||||||
service docker start
|
service docker start >/dev/null 2>&1
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo "Docker installed successfully."
|
echo " - Failed to install Docker with apk. Try to install it manually."
|
||||||
else
|
echo " Please visit https://wiki.alpinelinux.org/wiki/Docker for more information."
|
||||||
echo "Failed to install Docker with apk. Try to install it manually."
|
exit 1
|
||||||
echo "Please visit https://wiki.alpinelinux.org/wiki/Docker for more information."
|
|
||||||
exit
|
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"arch")
|
"arch")
|
||||||
pacman -Sy docker docker-compose --noconfirm
|
pacman -Sy docker docker-compose --noconfirm >/dev/null 2>&1
|
||||||
systemctl enable docker.service
|
systemctl enable docker.service >/dev/null 2>&1
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo "Docker installed successfully."
|
echo " - Failed to install Docker with pacman. Try to install it manually."
|
||||||
else
|
echo " Please visit https://wiki.archlinux.org/title/docker for more information."
|
||||||
echo "Failed to install Docker with pacman. Try to install it manually."
|
exit 1
|
||||||
echo "Please visit https://wiki.archlinux.org/title/docker for more information."
|
|
||||||
exit
|
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"amzn")
|
"amzn")
|
||||||
dnf install docker -y
|
dnf install docker -y >/dev/null 2>&1
|
||||||
DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker}
|
DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker}
|
||||||
mkdir -p $DOCKER_CONFIG/cli-plugins
|
mkdir -p $DOCKER_CONFIG/cli-plugins >/dev/null 2>&1
|
||||||
curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose
|
curl -sL https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1
|
||||||
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1
|
||||||
systemctl start docker
|
systemctl start docker >/dev/null 2>&1
|
||||||
systemctl enable docker
|
systemctl enable docker >/dev/null 2>&1
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo "Docker installed successfully."
|
echo " - Failed to install Docker with dnf. Try to install it manually."
|
||||||
else
|
echo " Please visit https://www.cyberciti.biz/faq/how-to-install-docker-on-amazon-linux-2/ for more information."
|
||||||
echo "Failed to install Docker with dnf. Try to install it manually."
|
exit 1
|
||||||
echo "Please visit https://www.cyberciti.biz/faq/how-to-install-docker-on-amazon-linux-2/ for more information."
|
|
||||||
exit
|
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# Automated Docker installation
|
curl -s https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh >/dev/null 2>&1
|
||||||
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
if [ -x "$(command -v docker)" ]; then
|
curl -s https://get.docker.com | sh -s -- --version ${DOCKER_VERSION} >/dev/null 2>&1
|
||||||
echo "Docker installed successfully."
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
else
|
echo " - Docker installation failed."
|
||||||
echo "Docker installation failed with Rancher script. Trying with official script."
|
echo " Maybe your OS is not supported?"
|
||||||
curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION}
|
echo " - Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
||||||
if [ -x "$(command -v docker)" ]; then
|
|
||||||
echo "Docker installed successfully."
|
|
||||||
else
|
|
||||||
echo "Docker installation failed with official script."
|
|
||||||
echo "Maybe your OS is not supported?"
|
|
||||||
echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
esac
|
esac
|
||||||
|
echo " - Docker installed successfully."
|
||||||
|
else
|
||||||
|
echo " - Docker is installed."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "-------------"
|
echo -e "4. Check Docker Configuration. "
|
||||||
echo -e "Check Docker Configuration..."
|
|
||||||
mkdir -p /etc/docker
|
mkdir -p /etc/docker
|
||||||
# shellcheck disable=SC2015
|
# shellcheck disable=SC2015
|
||||||
test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE" || cat >/etc/docker/daemon.json <<EOL
|
test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE" || cat >/etc/docker/daemon.json <<EOL
|
||||||
@@ -277,34 +300,33 @@ fi
|
|||||||
mv "$TEMP_FILE" /etc/docker/daemon.json
|
mv "$TEMP_FILE" /etc/docker/daemon.json
|
||||||
|
|
||||||
restart_docker_service() {
|
restart_docker_service() {
|
||||||
|
|
||||||
# Check if systemctl is available
|
# Check if systemctl is available
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
echo "Using systemctl to restart Docker..."
|
echo " - Using systemctl to restart Docker."
|
||||||
systemctl restart docker
|
systemctl restart docker
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "Docker restarted successfully using systemctl."
|
echo " - Docker restarted successfully using systemctl."
|
||||||
else
|
else
|
||||||
echo "Failed to restart Docker using systemctl."
|
echo " - Failed to restart Docker using systemctl."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if service command is available
|
# Check if service command is available
|
||||||
elif command -v service >/dev/null 2>&1; then
|
elif command -v service >/dev/null 2>&1; then
|
||||||
echo "Using service command to restart Docker..."
|
echo " - Using service command to restart Docker."
|
||||||
service docker restart
|
service docker restart
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "Docker restarted successfully using service."
|
echo " - Docker restarted successfully using service."
|
||||||
else
|
else
|
||||||
echo "Failed to restart Docker using service."
|
echo " - Failed to restart Docker using service."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If neither systemctl nor service is available
|
# If neither systemctl nor service is available
|
||||||
else
|
else
|
||||||
echo "Neither systemctl nor service command is available on this system."
|
echo " - Neither systemctl nor service command is available on this system."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -312,39 +334,30 @@ restart_docker_service() {
|
|||||||
if [ -s /etc/docker/daemon.json.original-"$DATE" ]; then
|
if [ -s /etc/docker/daemon.json.original-"$DATE" ]; then
|
||||||
DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-"$DATE"))
|
DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-"$DATE"))
|
||||||
if [ "$DIFF" != "" ]; then
|
if [ "$DIFF" != "" ]; then
|
||||||
echo "Docker configuration updated, restart docker daemon..."
|
echo " - Docker configuration updated, restart docker daemon..."
|
||||||
restart_docker_service
|
restart_docker_service
|
||||||
else
|
else
|
||||||
echo "Docker configuration is up to date."
|
echo " - Docker configuration is up to date."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Docker configuration updated, restart docker daemon..."
|
echo " - Docker configuration updated, restart docker daemon..."
|
||||||
restart_docker_service
|
restart_docker_service
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "-------------"
|
echo -e "5. Download required files from CDN. "
|
||||||
|
|
||||||
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs}
|
|
||||||
mkdir -p /data/coolify/ssh/{keys,mux}
|
|
||||||
mkdir -p /data/coolify/proxy/dynamic
|
|
||||||
|
|
||||||
chown -R 9999:root /data/coolify
|
|
||||||
chmod -R 700 /data/coolify
|
|
||||||
|
|
||||||
echo "Downloading required files from CDN..."
|
|
||||||
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
||||||
curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
||||||
curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
|
curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
|
||||||
curl -fsSL $CDN/upgrade.sh -o /data/coolify/source/upgrade.sh
|
curl -fsSL $CDN/upgrade.sh -o /data/coolify/source/upgrade.sh
|
||||||
|
|
||||||
|
echo -e "6. Make backup of .env to .env-$DATE"
|
||||||
|
|
||||||
# Copy .env.example if .env does not exist
|
# Copy .env.example if .env does not exist
|
||||||
if [ -f $ENV_FILE ]; then
|
if [ -f $ENV_FILE ]; then
|
||||||
echo "File exists: $ENV_FILE"
|
|
||||||
echo "Copying .env to .env-$DATE"
|
|
||||||
cp $ENV_FILE $ENV_FILE-$DATE
|
cp $ENV_FILE $ENV_FILE-$DATE
|
||||||
else
|
else
|
||||||
echo "File does not exist: $ENV_FILE"
|
echo " - File does not exist: $ENV_FILE"
|
||||||
echo "Copying .env.production to .env-$DATE"
|
echo " - Copying .env.production to .env-$DATE"
|
||||||
cp /data/coolify/source/.env.production $ENV_FILE-$DATE
|
cp /data/coolify/source/.env.production $ENV_FILE-$DATE
|
||||||
# Generate a secure APP_ID and APP_KEY
|
# Generate a secure APP_ID and APP_KEY
|
||||||
sed -i "s|^APP_ID=.*|APP_ID=$(openssl rand -hex 16)|" "$ENV_FILE-$DATE"
|
sed -i "s|^APP_ID=.*|APP_ID=$(openssl rand -hex 16)|" "$ENV_FILE-$DATE"
|
||||||
@@ -365,7 +378,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Merge .env and .env.production. New values will be added to .env
|
# Merge .env and .env.production. New values will be added to .env
|
||||||
echo "Updating .env with new values (if necessary)..."
|
echo -e "7. Propagating .env with new values - if necessary."
|
||||||
awk -F '=' '!seen[$1]++' "$ENV_FILE-$DATE" /data/coolify/source/.env.production > $ENV_FILE
|
awk -F '=' '!seen[$1]++' "$ENV_FILE-$DATE" /data/coolify/source/.env.production > $ENV_FILE
|
||||||
|
|
||||||
if [ "$AUTOUPDATE" = "false" ]; then
|
if [ "$AUTOUPDATE" = "false" ]; then
|
||||||
@@ -375,37 +388,122 @@ if [ "$AUTOUPDATE" = "false" ]; then
|
|||||||
sed -i "s|AUTOUPDATE=.*|AUTOUPDATE=false|g" /data/coolify/source/.env
|
sed -i "s|AUTOUPDATE=.*|AUTOUPDATE=false|g" /data/coolify/source/.env
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
echo -e "8. Checking for SSH key for localhost access."
|
||||||
# Generate an ssh key (ed25519) at /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
if [ ! -f /data/coolify/ssh/keys/id.root@host.docker.internal ]; then
|
|
||||||
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.root@host.docker.internal -q -N "" -C root@coolify
|
|
||||||
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
|
|
||||||
fi
|
|
||||||
|
|
||||||
addSshKey() {
|
|
||||||
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >>~/.ssh/authorized_keys
|
|
||||||
chmod 600 ~/.ssh/authorized_keys
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ ! -f ~/.ssh/authorized_keys ]; then
|
if [ ! -f ~/.ssh/authorized_keys ]; then
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
chmod 700 ~/.ssh
|
chmod 700 ~/.ssh
|
||||||
touch ~/.ssh/authorized_keys
|
touch ~/.ssh/authorized_keys
|
||||||
addSshKey
|
chmod 600 ~/.ssh/authorized_keys
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! grep -qw "root@coolify" ~/.ssh/authorized_keys; then
|
checkSshKeyInAuthorizedKeys() {
|
||||||
addSshKey
|
grep -qw "root@coolify" ~/.ssh/authorized_keys
|
||||||
fi
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}"
|
checkSshKeyInCoolifyData() {
|
||||||
|
[ -s /data/coolify/ssh/keys/id.root@host.docker.internal ]
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
generateAuthorizedKeys() {
|
||||||
|
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
||||||
|
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
||||||
|
rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
||||||
|
}
|
||||||
|
generateSshKey() {
|
||||||
|
echo " - Generating SSH key."
|
||||||
|
ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.root@host.docker.internal -q -N "" -C root@coolify
|
||||||
|
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
generateAuthorizedKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
syncSshKeys() {
|
||||||
|
DB_RUNNING=$(docker inspect coolify-db --format '{{ .State.Status }}' 2>/dev/null)
|
||||||
|
# Check if SSH key exists in Coolify data but not in authorized_keys
|
||||||
|
if checkSshKeyInCoolifyData && ! checkSshKeyInAuthorizedKeys; then
|
||||||
|
# Add the existing Coolify SSH key to authorized_keys
|
||||||
|
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
||||||
|
# Check if SSH key exists in authorized_keys but not in Coolify data
|
||||||
|
elif checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then
|
||||||
|
# Ensure Coolify DB is running before proceeding
|
||||||
|
if [ "$DB_RUNNING" = "running" ]; then
|
||||||
|
# Retrieve DB user and SSH key from Coolify database
|
||||||
|
DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+')
|
||||||
|
DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t)
|
||||||
|
|
||||||
|
if [ -z "$DB_SSH_KEY" ]; then
|
||||||
|
# If no key found in DB, generate a new one
|
||||||
|
echo " - SSH key not found in database. Generating new key."
|
||||||
|
generateSshKey
|
||||||
|
else
|
||||||
|
# If key found in DB, save it and update authorized_keys
|
||||||
|
echo " - SSH key found in database. Saving to file."
|
||||||
|
echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
|
||||||
|
# Generate public key from private key and update authorized_keys
|
||||||
|
ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
||||||
|
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
||||||
|
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
||||||
|
rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
||||||
|
chmod 600 ~/.ssh/authorized_keys
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# If SSH key doesn't exist in either location
|
||||||
|
elif ! checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then
|
||||||
|
# Ensure Coolify DB is running before proceeding
|
||||||
|
if [ "$DB_RUNNING" = "running" ]; then
|
||||||
|
# Retrieve DB user and SSH key from Coolify database
|
||||||
|
DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+')
|
||||||
|
DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t)
|
||||||
|
if [ -z "$DB_SSH_KEY" ]; then
|
||||||
|
# If no key found in DB, generate a new one
|
||||||
|
echo " - SSH key not found in database. Generating new key."
|
||||||
|
generateSshKey
|
||||||
|
else
|
||||||
|
# If key found in DB, save it and update authorized_keys
|
||||||
|
echo " - SSH key found in database. Saving to file."
|
||||||
|
echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal
|
||||||
|
ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub
|
||||||
|
sed -i "/root@coolify/d" ~/.ssh/authorized_keys
|
||||||
|
cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
generateSshKey
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
syncSshKeys || true
|
||||||
|
|
||||||
|
chown -R 9999:root /data/coolify
|
||||||
|
chmod -R 700 /data/coolify
|
||||||
|
|
||||||
|
echo -e "9. Installing Coolify ($LATEST_VERSION)"
|
||||||
|
echo -e " - It could take a while based on your server's performance, network speed, stars, etc."
|
||||||
|
echo -e " - Please wait."
|
||||||
|
getAJoke
|
||||||
|
|
||||||
|
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" >/dev/null 2>&1
|
||||||
|
echo " - Coolify installed successfully."
|
||||||
rm -f $ENV_FILE-$DATE
|
rm -f $ENV_FILE-$DATE
|
||||||
echo "Waiting for 20 seconds for Coolify to be ready..."
|
|
||||||
|
echo " - Waiting for 20 seconds for Coolify (database migrations) to be ready."
|
||||||
|
getAJoke
|
||||||
|
|
||||||
sleep 20
|
sleep 20
|
||||||
echo "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started."
|
echo -e "\033[0;35m
|
||||||
echo -e "\nCongratulations! Your Coolify instance is ready to use.\n"
|
____ _ _ _ _ _
|
||||||
|
/ ___|___ _ __ __ _ _ __ __ _| |_ _ _| | __ _| |_(_) ___ _ __ ___| |
|
||||||
echo -e "Make sure you backup your /data/coolify/source/.env file to a safe location, outside of this server.\n"
|
| | / _ \| '_ \ / _\` | '__/ _\` | __| | | | |/ _\` | __| |/ _ \| '_ \/ __| |
|
||||||
|
| |__| (_) | | | | (_| | | | (_| | |_| |_| | | (_| | |_| | (_) | | | \__ \_|
|
||||||
|
\____\___/|_| |_|\__, |_| \__,_|\__|\__,_|_|\__,_|\__|_|\___/|_| |_|___(_)
|
||||||
|
|___/
|
||||||
|
\033[0m"
|
||||||
|
echo -e "\nYour instance is ready to use."
|
||||||
|
echo -e "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started.\n"
|
||||||
|
echo -e "WARNING: We recommend you to backup your /data/coolify/source/.env file to a safe location, outside of this server."
|
||||||
cp /data/coolify/source/.env /data/coolify/source/.env.backup
|
cp /data/coolify/source/.env /data/coolify/source/.env.backup
|
||||||
echo -e "Your .env file has been copied to /data/coolify/source/.env.backup\n"
|
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.333"
|
"version": "4.0.0-beta.339"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.334"
|
"version": "4.0.0-beta.340"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.1"
|
"version": "1.0.1"
|
||||||
|
},
|
||||||
|
"realtime": {
|
||||||
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user