diff --git a/.dockerignore b/.dockerignore
index d6abd1451..0adca0b32 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -22,3 +22,6 @@ yarn-error.log
/_data
.rnd
/.ssh
+.ignition.json
+.env.dusk.local
+docker/coolify-realtime/node_modules
diff --git a/.env.development.example b/.env.development.example
index 3023a21a6..d4daed4f7 100644
--- a/.env.development.example
+++ b/.env.development.example
@@ -6,7 +6,7 @@ APP_KEY=
APP_URL=http://localhost
APP_PORT=8000
APP_DEBUG=true
-SSH_MUX_ENABLED=false
+SSH_MUX_ENABLED=true
# PostgreSQL Database Configuration
DB_DATABASE=coolify
@@ -19,11 +19,7 @@ DB_PORT=5432
# Set to true to enable Ray
RAY_ENABLED=false
# Set custom ray port
-RAY_PORT=
-
-# Clockwork Configuration
-CLOCKWORK_ENABLED=false
-CLOCKWORK_QUEUE_COLLECT=true
+# RAY_PORT=
# Enable Laravel Telescope for debugging
TELESCOPE_ENABLED=false
diff --git a/.env.dusk.ci b/.env.dusk.ci
new file mode 100644
index 000000000..9660de7b4
--- /dev/null
+++ b/.env.dusk.ci
@@ -0,0 +1,15 @@
+APP_ENV=production
+APP_NAME="Coolify Staging"
+APP_ID=development
+APP_KEY=
+APP_URL=http://localhost
+APP_PORT=8000
+SSH_MUX_ENABLED=true
+
+# PostgreSQL Database Configuration
+DB_DATABASE=coolify
+DB_USERNAME=coolify
+DB_PASSWORD=password
+DB_HOST=localhost
+DB_PORT=5432
+
diff --git a/.env.windows-docker-desktop.example b/.env.windows-docker-desktop.example
index 02a5a4174..b067b4c5c 100644
--- a/.env.windows-docker-desktop.example
+++ b/.env.windows-docker-desktop.example
@@ -4,6 +4,7 @@ APP_ID=coolify-windows-docker-desktop
APP_NAME=Coolify
APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=
+DB_USERNAME=coolify
DB_PASSWORD=coolify
REDIS_PASSWORD=coolify
diff --git a/.gitattributes b/.gitattributes
index fcb21d396..c48a5898b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -8,4 +8,4 @@
/.github export-ignore
CHANGELOG.md export-ignore
-.styleci.yml export-ignore
+.styleci.yml export-ignore
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/01_BUG_REPORT.yml
similarity index 100%
rename from .github/ISSUE_TEMPLATE/BUG_REPORT.yml
rename to .github/ISSUE_TEMPLATE/01_BUG_REPORT.yml
diff --git a/.github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml b/.github/ISSUE_TEMPLATE/02_ENHANCEMENT_BOUNTY.yml
similarity index 100%
rename from .github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml
rename to .github/ISSUE_TEMPLATE/02_ENHANCEMENT_BOUNTY.yml
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 3ded74ce3..5afe00a30 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1 +1,13 @@
-> Always use `next` branch as destination branch for PRs, not `main`
+## Submit Checklist (REMOVE THIS SECTION BEFORE SUBMITTING)
+- [ ] I have selected the `next` branch as the destination for my PR, not `main`.
+- [ ] I have listed all changes in the `Changes` section.
+- [ ] I have filled out the `Issues` section with the issue/discussion link(s) (if applicable).
+- [ ] I have tested my changes.
+- [ ] I have considered backwards compatibility.
+- [ ] I have removed this checklist and any unused sections.
+
+## Changes
+-
+
+## Issues
+- fix #
diff --git a/.github/workflows/browser-tests.yml b/.github/workflows/browser-tests.yml
new file mode 100644
index 000000000..b06c9e97c
--- /dev/null
+++ b/.github/workflows/browser-tests.yml
@@ -0,0 +1,65 @@
+name: Dusk
+on:
+ push:
+ branches: [ "not-existing" ]
+jobs:
+ dusk:
+ runs-on: ubuntu-latest
+
+ services:
+ redis:
+ image: redis
+ env:
+ REDIS_HOST: localhost
+ REDIS_PORT: 6379
+ ports:
+ - 6379:6379
+ options: >-
+ --health-cmd "redis-cli ping"
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up PostgreSQL
+ run: |
+ sudo systemctl start postgresql
+ sudo -u postgres psql -c "CREATE DATABASE coolify;"
+ sudo -u postgres psql -c "CREATE USER coolify WITH PASSWORD 'password';"
+ sudo -u postgres psql -c "ALTER ROLE coolify SET client_encoding TO 'utf8';"
+ sudo -u postgres psql -c "ALTER ROLE coolify SET default_transaction_isolation TO 'read committed';"
+ sudo -u postgres psql -c "ALTER ROLE coolify SET timezone TO 'UTC';"
+ sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE coolify TO coolify;"
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.2'
+ - name: Copy .env
+ run: cp .env.dusk.ci .env
+ - name: Install Dependencies
+ run: composer install --no-progress --prefer-dist --optimize-autoloader
+ - name: Generate key
+ run: php artisan key:generate
+ - name: Install Chrome binaries
+ run: php artisan dusk:chrome-driver --detect
+ - name: Start Chrome Driver
+ run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=4444 &
+ - name: Build assets
+ run: npm install && npm run build
+ - name: Run Laravel Server
+ run: php artisan serve --no-reload &
+ - name: Execute tests
+ run: php artisan dusk
+ - name: Upload Screenshots
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: screenshots
+ path: tests/Browser/screenshots
+ - name: Upload Console Logs
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: console
+ path: tests/Browser/console
diff --git a/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml b/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml
new file mode 100644
index 000000000..d00853964
--- /dev/null
+++ b/.github/workflows/chore-lock-closed-issues-discussions-and-prs.yml
@@ -0,0 +1,17 @@
+name: Lock closed Issues, Discussions, and PRs
+
+on:
+ schedule:
+ - cron: '0 1 * * *'
+
+jobs:
+ lock-threads:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Lock threads after 30 days of inactivity
+ uses: dessant/lock-threads@v5
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ issue-inactive-days: '30'
+ pr-inactive-days: '30'
+ discussion-inactive-days: '30'
diff --git a/.github/workflows/chore-manage-stale-issues-and-prs.yml b/.github/workflows/chore-manage-stale-issues-and-prs.yml
new file mode 100644
index 000000000..2afc996cb
--- /dev/null
+++ b/.github/workflows/chore-manage-stale-issues-and-prs.yml
@@ -0,0 +1,28 @@
+name: Manage Stale Issues and PRs
+
+on:
+ schedule:
+ - cron: '0 2 * * *'
+
+jobs:
+ manage-stale:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Manage stale issues and PRs
+ uses: actions/stale@v9
+ id: stale
+ with:
+ stale-issue-message: 'This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information.'
+ stale-pr-message: 'This pull request will be automatically closed in a few days if no response is received. Please update your PR or comment if you would like to continue working on it.'
+ close-issue-message: 'This issue has been automatically closed due to inactivity.'
+ close-pr-message: 'This pull request has been automatically closed due to inactivity.'
+ days-before-stale: 14
+ days-before-close: 7
+ stale-issue-label: '⏱︎ Stale'
+ stale-pr-label: '⏱︎ Stale'
+ only-labels: '💤 Waiting for feedback'
+ remove-stale-when-updated: true
+ operations-per-run: 100
+ labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback'
+ close-issue-reason: 'not_planned'
+ exempt-all-milestones: false
diff --git a/.github/workflows/remove-labels-and-assignees-on-close.yml b/.github/workflows/chore-remove-labels-and-assignees-on-close.yml
similarity index 84%
rename from .github/workflows/remove-labels-and-assignees-on-close.yml
rename to .github/workflows/chore-remove-labels-and-assignees-on-close.yml
index 04d62623c..ea097e328 100644
--- a/.github/workflows/remove-labels-and-assignees-on-close.yml
+++ b/.github/workflows/chore-remove-labels-and-assignees-on-close.yml
@@ -18,7 +18,7 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
-
+
async function processIssue(issueNumber) {
try {
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
@@ -65,11 +65,14 @@ jobs:
}
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);
+ const pr = context.payload.pull_request;
+ if (pr.body) {
+ const issueReferences = pr.body.match(/#(\d+)/g);
+ if (issueReferences) {
+ for (const reference of issueReferences) {
+ const issueNumber = parseInt(reference.substring(1));
+ await processIssue(issueNumber);
+ }
+ }
}
}
diff --git a/.github/workflows/coolify-helper-next.yml b/.github/workflows/coolify-helper-next.yml
index 4add8516e..4354294b1 100644
--- a/.github/workflows/coolify-helper-next.yml
+++ b/.github/workflows/coolify-helper-next.yml
@@ -1,4 +1,4 @@
-name: Coolify Helper Image Development (v4)
+name: Coolify Helper Image Development
on:
push:
@@ -8,7 +8,8 @@ on:
- docker/coolify-helper/Dockerfile
env:
- REGISTRY: ghcr.io
+ GITHUB_REGISTRY: ghcr.io
+ DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify-helper"
jobs:
@@ -19,25 +20,36 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- - name: Login to ghcr.io
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
with:
- no-cache: true
context: .
file: docker/coolify-helper/Dockerfile
platforms: linux/amd64
push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
labels: |
coolify.managed=true
aarch64:
@@ -47,27 +59,39 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- - name: Login to ghcr.io
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
with:
- no-cache: true
context: .
file: docker/coolify-helper/Dockerfile
platforms: linux/aarch64
push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
labels: |
coolify.managed=true
+
merge-manifest:
runs-on: ubuntu-latest
permissions:
@@ -75,25 +99,42 @@ jobs:
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: actions/checkout@v4
+ - uses: docker/setup-buildx-action@v3
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- - name: Create & publish manifest
+
+ - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
run: |
- docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
+ docker buildx imagetools create \
+ --append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:next
+
+ - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
+ run: |
+ docker buildx imagetools create \
+ --append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:next
+
- uses: sarisia/actions-status-discord@v1
if: always()
with:
diff --git a/.github/workflows/coolify-helper.yml b/.github/workflows/coolify-helper.yml
index fd4be2f11..6d852a2b3 100644
--- a/.github/workflows/coolify-helper.yml
+++ b/.github/workflows/coolify-helper.yml
@@ -1,4 +1,4 @@
-name: Coolify Helper Image (v4)
+name: Coolify Helper Image
on:
push:
@@ -8,7 +8,8 @@ on:
- docker/coolify-helper/Dockerfile
env:
- REGISTRY: ghcr.io
+ GITHUB_REGISTRY: ghcr.io
+ DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify-helper"
jobs:
@@ -19,25 +20,36 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- - name: Login to ghcr.io
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
with:
- no-cache: true
context: .
file: docker/coolify-helper/Dockerfile
platforms: linux/amd64
push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
labels: |
coolify.managed=true
aarch64:
@@ -47,25 +59,36 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- - name: Login to ghcr.io
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
- name: Get Version
id: version
run: |
- echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
+ echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
with:
- no-cache: true
context: .
file: docker/coolify-helper/Dockerfile
platforms: linux/aarch64
push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
labels: |
coolify.managed=true
merge-manifest:
@@ -75,26 +98,45 @@ jobs:
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: actions/checkout@v4
+
+ - uses: docker/setup-buildx-action@v3
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
- name: Get Version
id: version
run: |
- echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- - name: Create & publish manifest
+ echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
+
+ - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
run: |
- docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+ docker buildx imagetools create \
+ --append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+
+ - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
+ run: |
+ docker buildx imagetools create \
+ --append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
+
diff --git a/.github/workflows/coolify-production-build.yml b/.github/workflows/coolify-production-build.yml
new file mode 100644
index 000000000..5271143ec
--- /dev/null
+++ b/.github/workflows/coolify-production-build.yml
@@ -0,0 +1,139 @@
+name: Production Build (v4)
+
+on:
+ push:
+ branches: ["main"]
+ paths-ignore:
+ - .github/workflows/coolify-helper.yml
+ - .github/workflows/coolify-helper-next.yml
+ - .github/workflows/coolify-realtime.yml
+ - .github/workflows/coolify-realtime-next.yml
+ - docker/coolify-helper/Dockerfile
+ - docker/coolify-realtime/Dockerfile
+ - docker/testing-host/Dockerfile
+ - templates/**
+
+env:
+ GITHUB_REGISTRY: ghcr.io
+ DOCKER_REGISTRY: docker.io
+ IMAGE_NAME: "coollabsio/coolify"
+
+jobs:
+ amd64:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.GITHUB_REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Get Version
+ id: version
+ run: |
+ echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: docker/prod/Dockerfile
+ platforms: linux/amd64
+ push: true
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
+
+ aarch64:
+ runs-on: [self-hosted, arm64]
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.GITHUB_REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Get Version
+ id: version
+ run: |
+ echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: docker/prod/Dockerfile
+ platforms: linux/aarch64
+ push: true
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
+
+ merge-manifest:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ needs: [amd64, aarch64]
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: docker/setup-buildx-action@v3
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.GITHUB_REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Get Version
+ id: version
+ run: |
+ echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
+
+ - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
+ run: |
+ docker buildx imagetools create \
+ --append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+
+ - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
+ run: |
+ docker buildx imagetools create \
+ --append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+
+ - uses: sarisia/actions-status-discord@v1
+ if: always()
+ with:
+ webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
diff --git a/.github/workflows/coolify-realtime-next.yml b/.github/workflows/coolify-realtime-next.yml
new file mode 100644
index 000000000..ef247170f
--- /dev/null
+++ b/.github/workflows/coolify-realtime-next.yml
@@ -0,0 +1,147 @@
+name: Coolify Realtime Development
+
+on:
+ push:
+ branches: [ "next" ]
+ paths:
+ - .github/workflows/coolify-realtime-next.yml
+ - docker/coolify-realtime/Dockerfile
+ - docker/coolify-realtime/terminal-server.js
+ - docker/coolify-realtime/package.json
+ - docker/coolify-realtime/package-lock.json
+ - docker/coolify-realtime/soketi-entrypoint.sh
+
+env:
+ GITHUB_REGISTRY: ghcr.io
+ DOCKER_REGISTRY: docker.io
+ IMAGE_NAME: "coollabsio/coolify-realtime"
+
+jobs:
+ amd64:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.GITHUB_REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Get Version
+ id: version
+ run: |
+ echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: docker/coolify-realtime/Dockerfile
+ platforms: linux/amd64
+ push: true
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
+ labels: |
+ coolify.managed=true
+
+ aarch64:
+ runs-on: [ self-hosted, arm64 ]
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.GITHUB_REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Get Version
+ id: version
+ run: |
+ echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: docker/coolify-realtime/Dockerfile
+ platforms: linux/aarch64
+ push: true
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
+ labels: |
+ coolify.managed=true
+
+ merge-manifest:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ needs: [ amd64, aarch64 ]
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: docker/setup-buildx-action@v3
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.GITHUB_REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Get Version
+ id: version
+ run: |
+ echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
+
+ - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
+ run: |
+ docker buildx imagetools create \
+ --append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:next
+
+ - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
+ run: |
+ docker buildx imagetools create \
+ --append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:next
+
+ - uses: sarisia/actions-status-discord@v1
+ if: always()
+ with:
+ webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
diff --git a/.github/workflows/coolify-realtime.yml b/.github/workflows/coolify-realtime.yml
index 75e3f1681..9654a21b0 100644
--- a/.github/workflows/coolify-realtime.yml
+++ b/.github/workflows/coolify-realtime.yml
@@ -1,17 +1,19 @@
-name: Coolify Realtime (v4)
+name: Coolify Realtime
on:
push:
- branches: [ "main", "next" ]
+ branches: [ "main" ]
paths:
- .github/workflows/coolify-realtime.yml
- docker/coolify-realtime/Dockerfile
- docker/coolify-realtime/terminal-server.js
- docker/coolify-realtime/package.json
+ - docker/coolify-realtime/package-lock.json
- docker/coolify-realtime/soketi-entrypoint.sh
env:
- REGISTRY: ghcr.io
+ GITHUB_REGISTRY: ghcr.io
+ DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify-realtime"
jobs:
@@ -22,27 +24,39 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- - name: Login to ghcr.io
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
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 }}
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
labels: |
coolify.managed=true
+
aarch64:
runs-on: [ self-hosted, arm64 ]
permissions:
@@ -50,27 +64,39 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- - name: Login to ghcr.io
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
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
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
labels: |
coolify.managed=true
+
merge-manifest:
runs-on: ubuntu-latest
permissions:
@@ -78,25 +104,43 @@ jobs:
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: actions/checkout@v4
+
+ - uses: docker/setup-buildx-action@v3
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- - name: Create & publish manifest
+
+ - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
run: |
- docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+ docker buildx imagetools create \
+ --append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+
+ - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
+ run: |
+ docker buildx imagetools create \
+ --append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+
- uses: sarisia/actions-status-discord@v1
if: always()
with:
diff --git a/.github/workflows/coolify-staging-build.yml b/.github/workflows/coolify-staging-build.yml
new file mode 100644
index 000000000..2c57a36a3
--- /dev/null
+++ b/.github/workflows/coolify-staging-build.yml
@@ -0,0 +1,125 @@
+name: Staging Build
+
+on:
+ push:
+ branches-ignore: ["main", "v3"]
+ paths-ignore:
+ - .github/workflows/coolify-helper.yml
+ - .github/workflows/coolify-helper-next.yml
+ - .github/workflows/coolify-realtime.yml
+ - .github/workflows/coolify-realtime-next.yml
+ - docker/coolify-helper/Dockerfile
+ - docker/coolify-realtime/Dockerfile
+ - docker/testing-host/Dockerfile
+ - templates/**
+
+env:
+ GITHUB_REGISTRY: ghcr.io
+ DOCKER_REGISTRY: docker.io
+ IMAGE_NAME: "coollabsio/coolify"
+
+jobs:
+ amd64:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.GITHUB_REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: docker/prod/Dockerfile
+ platforms: linux/amd64
+ push: true
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
+
+ aarch64:
+ runs-on: [self-hosted, arm64]
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.GITHUB_REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: docker/prod/Dockerfile
+ platforms: linux/aarch64
+ push: true
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
+
+ merge-manifest:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ needs: [amd64, aarch64]
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: docker/setup-buildx-action@v3
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.GITHUB_REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
+ run: |
+ docker buildx imagetools create \
+ --append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
+
+ - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
+ run: |
+ docker buildx imagetools create \
+ --append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
+
+ - uses: sarisia/actions-status-discord@v1
+ if: always()
+ with:
+ webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
diff --git a/.github/workflows/coolify-testing-host.yml b/.github/workflows/coolify-testing-host.yml
index 5fdc32991..95a228114 100644
--- a/.github/workflows/coolify-testing-host.yml
+++ b/.github/workflows/coolify-testing-host.yml
@@ -1,14 +1,15 @@
-name: Coolify Testing Host (v4-non-prod)
+name: Coolify Testing Host
on:
push:
- branches: [ "main", "next" ]
+ branches: [ "next" ]
paths:
- .github/workflows/coolify-testing-host.yml
- docker/testing-host/Dockerfile
env:
- REGISTRY: ghcr.io
+ GITHUB_REGISTRY: ghcr.io
+ DOCKER_REGISTRY: docker.io
IMAGE_NAME: "coollabsio/coolify-testing-host"
jobs:
@@ -19,21 +20,34 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- - name: Login to ghcr.io
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
with:
- no-cache: true
context: .
file: docker/testing-host/Dockerfile
platforms: linux/amd64
push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+ labels: |
+ coolify.managed=true
+
aarch64:
runs-on: [ self-hosted, arm64 ]
permissions:
@@ -41,21 +55,34 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- - name: Login to ghcr.io
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Build and Push Image
+ uses: docker/build-push-action@v6
with:
- no-cache: true
context: .
file: docker/testing-host/Dockerfile
platforms: linux/aarch64
push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
+ tags: |
+ ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
+ ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
+ labels: |
+ coolify.managed=true
+
merge-manifest:
runs-on: ubuntu-latest
permissions:
@@ -63,21 +90,36 @@ jobs:
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: actions/checkout@v4
+
+ - uses: docker/setup-buildx-action@v3
+
+ - name: Login to ${{ env.GITHUB_REGISTRY }}
uses: docker/login-action@v3
with:
- registry: ${{ env.REGISTRY }}
+ registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- - name: Create & publish manifest
+
+ - name: Login to ${{ env.DOCKER_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_REGISTRY }}
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_TOKEN }}
+
+ - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
run: |
- docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+ docker buildx imagetools create \
+ --append ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 \
+ --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+
+ - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }}
+ run: |
+ docker buildx imagetools create \
+ --append ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 \
+ --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+
- uses: sarisia/actions-status-discord@v1
if: always()
with:
diff --git a/.github/workflows/development-build.yml b/.github/workflows/development-build.yml
deleted file mode 100644
index 268b885ac..000000000
--- a/.github/workflows/development-build.yml
+++ /dev/null
@@ -1,79 +0,0 @@
-name: Development Build (v4)
-
-on:
- push:
- branches-ignore: ["main", "v3"]
- paths-ignore:
- - .github/workflows/coolify-helper.yml
- - docker/coolify-helper/Dockerfile
-
-env:
- REGISTRY: ghcr.io
- IMAGE_NAME: "coollabsio/coolify"
-
-jobs:
- amd64:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - name: Login to ghcr.io
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
- with:
- context: .
- file: docker/prod/Dockerfile
- platforms: linux/amd64
- push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
- aarch64:
- runs-on: [self-hosted, arm64]
- permissions:
- contents: read
- packages: write
- steps:
- - uses: actions/checkout@v4
- - name: Login to ghcr.io
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
- with:
- context: .
- file: docker/prod/Dockerfile
- platforms: linux/aarch64
- push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
- merge-manifest:
- runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
- needs: [amd64, aarch64]
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
- - name: Login to ghcr.io
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Create & publish manifest
- run: |
- docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
- - uses: sarisia/actions-status-discord@v1
- if: always()
- with:
- webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml
deleted file mode 100644
index 0edaa4f1c..000000000
--- a/.github/workflows/docker-image.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-name: Docker Image CI
-
-on:
- # push:
- # branches: [ "main" ]
- # pull_request:
- # branches: [ "*" ]
- push:
- branches: ["this-does-not-exist"]
- pull_request:
- branches: ["this-does-not-exist"]
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - name: Cache Docker layers
- uses: actions/cache@v2
- with:
- path: |
- /usr/local/share/ca-certificates
- /var/cache/apt/archives
- /var/lib/apt/lists
- ~/.cache
- key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}
- restore-keys: |
- ${{ runner.os }}-docker-
- - name: Build the Docker image
- run: |
- cp .env.example .env
- docker run --rm -u "$(id -u):$(id -g)" \
- -v "$(pwd):/app" \
- -w /app composer:2 \
- composer install --ignore-platform-reqs
- ./vendor/bin/spin build
- - name: Start the stack
- run: |
- ./vendor/bin/spin up -d
- ./vendor/bin/spin exec coolify php artisan key:generate
- ./vendor/bin/spin exec coolify php artisan migrate:fresh --seed
- - name: Test (missing E2E tests)
- run: |
- ./vendor/bin/spin exec coolify php artisan test
diff --git a/.github/workflows/fix-php-code-style-issues b/.github/workflows/fix-php-code-style-issues
deleted file mode 100644
index aebce91bc..000000000
--- a/.github/workflows/fix-php-code-style-issues
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Fix PHP code style issues
-
-on: [push]
-
-permissions:
- contents: write
-
-jobs:
- php-code-styling:
- runs-on: ubuntu-latest
- timeout-minutes: 5
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- ref: ${{ github.head_ref }}
-
- - name: Fix PHP code style issues
- uses: aglipanci/laravel-pint-action@2.4
-
- - name: Commit changes
- uses: stefanzweifel/git-auto-commit-action@v5
- with:
- commit_message: Fix styling
diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml
deleted file mode 100644
index d7a680170..000000000
--- a/.github/workflows/pr-build.yml
+++ /dev/null
@@ -1,93 +0,0 @@
-name: PR Build (v4)
-
-on:
- pull_request:
- types:
- - opened
- branches-ignore: ["main", "v3"]
- paths-ignore:
- - .github/workflows/coolify-helper.yml
- - docker/coolify-helper/Dockerfile
-
-env:
- REGISTRY: ghcr.io
- IMAGE_NAME: "coollabsio/coolify"
-
-jobs:
- amd64:
- runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
- attestations: write
- id-token: write
- actions: write
- steps:
- - uses: actions/checkout@v4
- - name: Login to ghcr.io
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
- with:
- context: .
- file: docker/prod/Dockerfile
- platforms: linux/amd64
- push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
- aarch64:
- runs-on: [self-hosted, arm64]
- permissions:
- contents: read
- packages: write
- attestations: write
- id-token: write
- actions: write
- steps:
- - uses: actions/checkout@v4
- - name: Login to ghcr.io
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
- with:
- context: .
- file: docker/prod/Dockerfile
- platforms: linux/aarch64
- push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64
- merge-manifest:
- runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
- attestations: write
- id-token: write
- actions: write
- needs: [amd64, aarch64]
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
- - name: Login to ghcr.io
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Create & publish manifest
- run: |
- docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
- - uses: sarisia/actions-status-discord@v1
- if: always()
- with:
- webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
diff --git a/.github/workflows/production-build.yml b/.github/workflows/production-build.yml
deleted file mode 100644
index c78c865bf..000000000
--- a/.github/workflows/production-build.yml
+++ /dev/null
@@ -1,89 +0,0 @@
-name: Production Build (v4)
-
-on:
- push:
- branches: ["main"]
- paths-ignore:
- - .github/workflows/coolify-helper.yml
- - docker/coolify-helper/Dockerfile
- - templates/service-templates.json
-
-env:
- REGISTRY: ghcr.io
- IMAGE_NAME: "coollabsio/coolify"
-
-jobs:
- amd64:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - name: Login to ghcr.io
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Get Version
- id: version
- run: |
- echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
- with:
- context: .
- file: docker/prod/Dockerfile
- platforms: linux/amd64
- push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
- aarch64:
- runs-on: [self-hosted, arm64]
- steps:
- - uses: actions/checkout@v4
- - name: Login to ghcr.io
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Get Version
- id: version
- run: |
- echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- - name: Build image and push to registry
- uses: docker/build-push-action@v5
- with:
- context: .
- file: docker/prod/Dockerfile
- platforms: linux/aarch64
- push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
- merge-manifest:
- runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
- needs: [amd64, aarch64]
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
- - name: Login to ghcr.io
- uses: docker/login-action@v3
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Get Version
- id: version
- run: |
- echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- - name: Create & publish manifest
- run: |
- docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- - uses: sarisia/actions-status-discord@v1
- if: always()
- with:
- webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
diff --git a/.gitignore b/.gitignore
index ac8a1e090..d7ee7e96c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,7 @@ _ide_helper_models.php
.rnd
/.ssh
scripts/load-test/*
+.ignition.json
+.env.dusk.local
+docker/coolify-realtime/node_modules
+.DS_Store
diff --git a/.gitpod.yml b/.gitpod.yml
deleted file mode 100644
index 6fd6797b5..000000000
--- a/.gitpod.yml
+++ /dev/null
@@ -1,65 +0,0 @@
-tasks:
- - name: Setup Spin environment and Composer dependencies
- # Fix because of https://github.com/gitpod-io/gitpod/issues/16614
- before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m)
- init: |
- cp .env.development.example .env &&
- sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env
- sed -i "s#USERID=#USERID=33333#g" .env
- sed -i "s#GROUPID=#GROUPID=33333#g" .env
- composer install --ignore-platform-reqs
- ./vendor/bin/spin up -d
- ./vendor/bin/spin exec -u webuser coolify php artisan key:generate
- ./vendor/bin/spin exec -u webuser coolify php artisan storage:link
- ./vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --seed
- cat .coolify-logo
- gp sync-done spin-is-ready
-
- - name: Install Node dependencies and run Vite
- command: |
- echo "Waiting for Sail environment to boot up."
- gp sync-await spin-is-ready
- ./vendor/bin/spin exec vite npm install
- ./vendor/bin/spin exec vite npm run dev -- --host
-
- - name: Laravel Queue Worker, listening to code changes
- command: |
- echo "Waiting for Sail environment to boot up."
- gp sync-await spin-is-ready
- ./vendor/bin/spin exec -u webuser coolify php artisan queue:listen
-
-ports:
- - port: 5432
- onOpen: ignore
- name: PostgreSQL
- visibility: public
- - port: 5173
- onOpen: ignore
- visibility: public
- name: Node Server for Vite
- - port: 8000
- onOpen: ignore
- visibility: public
- name: Coolify
-
-# Configure vscode
-vscode:
- extensions:
- - bmewburn.vscode-intelephense-client
- - ikappas.composer
- - ms-azuretools.vscode-docker
- - ecmel.vscode-html-css
- - MehediDracula.php-namespace-resolver
- - wmaurer.change-case
- - Equinusocio.vsc-community-material-theme
- - EditorConfig.EditorConfig
- - streetsidesoftware.code-spell-checker
- - rangav.vscode-thunder-client
- - PKief.material-icon-theme
- - cierra.livewire-vscode
- - lennardv.livewire-goto-updated
- - bradlc.vscode-tailwindcss
- - heybourn.headwind
- - adrianwilczynski.alpine-js-intellisense
- - amiralizadeh9480.laravel-extra-intellisense
- - shufo.vscode-blade-formatter
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 590360ddb..80ec0614e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,9 +12,10 @@ You can ask for guidance anytime on our [Discord server](https://coollabs.io/dis
4. [Set up Environment Variables](#4-set-up-environment-variables)
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)
+7. [Create a Pull Request](#7-create-a-pull-request)
+8. [Development Notes](#development-notes)
+9. [Resetting Development Environment](#resetting-development-environment)
+10. [Additional Contribution Guidelines](#additional-contribution-guidelines)
## 1. Setup Development Environment
@@ -25,15 +26,15 @@ Follow the steps below for your operating system:
1. Install `docker-ce`, Docker Desktop (or similar):
- Docker CE (recommended):
- - Install Windows Subsystem for Linux v2 (WSL2) by following this guide: [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install)
- - After installing WSL2, install Docker CE for your Linux distribution by following this guide: [Install Docker Engine](https://docs.docker.com/engine/install/)
+ - Install Windows Subsystem for Linux v2 (WSL2) by following this guide: [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install?ref=coolify)
+ - After installing WSL2, install Docker CE for your Linux distribution by following this guide: [Install Docker Engine](https://docs.docker.com/engine/install/?ref=coolify)
- Make sure to choose the appropriate Linux distribution (e.g., Ubuntu) when following the Docker installation guide
- Install Docker Desktop (easier):
- - Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)
+ - Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/?ref=coolify)
- Ensure WSL2 backend is enabled in Docker Desktop settings
2. Install Spin:
- - Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2)
+ - Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2?ref=coolify)
@@ -42,12 +43,12 @@ Follow the steps below for your operating system:
1. Install Orbstack, Docker Desktop (or similar):
- Orbstack (recommended, as it is a faster and lighter alternative to Docker Desktop):
- - Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation)
+ - Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation?ref=coolify)
- Docker Desktop:
- - Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/)
+ - Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/?ref=coolify)
2. Install Spin:
- - Follow the instructions to install Spin on MacOS from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-macos/#download-and-install-spin)
+ - Follow the instructions to install Spin on MacOS from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-macos/#download-and-install-spin?ref=coolify)
@@ -56,12 +57,12 @@ Follow the steps below for your operating system:
1. Install Docker Engine, Docker Desktop (or similar):
- Docker Engine (recommended, as there is no VM overhead):
- - Follow the official [Docker Engine installation guide](https://docs.docker.com/engine/install/) for your Linux distribution
+ - Follow the official [Docker Engine installation guide](https://docs.docker.com/engine/install/?ref=coolify) for your Linux distribution
- Docker Desktop:
- - If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/)
+ - If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/?ref=coolify)
2. Install Spin:
- - Follow the instructions to install Spin on Linux from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-linux#configure-docker-permissions)
+ - Follow the instructions to install Spin on Linux from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-linux#configure-docker-permissions?ref=coolify)
@@ -85,14 +86,14 @@ After installing Docker (or Orbstack) and Spin, verify the installation:
| Editor | Platform | Download Link |
|--------|----------|---------------|
- | Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download) |
- | Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/) |
- | Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download) |
+ | Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download?ref=coolify) |
+ | Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/?ref=coolify) |
+ | Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download?ref=coolify) |
3. Clone the Coolify Repository from your fork to your local machine
- Use `git clone` in the command line, or
- Use GitHub Desktop (recommended):
- - Download and install from [https://desktop.github.com/](https://desktop.github.com/)
+ - Download and install from [https://desktop.github.com/](https://desktop.github.com/?ref=coolify)
- Open GitHub Desktop and login with your GitHub account
- Click on `File` -> `Clone Repository` select `github.com` as the repository location, then select your forked Coolify repository, choose the local path and then click `Clone`
@@ -145,7 +146,36 @@ After installing Docker (or Orbstack) and Spin, verify the installation:
> TELESCOPE_ENABLED=true
> ```
-## 7. Development Notes
+## 7. Create a Pull Request
+
+1. After making changes or adding a new service:
+ - Commit your changes to your forked repository.
+ - Push the changes to your GitHub account.
+
+2. Creating the Pull Request (PR):
+ - Navigate to the main Coolify repository on GitHub.
+ - Click the "Pull requests" tab.
+ - Click the green "New pull request" button.
+ - Choose your fork and branch as the compare branch.
+ - Click "Create pull request".
+
+3. Filling out the PR details:
+ - Give your PR a descriptive title.
+ - Use the Pull Request Template provided and fill in the details.
+
+> [!IMPORTANT]
+> Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
+
+4. Submit your PR:
+ - Review your changes one last time.
+ - Click "Create pull request" to submit.
+
+> [!NOTE]
+> Make sure your PR is out of draft mode as soon as it's ready for review. PRs that are in draft mode for a long time may be closed by maintainers.
+
+After submission, maintainers will review your PR and may request changes or provide feedback.
+
+## Development Notes
When working on Coolify, keep the following in mind:
@@ -164,35 +194,41 @@ When working on Coolify, keep the following in mind:
> [!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
+## Resetting Development Environment
-1. After making changes or adding a new service:
- - Commit your changes to your forked repository.
- - Push the changes to your GitHub account.
+If you encounter issues or break your database or something else, follow these steps to start from a clean slate (works since `v4.0.0-beta.342`):
-2. Creating the Pull Request (PR):
- - Navigate to the main Coolify repository on GitHub.
- - Click the "Pull requests" tab.
- - Click the green "New pull request" button.
- - Choose your fork and branch as the compare branch.
- - Click "Create pull request".
+1. Stop all running containers `ctrl + c`.
-3. Filling out the PR details:
- - Give your PR a descriptive title.
- - In the description, explain the changes you've made.
- - Reference any related issues by using keywords like "Fixes #123" or "Closes #456".
+2. Remove all Coolify containers:
+ ```bash
+ docker rm coolify coolify-db coolify-redis coolify-realtime coolify-testing-host coolify-minio coolify-vite-1 coolify-mail
+ ```
+
+3. Remove Coolify volumes (it is possible that the volumes have no `coolify` prefix on your machine, in that case remove the prefix from the command):
+ ```bash
+ docker volume rm coolify_dev_backups_data coolify_dev_postgres_data coolify_dev_redis_data coolify_dev_coolify_data coolify_dev_minio_data
+ ```
+
+4. Remove unused images:
+ ```bash
+ docker image prune -a
+ ```
+
+5. Start Coolify again:
+ ```bash
+ spin up
+ ```
+
+6. Run database migrations and seeders:
+ ```bash
+ docker exec -it coolify php artisan migrate:fresh --seed
+ ```
+
+After completing these steps, you'll have a fresh development setup.
> [!IMPORTANT]
-> Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
-
-4. Submit your PR:
- - Review your changes one last time.
- - Click "Create pull request" to submit.
-
-> [!NOTE]
-> Make sure your PR is out of draft mode as soon as it's ready for review. PRs that are in draft mode for a long time may be closed by maintainers.
-
-After submission, maintainers will review your PR and may request changes or provide feedback.
+> Always run database migrations and seeders after switching branches or pulling updates to ensure your local database structure matches the current codebase and includes necessary seed data.
## Additional Contribution Guidelines
diff --git a/README.md b/README.md
index 14a741088..0a3ce0132 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
# About the Project
-Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
+Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else.
@@ -22,6 +22,9 @@ curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
```
You can find the installation script source [here](./scripts/install.sh).
+> [!NOTE]
+> Please refer to the [docs](https://coolify.io/docs/installation) for more information about the installation.
+
# Support
Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
@@ -37,21 +40,20 @@ Special thanks to our biggest sponsors!
### Special Sponsors
-
+
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities.
+* [Tolgee](https://tolgee.io/?ref=coolify) - Developer & translator friendly web-based localization platform.
* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies.
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution.
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks.
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management.
-* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions.
* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies.
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets.
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers.
-* [Latitude](https://latitude.sh/?ref=coolify.io) - A cloud computing platform offering bare metal servers and cloud instances for developers and businesses.
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities.
* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries.
* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools.
@@ -60,6 +62,7 @@ Special thanks to our biggest sponsors!
* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses.
* [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly.
* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
+* [LiquidWeb](https://liquidweb.com/?utm_source=coolify.io) - Fast web hosting provider.
## Github Sponsors ($40+)
@@ -88,6 +91,11 @@ Special thanks to our biggest sponsors!
+
+
+
+
+
## Organizations
@@ -121,7 +129,6 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
- Better support
- Less maintenance for you
-
# Recognitions
@@ -138,6 +145,13 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
+# Core Maintainers
+
+| Andras Bacsai | Peak |
+|------------|------------|
+|
|
|
+|
|
|
+
# Repo Activity

diff --git a/RELEASE.md b/RELEASE.md
index 2cb96b72b..bc159b040 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,45 +1,133 @@
# 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.
+This guide outlines the release process for Coolify, intended for developers and those interested in understanding how Coolify releases are managed and deployed.
+
+## Table of Contents
+- [Release Process](#release-process)
+- [Version Types](#version-types)
+ - [Stable](#stable)
+ - [Nightly](#nightly)
+ - [Beta](#beta)
+- [Version Availability](#version-availability)
+ - [Self-Hosted](#self-hosted)
+ - [Cloud](#cloud)
+- [Manually Update to Specific Versions](#manually-update-to-specific-versions)
## Release Process
-1. **Development on `next` or separate branches**
- - Changes, fixes and new features are developed on the `next` or even separate branches.
+1. **Development on `next` or Feature Branches**
+ - Improvements, fixes, and new features are developed on the `next` branch or separate feature branches.
2. **Merging to `main`**
- - Once changes are ready, they are merged from `next` into the `main` branch.
+ - Once ready, changes are merged from the `next` branch into the `main` branch (via a pull request).
-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.
+3. **Building the Release**
+ - After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry and Docker Hub with the specific version tag and the `latest` tag.
-4. **Creating a GitHub release**
- - A new release is created on GitHub with the new version details.
+4. **Creating a GitHub Release**
+ - A new GitHub release is manually created with details of the changes made in the version.
5. **Updating the CDN**
- - 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)
+ - To make a new version publicly available, the version information on the CDN needs to be updated manually. After that the new version number will be available at [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.
+> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated. After the CDN is updated, a discord announcement will be made in the Production Release channel.**
+## Version Types
+
+
Stable (coming soon)
+
+- **Stable**
+ - The production version suitable for stable, production environments (recommended).
+ - **Update Frequency:** Every 2 to 4 weeks, with more frequent possible fixes.
+ - **Release Size:** Larger but less frequent releases. Multiple nightly versions are consolidated into a single stable release.
+ - **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`, `4.1.0`, etc.).
+ - **Installation Command:**
+ ```bash
+ curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
+ ```
+
+Nightly
+
+- **Nightly**
+ - The latest development version, suitable for testing the latest changes and experimenting with new features.
+ - **Update Frequency:** Daily or bi-weekly updates.
+ - **Release Size:** Smaller, more frequent releases.
+ - **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-nightly.1`, `4.1.0-nightly.2`, etc.).
+ - **Installation Command:**
+ ```bash
+ curl -fsSL https://cdn.coollabs.io/coolify-nightly/install.sh | bash -s next
+ ```
+
+Beta
+
+- **Beta**
+ - Test releases for the upcoming stable version.
+ - **Purpose:** Allows users to test and provide feedback on new features and changes before they become stable.
+ - **Update Frequency:** Available if we think beta testing is necessary.
+ - **Release Size:** Same size as stable release as it will become the next stabe release after some time.
+ - **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`, `4.1.0-beta.2`, etc.).
+ - **Installation Command:**
+ ```bash
+ curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
+ ```
+
+
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord");
+ $portsToCheck = ['80', '443'];
+
+ try {
+ if ($server->proxyType() !== ProxyTypes::NONE->value) {
+ $proxyCompose = CheckConfiguration::run($server);
+ if (isset($proxyCompose)) {
+ $yaml = Yaml::parse($proxyCompose);
+ $portsToCheck = [];
+ if ($server->proxyType() === ProxyTypes::TRAEFIK->value) {
+ $ports = data_get($yaml, 'services.traefik.ports');
+ } elseif ($server->proxyType() === ProxyTypes::CADDY->value) {
+ $ports = data_get($yaml, 'services.caddy.ports');
+ }
+ if (isset($ports)) {
+ foreach ($ports as $port) {
+ $portsToCheck[] = str($port)->before(':')->value();
+ }
+ }
+ }
} else {
- return false;
+ $portsToCheck = [];
}
+ } catch (\Exception $e) {
+ Log::error('Error checking proxy: '.$e->getMessage());
}
- if ($port443) {
- if ($fromUI) {
- throw new \Exception("Port 443 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord");
- } else {
- return false;
+ if (count($portsToCheck) === 0) {
+ return false;
+ }
+ foreach ($portsToCheck as $port) {
+ $connection = @fsockopen($ip, $port);
+ if (is_resource($connection) && fclose($connection)) {
+ if ($fromUI) {
+ throw new \Exception("Port $port is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord");
+ } else {
+ return false;
+ }
}
}
diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php
index 991c94b11..7c93720cb 100644
--- a/app/Actions/Proxy/StartProxy.php
+++ b/app/Actions/Proxy/StartProxy.php
@@ -13,64 +13,60 @@ class StartProxy
public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
{
- try {
- $proxyType = $server->proxyType();
- if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
- return 'OK';
- }
- $commands = collect([]);
- $proxy_path = $server->proxyPath();
- $configuration = CheckConfiguration::run($server);
- if (! $configuration) {
- throw new \Exception('Configuration is not synced');
- }
- SaveConfiguration::run($server, $configuration);
- $docker_compose_yml_base64 = base64_encode($configuration);
- $server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
+ $proxyType = $server->proxyType();
+ if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
+ return 'OK';
+ }
+ $commands = collect([]);
+ $proxy_path = $server->proxyPath();
+ $configuration = CheckConfiguration::run($server);
+ if (! $configuration) {
+ throw new \Exception('Configuration is not synced');
+ }
+ SaveConfiguration::run($server, $configuration);
+ $docker_compose_yml_base64 = base64_encode($configuration);
+ $server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
+ $server->save();
+ if ($server->isSwarm()) {
+ $commands = $commands->merge([
+ "mkdir -p $proxy_path/dynamic",
+ "cd $proxy_path",
+ "echo 'Creating required Docker Compose file.'",
+ "echo 'Starting coolify-proxy.'",
+ 'docker stack deploy -c docker-compose.yml coolify-proxy',
+ "echo 'Successfully started coolify-proxy.'",
+ ]);
+ } else {
+ $caddfile = 'import /dynamic/*.caddy';
+ $commands = $commands->merge([
+ "mkdir -p $proxy_path/dynamic",
+ "cd $proxy_path",
+ "echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
+ "echo 'Creating required Docker Compose file.'",
+ "echo 'Pulling docker image.'",
+ 'docker compose pull',
+ 'if docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then',
+ " echo 'Stopping and removing existing coolify-proxy.'",
+ ' docker rm -f coolify-proxy || true',
+ " echo 'Successfully stopped and removed existing coolify-proxy.'",
+ 'fi',
+ "echo 'Starting coolify-proxy.'",
+ 'docker compose up -d --remove-orphans',
+ "echo 'Successfully started coolify-proxy.'",
+ ]);
+ $commands = $commands->merge(connectProxyToNetworks($server));
+ }
+
+ if ($async) {
+ return remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
+ } else {
+ instant_remote_process($commands, $server);
+ $server->proxy->set('status', 'running');
+ $server->proxy->set('type', $proxyType);
$server->save();
- if ($server->isSwarm()) {
- $commands = $commands->merge([
- "mkdir -p $proxy_path/dynamic",
- "cd $proxy_path",
- "echo 'Creating required Docker Compose file.'",
- "echo 'Starting coolify-proxy.'",
- 'docker stack deploy -c docker-compose.yml coolify-proxy',
- "echo 'Proxy started successfully.'",
- ]);
- } else {
- $caddfile = 'import /dynamic/*.caddy';
- $commands = $commands->merge([
- "mkdir -p $proxy_path/dynamic",
- "cd $proxy_path",
- "echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
- "echo 'Creating required Docker Compose file.'",
- "echo 'Pulling docker image.'",
- 'docker compose pull',
- "echo 'Stopping existing coolify-proxy.'",
- 'docker compose down -v --remove-orphans > /dev/null 2>&1',
- "echo 'Starting coolify-proxy.'",
- 'docker compose up -d --remove-orphans',
- "echo 'Proxy started successfully.'",
- ]);
- $commands = $commands->merge(connectProxyToNetworks($server));
- }
+ ProxyStarted::dispatch($server);
- if ($async) {
- $activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
-
- return $activity;
- } else {
- instant_remote_process($commands, $server);
- $server->proxy->set('status', 'running');
- $server->proxy->set('type', $proxyType);
- $server->save();
- ProxyStarted::dispatch($server);
-
- return 'OK';
- }
- } catch (\Throwable $e) {
- ray($e);
- throw $e;
+ return 'OK';
}
}
}
diff --git a/app/Actions/Server/CleanupDocker.php b/app/Actions/Server/CleanupDocker.php
index 1034c13d6..0349ead89 100644
--- a/app/Actions/Server/CleanupDocker.php
+++ b/app/Actions/Server/CleanupDocker.php
@@ -2,7 +2,6 @@
namespace App\Actions\Server;
-use App\Models\InstanceSettings;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -10,30 +9,33 @@ class CleanupDocker
{
use AsAction;
+ public string $jobQueue = 'high';
+
public function handle(Server $server)
{
+ $settings = instanceSettings();
+ $helperImageVersion = data_get($settings, 'helper_version');
+ $helperImage = config('constants.coolify.helper_image');
+ $helperImageWithVersion = "$helperImage:$helperImageVersion";
- $commands = $this->getCommands();
+ $commands = [
+ 'docker container prune -f --filter "label=coolify.managed=true" --filter "label!=coolify.proxy=true"',
+ 'docker image prune -af --filter "label!=coolify.managed=true"',
+ 'docker builder prune -af',
+ "docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
+ ];
+
+ $serverSettings = $server->settings;
+ if ($serverSettings->delete_unused_volumes) {
+ $commands[] = 'docker volume prune -af';
+ }
+
+ if ($serverSettings->delete_unused_networks) {
+ $commands[] = 'docker network prune -f';
+ }
foreach ($commands as $command) {
instant_remote_process([$command], $server, false);
}
}
-
- private function getCommands(): array
- {
- $settings = InstanceSettings::get();
- $helperImageVersion = data_get($settings, 'helper_version');
- $helperImage = config('coolify.helper_image');
- $helperImageWithVersion = config('coolify.helper_image').':'.$helperImageVersion;
-
- $commonCommands = [
- 'docker container prune -f --filter "label=coolify.managed=true"',
- 'docker image prune -af --filter "label!=coolify.managed=true"',
- 'docker builder prune -af',
- "docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi",
- ];
-
- return $commonCommands;
- }
}
diff --git a/app/Actions/Server/ConfigureCloudflared.php b/app/Actions/Server/ConfigureCloudflared.php
index 3946afe95..fc04e67a4 100644
--- a/app/Actions/Server/ConfigureCloudflared.php
+++ b/app/Actions/Server/ConfigureCloudflared.php
@@ -2,6 +2,7 @@
namespace App\Actions\Server;
+use App\Events\CloudflareTunnelConfigured;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
@@ -39,9 +40,12 @@ class ConfigureCloudflared
]);
instant_remote_process($commands, $server);
} catch (\Throwable $e) {
- ray($e);
+ $server->settings->is_cloudflare_tunnel = false;
+ $server->settings->save();
throw $e;
} finally {
+ CloudflareTunnelConfigured::dispatch($server->team_id);
+
$commands = collect([
'rm -fr /tmp/cloudflared',
]);
diff --git a/app/Actions/Server/DeleteServer.php b/app/Actions/Server/DeleteServer.php
new file mode 100644
index 000000000..15c892e75
--- /dev/null
+++ b/app/Actions/Server/DeleteServer.php
@@ -0,0 +1,17 @@
+forceDelete();
+ }
+}
diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php
index f671f2d2a..cbcb20368 100644
--- a/app/Actions/Server/InstallDocker.php
+++ b/app/Actions/Server/InstallDocker.php
@@ -12,12 +12,11 @@ class InstallDocker
public function handle(Server $server)
{
+ $dockerVersion = config('constants.docker.minimum_required_version');
$supported_os_type = $server->validateOS();
if (! $supported_os_type) {
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: documentation.');
}
- ray('Installing Docker on server: '.$server->name.' ('.$server->ip.')'.' with OS type: '.$supported_os_type);
- $dockerVersion = '24.0';
$config = base64_encode('{
"log-driver": "json-file",
"log-opts": {
diff --git a/app/Actions/Server/ResourcesCheck.php b/app/Actions/Server/ResourcesCheck.php
new file mode 100644
index 000000000..e6b90ba38
--- /dev/null
+++ b/app/Actions/Server/ResourcesCheck.php
@@ -0,0 +1,41 @@
+subSeconds($seconds))->update(['status' => 'exited']);
+ ServiceApplication::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
+ ServiceDatabase::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
+ StandalonePostgresql::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
+ StandaloneRedis::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
+ StandaloneMongodb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
+ StandaloneMysql::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
+ StandaloneMariadb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
+ StandaloneKeydb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
+ StandaloneDragonfly::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
+ StandaloneClickhouse::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
+ } catch (\Throwable $e) {
+ return handleError($e);
+ }
+ }
+}
diff --git a/app/Actions/Server/RestartContainer.php b/app/Actions/Server/RestartContainer.php
new file mode 100644
index 000000000..63361d8b7
--- /dev/null
+++ b/app/Actions/Server/RestartContainer.php
@@ -0,0 +1,16 @@
+restartContainer($containerName);
+ }
+}
diff --git a/app/Actions/Server/RunCommand.php b/app/Actions/Server/RunCommand.php
index fce862eb0..254c78587 100644
--- a/app/Actions/Server/RunCommand.php
+++ b/app/Actions/Server/RunCommand.php
@@ -12,8 +12,6 @@ class RunCommand
public function handle(Server $server, $command)
{
- $activity = remote_process(command: [$command], server: $server, ignore_errors: true, type: ActivityTypes::COMMAND->value);
-
- return $activity;
+ return remote_process(command: [$command], server: $server, ignore_errors: true, type: ActivityTypes::COMMAND->value);
}
}
diff --git a/app/Actions/Server/ServerCheck.php b/app/Actions/Server/ServerCheck.php
new file mode 100644
index 000000000..5f9a1e357
--- /dev/null
+++ b/app/Actions/Server/ServerCheck.php
@@ -0,0 +1,269 @@
+server = $server;
+ try {
+ if ($this->server->isFunctional() === false) {
+ return 'Server is not functional.';
+ }
+
+ if (! $this->server->isSwarmWorker() && ! $this->server->isBuildServer()) {
+
+ if (isset($data)) {
+ $data = collect($data);
+
+ $this->server->sentinelHeartbeat();
+
+ $this->containers = collect(data_get($data, 'containers'));
+
+ $filesystemUsageRoot = data_get($data, 'filesystem_usage_root.used_percentage');
+ ServerStorageCheckJob::dispatch($this->server, $filesystemUsageRoot);
+
+ $containerReplicates = null;
+ $this->isSentinel = true;
+
+ } else {
+ ['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
+ // ServerStorageCheckJob::dispatch($this->server);
+ }
+
+ if (is_null($this->containers)) {
+ return 'No containers found.';
+ }
+
+ if (isset($containerReplicates)) {
+ foreach ($containerReplicates as $containerReplica) {
+ $name = data_get($containerReplica, 'Name');
+ $this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
+ if (data_get($container, 'Spec.Name') === $name) {
+ $replicas = data_get($containerReplica, 'Replicas');
+ $running = str($replicas)->explode('/')[0];
+ $total = str($replicas)->explode('/')[1];
+ if ($running === $total) {
+ data_set($container, 'State.Status', 'running');
+ data_set($container, 'State.Health.Status', 'healthy');
+ } else {
+ data_set($container, 'State.Status', 'starting');
+ data_set($container, 'State.Health.Status', 'unhealthy');
+ }
+ }
+
+ return $container;
+ });
+ }
+ }
+ $this->checkContainers();
+
+ if ($this->server->isSentinelEnabled() && $this->isSentinel === false) {
+ CheckAndStartSentinelJob::dispatch($this->server);
+ }
+
+ if ($this->server->isLogDrainEnabled()) {
+ $this->checkLogDrainContainer();
+ }
+
+ if ($this->server->proxySet() && ! $this->server->proxy->force_stop) {
+ $foundProxyContainer = $this->containers->filter(function ($value, $key) {
+ if ($this->server->isSwarm()) {
+ return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
+ } else {
+ return data_get($value, 'Name') === '/coolify-proxy';
+ }
+ })->first();
+ if (! $foundProxyContainer) {
+ try {
+ $shouldStart = CheckProxy::run($this->server);
+ if ($shouldStart) {
+ StartProxy::run($this->server, false);
+ $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
+ }
+ } catch (\Throwable $e) {
+ }
+ } else {
+ $this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
+ $this->server->save();
+ $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
+ instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
+ }
+ }
+ }
+ } catch (\Throwable $e) {
+ return handleError($e);
+ }
+ }
+
+ private function checkLogDrainContainer()
+ {
+ $foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
+ return data_get($value, 'Name') === '/coolify-log-drain';
+ })->first();
+ if ($foundLogDrainContainer) {
+ $status = data_get($foundLogDrainContainer, 'State.Status');
+ if ($status !== 'running') {
+ StartLogDrain::dispatch($this->server);
+ }
+ } else {
+ StartLogDrain::dispatch($this->server);
+ }
+ }
+
+ private function checkContainers()
+ {
+ foreach ($this->containers as $container) {
+ if ($this->isSentinel) {
+ $labels = Arr::undot(data_get($container, 'labels'));
+ } else {
+ if ($this->server->isSwarm()) {
+ $labels = Arr::undot(data_get($container, 'Spec.Labels'));
+ } else {
+ $labels = Arr::undot(data_get($container, 'Config.Labels'));
+ }
+
+ }
+ $managed = data_get($labels, 'coolify.managed');
+ if (! $managed) {
+ continue;
+ }
+ $uuid = data_get($labels, 'coolify.name');
+ if (! $uuid) {
+ $uuid = data_get($labels, 'com.docker.compose.service');
+ }
+
+ if ($this->isSentinel) {
+ $containerStatus = data_get($container, 'state');
+ $containerHealth = data_get($container, 'health_status');
+ } else {
+ $containerStatus = data_get($container, 'State.Status');
+ $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
+ }
+ $containerStatus = "$containerStatus ($containerHealth)";
+
+ $applicationId = data_get($labels, 'coolify.applicationId');
+ $serviceId = data_get($labels, 'coolify.serviceId');
+ $databaseId = data_get($labels, 'coolify.databaseId');
+ $pullRequestId = data_get($labels, 'coolify.pullRequestId');
+
+ if ($applicationId) {
+ // Application
+ if ($pullRequestId != 0) {
+ if (str($applicationId)->contains('-')) {
+ $applicationId = str($applicationId)->before('-');
+ }
+ $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
+ if ($preview) {
+ $preview->update(['status' => $containerStatus]);
+ }
+ } else {
+ $application = Application::where('id', $applicationId)->first();
+ if ($application) {
+ $application->update([
+ 'status' => $containerStatus,
+ 'last_online_at' => now(),
+ ]);
+ }
+ }
+ } elseif (isset($serviceId)) {
+ // Service
+ $subType = data_get($labels, 'coolify.service.subType');
+ $subId = data_get($labels, 'coolify.service.subId');
+ $service = Service::where('id', $serviceId)->first();
+ if (! $service) {
+ continue;
+ }
+ if ($subType === 'application') {
+ $service = ServiceApplication::where('id', $subId)->first();
+ } else {
+ $service = ServiceDatabase::where('id', $subId)->first();
+ }
+ if ($service) {
+ $service->update([
+ 'status' => $containerStatus,
+ 'last_online_at' => now(),
+ ]);
+ if ($subType === 'database') {
+ $isPublic = data_get($service, 'is_public');
+ if ($isPublic) {
+ $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
+ if ($this->isSentinel) {
+ return data_get($value, 'name') === $uuid.'-proxy';
+ } else {
+
+ if ($this->server->isSwarm()) {
+ return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
+ } else {
+ return data_get($value, 'Name') === "/$uuid-proxy";
+ }
+ }
+ })->first();
+ if (! $foundTcpProxy) {
+ StartDatabaseProxy::run($service);
+ }
+ }
+ }
+ }
+ } else {
+ // Database
+ if (is_null($this->databases)) {
+ $this->databases = $this->server->databases();
+ }
+ $database = $this->databases->where('uuid', $uuid)->first();
+ if ($database) {
+ $database->update([
+ 'status' => $containerStatus,
+ 'last_online_at' => now(),
+ ]);
+
+ $isPublic = data_get($database, 'is_public');
+ if ($isPublic) {
+ $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
+ if ($this->isSentinel) {
+ return data_get($value, 'name') === $uuid.'-proxy';
+ } else {
+ if ($this->server->isSwarm()) {
+ return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
+ } else {
+
+ return data_get($value, 'Name') === "/$uuid-proxy";
+ }
+ }
+ })->first();
+ if (! $foundTcpProxy) {
+ StartDatabaseProxy::run($database);
+ // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/Actions/Server/InstallLogDrain.php b/app/Actions/Server/StartLogDrain.php
similarity index 95%
rename from app/Actions/Server/InstallLogDrain.php
rename to app/Actions/Server/StartLogDrain.php
index 9b6741211..0d28a0099 100644
--- a/app/Actions/Server/InstallLogDrain.php
+++ b/app/Actions/Server/StartLogDrain.php
@@ -5,20 +5,26 @@ namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
-class InstallLogDrain
+class StartLogDrain
{
use AsAction;
+ public string $jobQueue = 'high';
+
public function handle(Server $server)
{
if ($server->settings->is_logdrain_newrelic_enabled) {
$type = 'newrelic';
+ StopLogDrain::run($server);
} elseif ($server->settings->is_logdrain_highlight_enabled) {
$type = 'highlight';
+ StopLogDrain::run($server);
} elseif ($server->settings->is_logdrain_axiom_enabled) {
$type = 'axiom';
+ StopLogDrain::run($server);
} elseif ($server->settings->is_logdrain_custom_enabled) {
$type = 'custom';
+ StopLogDrain::run($server);
} else {
$type = 'none';
}
@@ -151,6 +157,8 @@ services:
- ./parsers.conf:/parsers.conf
ports:
- 127.0.0.1:24224:24224
+ labels:
+ - coolify.managed=true
restart: unless-stopped
');
$readme = base64_encode('# New Relic Log Drain
@@ -163,7 +171,7 @@ Files:
');
$license_key = $server->settings->logdrain_newrelic_license_key;
$base_uri = $server->settings->logdrain_newrelic_base_uri;
- $base_path = config('coolify.base_config_path');
+ $base_path = config('constants.coolify.base_config_path');
$config_path = $base_path.'/log-drains';
$fluent_bit_config = $config_path.'/fluent-bit.conf';
@@ -202,10 +210,8 @@ Files:
throw new \Exception('Unknown log drain type.');
}
$restart_command = [
- "echo 'Stopping old Fluent Bit'",
- "cd $config_path && docker compose down --remove-orphans || true",
"echo 'Starting Fluent Bit'",
- "cd $config_path && docker compose up -d --remove-orphans",
+ "cd $config_path && docker compose up -d",
];
$command = array_merge($command, $add_envs_command, $restart_command);
diff --git a/app/Actions/Server/StartSentinel.php b/app/Actions/Server/StartSentinel.php
index b79bc8f67..587ac4a8d 100644
--- a/app/Actions/Server/StartSentinel.php
+++ b/app/Actions/Server/StartSentinel.php
@@ -9,18 +9,57 @@ class StartSentinel
{
use AsAction;
- public function handle(Server $server, $version = 'latest', bool $restart = false)
+ public function handle(Server $server, bool $restart = false, ?string $latestVersion = null)
{
+ if ($server->isSwarm() || $server->isBuildServer()) {
+ return;
+ }
if ($restart) {
StopSentinel::run($server);
}
- $metrics_history = $server->settings->metrics_history_days;
- $refresh_rate = $server->settings->metrics_refresh_rate_seconds;
- $token = $server->settings->metrics_token;
+ $version = $latestVersion ?? get_latest_sentinel_version();
+ $metricsHistory = data_get($server, 'settings.sentinel_metrics_history_days');
+ $refreshRate = data_get($server, 'settings.sentinel_metrics_refresh_rate_seconds');
+ $pushInterval = data_get($server, 'settings.sentinel_push_interval_seconds');
+ $token = data_get($server, 'settings.sentinel_token');
+ $endpoint = data_get($server, 'settings.sentinel_custom_url');
+ $debug = data_get($server, 'settings.is_sentinel_debug_enabled');
+ $mountDir = '/data/coolify/sentinel';
+ $image = "ghcr.io/coollabsio/sentinel:$version";
+ if (! $endpoint) {
+ throw new \Exception('You should set FQDN in Instance Settings.');
+ }
+ $environments = [
+ 'TOKEN' => $token,
+ 'DEBUG' => $debug ? 'true' : 'false',
+ 'PUSH_ENDPOINT' => $endpoint,
+ 'PUSH_INTERVAL_SECONDS' => $pushInterval,
+ 'COLLECTOR_ENABLED' => $server->isMetricsEnabled() ? 'true' : 'false',
+ 'COLLECTOR_REFRESH_RATE_SECONDS' => $refreshRate,
+ 'COLLECTOR_RETENTION_PERIOD_DAYS' => $metricsHistory,
+ ];
+ $labels = [
+ 'coolify.managed' => 'true',
+ ];
+ if (isDev()) {
+ // data_set($environments, 'DEBUG', 'true');
+ // $image = 'sentinel';
+ $mountDir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/sentinel';
+ }
+ $dockerEnvironments = '-e "'.implode('" -e "', array_map(fn ($key, $value) => "$key=$value", array_keys($environments), $environments)).'"';
+ $dockerLabels = implode(' ', array_map(fn ($key, $value) => "$key=$value", array_keys($labels), $labels));
+ $dockerCommand = "docker run -d $dockerEnvironments --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v $mountDir:/app/db --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 --add-host=host.docker.internal:host-gateway --label $dockerLabels $image";
+
instant_remote_process([
- "docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
- 'chown -R 9999:root /data/coolify/metrics /data/coolify/logs',
- 'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
- ], $server, true);
+ 'docker rm -f coolify-sentinel || true',
+ "mkdir -p $mountDir",
+ $dockerCommand,
+ "chown -R 9999:root $mountDir",
+ "chmod -R 700 $mountDir",
+ ], $server);
+
+ $server->settings->is_sentinel_enabled = true;
+ $server->settings->save();
+ $server->sentinelHeartbeat();
}
}
diff --git a/app/Actions/Server/StopLogDrain.php b/app/Actions/Server/StopLogDrain.php
index a5bce94a5..96c2466de 100644
--- a/app/Actions/Server/StopLogDrain.php
+++ b/app/Actions/Server/StopLogDrain.php
@@ -12,7 +12,7 @@ class StopLogDrain
public function handle(Server $server)
{
try {
- return instant_remote_process(['docker rm -f coolify-log-drain || true'], $server);
+ return instant_remote_process(['docker rm -f coolify-log-drain'], $server, false);
} catch (\Throwable $e) {
return handleError($e);
}
diff --git a/app/Actions/Server/StopSentinel.php b/app/Actions/Server/StopSentinel.php
index 21ffca3bd..aecb96c87 100644
--- a/app/Actions/Server/StopSentinel.php
+++ b/app/Actions/Server/StopSentinel.php
@@ -12,5 +12,6 @@ class StopSentinel
public function handle(Server $server)
{
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
+ $server->sentinelHeartbeat(isReset: true);
}
}
diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php
index 901f2cf77..53c443778 100644
--- a/app/Actions/Server/UpdateCoolify.php
+++ b/app/Actions/Server/UpdateCoolify.php
@@ -3,8 +3,8 @@
namespace App\Actions\Server;
use App\Jobs\PullHelperImageJob;
-use App\Models\InstanceSettings;
use App\Models\Server;
+use Illuminate\Support\Sleep;
use Lorisleiva\Actions\Concerns\AsAction;
class UpdateCoolify
@@ -19,49 +19,38 @@ class UpdateCoolify
public function handle($manual_update = false)
{
- try {
- $settings = InstanceSettings::get();
- $this->server = Server::find(0);
- if (! $this->server) {
+ if (isDev()) {
+ Sleep::for(10)->seconds();
+
+ return;
+ }
+ $settings = instanceSettings();
+ $this->server = Server::find(0);
+ if (! $this->server) {
+ return;
+ }
+ CleanupDocker::dispatch($this->server);
+ $this->latestVersion = get_latest_version_of_coolify();
+ $this->currentVersion = config('version');
+ if (! $manual_update) {
+ if (! $settings->is_auto_update_enabled) {
return;
}
- CleanupDocker::dispatch($this->server)->onQueue('high');
- $this->latestVersion = get_latest_version_of_coolify();
- $this->currentVersion = config('version');
- if (! $manual_update) {
- if (! $settings->is_auto_update_enabled) {
- return;
- }
- if ($this->latestVersion === $this->currentVersion) {
- return;
- }
- if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
- return;
- }
+ if ($this->latestVersion === $this->currentVersion) {
+ return;
+ }
+ if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
+ return;
}
- $this->update();
- $settings->new_version_available = false;
- $settings->save();
- } catch (\Throwable $e) {
- throw $e;
}
+ $this->update();
+ $settings->new_version_available = false;
+ $settings->save();
}
private function update()
{
- if (isDev()) {
- remote_process([
- 'sleep 10',
- ], $this->server);
-
- 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);
- }
+ PullHelperImageJob::dispatch($this->server);
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
diff --git a/app/Actions/Server/ValidateServer.php b/app/Actions/Server/ValidateServer.php
index d0a4cd6be..55b37a77c 100644
--- a/app/Actions/Server/ValidateServer.php
+++ b/app/Actions/Server/ValidateServer.php
@@ -9,6 +9,8 @@ class ValidateServer
{
use AsAction;
+ public string $jobQueue = 'high';
+
public ?string $uptime = null;
public ?string $error = null;
diff --git a/app/Actions/Service/DeleteService.php b/app/Actions/Service/DeleteService.php
index 194cf4db9..9b87454da 100644
--- a/app/Actions/Service/DeleteService.php
+++ b/app/Actions/Service/DeleteService.php
@@ -2,18 +2,20 @@
namespace App\Actions\Service;
+use App\Actions\Server\CleanupDocker;
use App\Models\Service;
+use Illuminate\Support\Facades\Log;
use Lorisleiva\Actions\Concerns\AsAction;
class DeleteService
{
use AsAction;
- public function handle(Service $service)
+ public function handle(Service $service, bool $deleteConfigurations, bool $deleteVolumes, bool $dockerCleanup, bool $deleteConnectedNetworks)
{
try {
$server = data_get($service, 'server');
- if ($server->isFunctional()) {
+ if ($deleteVolumes && $server->isFunctional()) {
$storagesToDelete = collect([]);
$service->environment_variables()->delete();
@@ -33,13 +35,29 @@ class DeleteService
foreach ($storagesToDelete as $storage) {
$commands[] = "docker volume rm -f $storage->name";
}
- $commands[] = "docker rm -f $service->uuid";
- instant_remote_process($commands, $server, false);
+ // Execute volume deletion first, this must be done first otherwise volumes will not be deleted.
+ if (! empty($commands)) {
+ foreach ($commands as $command) {
+ $result = instant_remote_process([$command], $server, false);
+ if ($result !== null && $result !== 0) {
+ Log::error('Error deleting volumes: '.$result);
+ }
+ }
+ }
}
+
+ if ($deleteConnectedNetworks) {
+ $service->delete_connected_networks($service->uuid);
+ }
+
+ instant_remote_process(["docker rm -f $service->uuid"], $server, throwError: false);
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
} finally {
+ if ($deleteConfigurations) {
+ $service->delete_configurations();
+ }
foreach ($service->applications()->get() as $application) {
$application->forceDelete();
}
@@ -50,6 +68,11 @@ class DeleteService
$task->delete();
}
$service->tags()->detach();
+ $service->forceDelete();
+
+ if ($dockerCleanup) {
+ CleanupDocker::dispatch($server, true);
+ }
}
}
}
diff --git a/app/Actions/Service/RestartService.php b/app/Actions/Service/RestartService.php
index 1b6a5c32c..4151ea947 100644
--- a/app/Actions/Service/RestartService.php
+++ b/app/Actions/Service/RestartService.php
@@ -9,6 +9,8 @@ class RestartService
{
use AsAction;
+ public string $jobQueue = 'high';
+
public function handle(Service $service)
{
StopService::run($service);
diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php
index 06d2e0efb..1dfaf6c49 100644
--- a/app/Actions/Service/StartService.php
+++ b/app/Actions/Service/StartService.php
@@ -10,9 +10,10 @@ class StartService
{
use AsAction;
+ public string $jobQueue = 'high';
+
public function handle(Service $service)
{
- ray('Starting service: '.$service->name);
$service->saveComposeConfigs();
$commands[] = 'cd '.$service->workdir();
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
@@ -34,8 +35,7 @@ class StartService
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} >/dev/null 2>&1 || true";
}
}
- $activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
- return $activity;
+ return remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
}
}
diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php
index 82b0b3ece..95b08b437 100644
--- a/app/Actions/Service/StopService.php
+++ b/app/Actions/Service/StopService.php
@@ -2,6 +2,7 @@
namespace App\Actions\Service;
+use App\Actions\Server\CleanupDocker;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -9,40 +10,27 @@ class StopService
{
use AsAction;
- public function handle(Service $service)
+ public string $jobQueue = 'high';
+
+ public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true)
{
try {
$server = $service->destination->server;
if (! $server->isFunctional()) {
return 'Server is not functional';
}
- ray('Stopping service: '.$service->name);
- $applications = $service->applications()->get();
- foreach ($applications as $application) {
- if ($applications->count() < 6) {
- instant_remote_process(command: ["docker stop --time=10 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
- }
- instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
- instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false);
- $application->update(['status' => 'exited']);
- }
- $dbs = $service->databases()->get();
- foreach ($dbs as $db) {
- if ($dbs->count() < 6) {
- instant_remote_process(command: ["docker stop --time=10 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
+ $containersToStop = $service->getContainersToStop();
+ $service->stopContainers($containersToStop, $server);
+
+ if (! $isDeleteOperation) {
+ $service->delete_connected_networks($service->uuid);
+ if ($dockerCleanup) {
+ CleanupDocker::dispatch($server, true);
}
- instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
- instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
- $db->update(['status' => 'exited']);
}
- instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy"], $service->server);
- instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
} catch (\Exception $e) {
- ray($e->getMessage());
-
return $e->getMessage();
}
-
}
}
diff --git a/app/Console/Commands/CheckApplicationDeploymentQueue.php b/app/Console/Commands/CheckApplicationDeploymentQueue.php
new file mode 100644
index 000000000..e89d26f2c
--- /dev/null
+++ b/app/Console/Commands/CheckApplicationDeploymentQueue.php
@@ -0,0 +1,50 @@
+option('seconds');
+ $deployments = ApplicationDeploymentQueue::whereIn('status', [
+ ApplicationDeploymentStatus::IN_PROGRESS,
+ ApplicationDeploymentStatus::QUEUED,
+ ])->where('created_at', '<=', now()->subSeconds($seconds))->get();
+ if ($deployments->isEmpty()) {
+ $this->info('No deployments found in the last '.$seconds.' seconds.');
+
+ return;
+ }
+
+ $this->info('Found '.$deployments->count().' deployments created in the last '.$seconds.' seconds.');
+
+ foreach ($deployments as $deployment) {
+ if ($this->option('force')) {
+ $this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
+ $this->cancelDeployment($deployment);
+ } else {
+ $this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
+ if ($this->confirm('Do you want to cancel this deployment?', true)) {
+ $this->cancelDeployment($deployment);
+ }
+ }
+ }
+ }
+
+ private function cancelDeployment(ApplicationDeploymentQueue $deployment)
+ {
+ $deployment->update(['status' => ApplicationDeploymentStatus::FAILED]);
+ if ($deployment->server?->isFunctional()) {
+ remote_process(['docker rm -f '.$deployment->deployment_uuid], $deployment->server, false);
+ }
+ }
+}
diff --git a/app/Console/Commands/CleanupApplicationDeploymentQueue.php b/app/Console/Commands/CleanupApplicationDeploymentQueue.php
index f068e3eb2..3aae28ae6 100644
--- a/app/Console/Commands/CleanupApplicationDeploymentQueue.php
+++ b/app/Console/Commands/CleanupApplicationDeploymentQueue.php
@@ -7,9 +7,9 @@ use Illuminate\Console\Command;
class CleanupApplicationDeploymentQueue extends Command
{
- protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
+ protected $signature = 'cleanup:deployment-queue {--team-id=}';
- protected $description = 'CleanupApplicationDeploymentQueue';
+ protected $description = 'Cleanup application deployment queue.';
public function handle()
{
diff --git a/app/Console/Commands/CleanupDatabase.php b/app/Console/Commands/CleanupDatabase.php
index 6f130626b..a0adc8b36 100644
--- a/app/Console/Commands/CleanupDatabase.php
+++ b/app/Console/Commands/CleanupDatabase.php
@@ -7,7 +7,7 @@ use Illuminate\Support\Facades\DB;
class CleanupDatabase extends Command
{
- protected $signature = 'cleanup:database {--yes}';
+ protected $signature = 'cleanup:database {--yes} {--keep-days=}';
protected $description = 'Cleanup database';
@@ -20,9 +20,9 @@ class CleanupDatabase extends Command
}
if (isCloud()) {
// Later on we can increase this to 180 days or dynamically set
- $keep_days = 60;
+ $keep_days = $this->option('keep-days') ?? 60;
} else {
- $keep_days = 60;
+ $keep_days = $this->option('keep-days') ?? 60;
}
echo "Keep days: $keep_days\n";
// Cleanup failed jobs table
@@ -64,6 +64,5 @@ class CleanupDatabase extends Command
if ($this->option('yes')) {
$webhooks->delete();
}
-
}
}
diff --git a/app/Console/Commands/CleanupQueue.php b/app/Console/Commands/CleanupRedis.php
similarity index 50%
rename from app/Console/Commands/CleanupQueue.php
rename to app/Console/Commands/CleanupRedis.php
index fd2b637ac..e16a82be4 100644
--- a/app/Console/Commands/CleanupQueue.php
+++ b/app/Console/Commands/CleanupRedis.php
@@ -5,20 +5,25 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;
-class CleanupQueue extends Command
+class CleanupRedis extends Command
{
- protected $signature = 'cleanup:queue';
+ protected $signature = 'cleanup:redis';
- protected $description = 'Cleanup Queue';
+ protected $description = 'Cleanup Redis';
public function handle()
{
- echo "Running queue cleanup...\n";
$prefix = config('database.redis.options.prefix');
+
$keys = Redis::connection()->keys('*:laravel*');
- foreach ($keys as $key) {
+ collect($keys)->each(function ($key) use ($prefix) {
$keyWithoutPrefix = str_replace($prefix, '', $key);
Redis::connection()->del($keyWithoutPrefix);
- }
+ });
+
+ $queueOverlaps = Redis::connection()->keys('*laravel-queue-overlap*');
+ collect($queueOverlaps)->each(function ($key) {
+ Redis::connection()->del($key);
+ });
}
}
diff --git a/app/Console/Commands/CleanupStuckedResources.php b/app/Console/Commands/CleanupStuckedResources.php
index 68beb448a..def3d5a2c 100644
--- a/app/Console/Commands/CleanupStuckedResources.php
+++ b/app/Console/Commands/CleanupStuckedResources.php
@@ -2,10 +2,13 @@
namespace App\Console\Commands;
+use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application;
+use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
+use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@@ -27,14 +30,32 @@ class CleanupStuckedResources extends Command
public function handle()
{
- ray('Running cleanup stucked resources.');
- echo "Running cleanup stucked resources.\n";
$this->cleanup_stucked_resources();
}
private function cleanup_stucked_resources()
{
-
+ try {
+ $servers = Server::all()->filter(function ($server) {
+ return $server->isFunctional();
+ });
+ foreach ($servers as $server) {
+ CleanupHelperContainersJob::dispatch($server);
+ }
+ } catch (\Throwable $e) {
+ echo "Error in cleaning stucked resources: {$e->getMessage()}\n";
+ }
+ try {
+ $applicationsDeploymentQueue = ApplicationDeploymentQueue::get();
+ foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) {
+ if (is_null($applicationDeploymentQueue->application)) {
+ echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n";
+ $applicationDeploymentQueue->delete();
+ }
+ }
+ } catch (\Throwable $e) {
+ echo "Error in cleaning stuck application deployment queue: {$e->getMessage()}\n";
+ }
try {
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($applications as $application) {
diff --git a/app/Console/Commands/CloudCheckSubscription.php b/app/Console/Commands/CloudCheckSubscription.php
new file mode 100644
index 000000000..6e237e84b
--- /dev/null
+++ b/app/Console/Commands/CloudCheckSubscription.php
@@ -0,0 +1,49 @@
+get();
+ foreach ($activeSubscribers as $team) {
+ $stripeSubscriptionId = $team->subscription->stripe_subscription_id;
+ $stripeInvoicePaid = $team->subscription->stripe_invoice_paid;
+ $stripeCustomerId = $team->subscription->stripe_customer_id;
+ if (! $stripeSubscriptionId) {
+ echo "Team {$team->id} has no subscription, but invoice status is: {$stripeInvoicePaid}\n";
+ echo "Link on Stripe: https://dashboard.stripe.com/customers/{$stripeCustomerId}\n";
+
+ continue;
+ }
+ $subscription = $stripe->subscriptions->retrieve($stripeSubscriptionId);
+ if ($subscription->status === 'active') {
+ continue;
+ }
+ echo "Subscription {$stripeSubscriptionId} is not active ({$subscription->status})\n";
+ echo "Link on Stripe: https://dashboard.stripe.com/subscriptions/{$stripeSubscriptionId}\n";
+ }
+ }
+}
diff --git a/app/Console/Commands/CloudCleanupSubscriptions.php b/app/Console/Commands/CloudCleanupSubscriptions.php
index d220aa00b..8bb420ab8 100644
--- a/app/Console/Commands/CloudCleanupSubscriptions.php
+++ b/app/Console/Commands/CloudCleanupSubscriptions.php
@@ -19,7 +19,6 @@ class CloudCleanupSubscriptions extends Command
return;
}
- ray()->clearAll();
$this->info('Cleaning up subcriptions teams');
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
@@ -74,7 +73,6 @@ class CloudCleanupSubscriptions extends Command
}
}
}
-
} catch (\Exception $e) {
$this->error($e->getMessage());
@@ -96,6 +94,5 @@ class CloudCleanupSubscriptions extends Command
]);
}
}
-
}
}
diff --git a/app/Console/Commands/Dev.php b/app/Console/Commands/Dev.php
index 964b8e46e..962000d07 100644
--- a/app/Console/Commands/Dev.php
+++ b/app/Console/Commands/Dev.php
@@ -6,6 +6,7 @@ use App\Models\InstanceSettings;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Process;
+use Symfony\Component\Yaml\Yaml;
class Dev extends Command
{
@@ -25,29 +26,48 @@ class Dev extends Command
return;
}
-
}
public function generateOpenApi()
{
// Generate OpenAPI documentation
echo "Generating OpenAPI documentation.\n";
- $process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
+ // https://github.com/OAI/OpenAPI-Specification/releases
+ $process = Process::run([
+ '/var/www/html/vendor/bin/openapi',
+ 'app',
+ '-o',
+ 'openapi.yaml',
+ '--version',
+ '3.1.0',
+ ]);
$error = $process->errorOutput();
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
$error = preg_replace('/^\h*\v+/m', '', $error);
echo $error;
echo $process->output();
+ // Convert YAML to JSON
+ $yaml = file_get_contents('openapi.yaml');
+ $json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT);
+ file_put_contents('openapi.json', $json);
+ echo "Converted OpenAPI YAML to JSON.\n";
}
public function init()
{
// Generate APP_KEY if not exists
- if (empty(env('APP_KEY'))) {
+ if (empty(config('app.key'))) {
echo "Generating APP_KEY.\n";
Artisan::call('key:generate');
}
+
+ // Generate STORAGE link if not exists
+ if (! file_exists(public_path('storage'))) {
+ echo "Generating STORAGE link.\n";
+ Artisan::call('storage:link');
+ }
+
// Seed database if it's empty
$settings = InstanceSettings::find(0);
if (! $settings) {
diff --git a/app/Console/Commands/Emails.php b/app/Console/Commands/Emails.php
index 36722564c..cda4ca84f 100644
--- a/app/Console/Commands/Emails.php
+++ b/app/Console/Commands/Emails.php
@@ -15,7 +15,6 @@ use App\Notifications\Application\DeploymentSuccess;
use App\Notifications\Application\StatusChanged;
use App\Notifications\Database\BackupFailed;
use App\Notifications\Database\BackupSuccess;
-use App\Notifications\Database\DailyBackup;
use App\Notifications\Test;
use Exception;
use Illuminate\Console\Command;
@@ -121,28 +120,10 @@ class Emails extends Command
$this->mail = (new Test)->toMail();
$this->sendEmail();
break;
- case 'database-backup-statuses-daily':
- $scheduled_backups = ScheduledDatabaseBackup::all();
- $databases = collect();
- foreach ($scheduled_backups as $scheduled_backup) {
- $last_days_backups = $scheduled_backup->get_last_days_backup_status();
- if ($last_days_backups->isEmpty()) {
- continue;
- }
- $failed = $last_days_backups->where('status', 'failed');
- $database = $scheduled_backup->database;
- $databases->put($database->name, [
- 'failed_count' => $failed->count(),
- ]);
- }
- $this->mail = (new DailyBackup($databases))->toMail();
- $this->sendEmail();
- break;
case 'application-deployment-success-daily':
$applications = Application::all();
foreach ($applications as $application) {
$deployments = $application->get_last_days_deployments();
- ray($deployments);
if ($deployments->isEmpty()) {
continue;
}
diff --git a/app/Console/Commands/Horizon.php b/app/Console/Commands/Horizon.php
index 65a142d6e..655729ec9 100644
--- a/app/Console/Commands/Horizon.php
+++ b/app/Console/Commands/Horizon.php
@@ -12,8 +12,8 @@ class Horizon extends Command
public function handle()
{
- if (config('coolify.is_horizon_enabled')) {
- $this->info('Horizon is enabled. Starting.');
+ if (config('constants.horizon.is_horizon_enabled')) {
+ $this->info('[x]: Horizon is enabled. Starting.');
$this->call('horizon');
exit(0);
} else {
diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php
index 7bfd1a14f..57bbe896b 100644
--- a/app/Console/Commands/Init.php
+++ b/app/Console/Commands/Init.php
@@ -2,23 +2,23 @@
namespace App\Console\Commands;
-use App\Actions\Server\StopSentinel;
use App\Enums\ActivityTypes;
use App\Enums\ApplicationDeploymentStatus;
-use App\Jobs\CleanupHelperContainersJob;
+use App\Jobs\CheckHelperImageJob;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Environment;
-use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
+use App\Models\User;
use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class Init extends Command
{
- protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments} {--cleanup-proxy-networks}';
+ protected $signature = 'app:init {--force-cloud}';
protected $description = 'Cleanup instance related stuffs';
@@ -26,75 +26,106 @@ class Init extends Command
public function handle()
{
+ $this->optimize();
+
+ if (isCloud() && ! $this->option('force-cloud')) {
+ echo "Skipping init as we are on cloud and --force-cloud option is not set\n";
+
+ return;
+ }
+
$this->servers = Server::all();
- $this->alive();
- get_public_ips();
- if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
- foreach ($this->servers as $server) {
- if ($server->settings->is_metrics_enabled === true) {
- $server->settings->update(['is_metrics_enabled' => false]);
- }
- if ($server->isFunctional()) {
- StopSentinel::dispatch($server);
- }
- }
+ if (isCloud()) {
+ } else {
+ $this->send_alive_signal();
+ get_public_ips();
}
- $full_cleanup = $this->option('full-cleanup');
- $cleanup_deployments = $this->option('cleanup-deployments');
- $cleanup_proxy_networks = $this->option('cleanup-proxy-networks');
+ // Backward compatibility
$this->replace_slash_in_environment_name();
- if ($cleanup_deployments) {
- echo "Running cleanup deployments.\n";
- $this->cleanup_in_progress_application_deployments();
-
- return;
- }
- if ($cleanup_proxy_networks) {
- echo "Running cleanup proxy networks.\n";
+ $this->restore_coolify_db_backup();
+ $this->update_user_emails();
+ //
+ $this->update_traefik_labels();
+ if (! isCloud() || $this->option('force-cloud')) {
$this->cleanup_unused_network_from_coolify_proxy();
-
- return;
}
- if ($full_cleanup) {
- // Required for falsely deleted coolify db
- $this->restore_coolify_db_backup();
- $this->update_traefik_labels();
- $this->cleanup_unused_network_from_coolify_proxy();
+ if (isCloud()) {
$this->cleanup_unnecessary_dynamic_proxy_configuration();
+ } else {
$this->cleanup_in_progress_application_deployments();
- $this->cleanup_stucked_helper_containers();
- $this->call('cleanup:queue');
- $this->call('cleanup:stucked-resources');
- if (! isCloud()) {
- try {
- $localhost = $this->servers->where('id', 0)->first();
- $localhost->setupDynamicProxyConfiguration();
- } catch (\Throwable $e) {
- echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
- }
- }
+ }
+ echo "[3]: Cleanup Redis keys.\n";
+ $this->call('cleanup:redis');
- $settings = InstanceSettings::get();
- if (! is_null(env('AUTOUPDATE', null))) {
- if (env('AUTOUPDATE') == true) {
+ echo "[4]: Cleanup stucked resources.\n";
+ $this->call('cleanup:stucked-resources');
+
+ try {
+ $this->pullHelperImage();
+ } catch (\Throwable $e) {
+ //
+ }
+
+ if (isCloud()) {
+ try {
+ $this->pullTemplatesFromCDN();
+ } catch (\Throwable $e) {
+ echo "Could not pull templates from CDN: {$e->getMessage()}\n";
+ }
+ }
+
+ if (! isCloud()) {
+ try {
+ $this->pullTemplatesFromCDN();
+ } catch (\Throwable $e) {
+ echo "Could not pull templates from CDN: {$e->getMessage()}\n";
+ }
+ try {
+ $localhost = $this->servers->where('id', 0)->first();
+ $localhost->setupDynamicProxyConfiguration();
+ } catch (\Throwable $e) {
+ echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
+ }
+ $settings = instanceSettings();
+ if (! is_null(config('constants.coolify.autoupdate', null))) {
+ if (config('constants.coolify.autoupdate') == true) {
$settings->update(['is_auto_update_enabled' => true]);
} else {
$settings->update(['is_auto_update_enabled' => false]);
}
}
- if (isCloud()) {
- $response = Http::retry(3, 1000)->get(config('constants.services.official'));
- if ($response->successful()) {
- $services = $response->json();
- File::put(base_path('templates/service-templates.json'), json_encode($services));
- }
- }
-
- return;
}
- $this->cleanup_stucked_helper_containers();
- $this->call('cleanup:stucked-resources');
+ }
+
+ private function pullHelperImage()
+ {
+ CheckHelperImageJob::dispatch();
+ }
+
+ private function pullTemplatesFromCDN()
+ {
+ $response = Http::retry(3, 1000)->get(config('constants.services.official'));
+ if ($response->successful()) {
+ $services = $response->json();
+ File::put(base_path('templates/service-templates.json'), json_encode($services));
+ }
+ }
+
+ private function optimize()
+ {
+ echo "[1]: Optimizing Laravel (caching config, routes, views).\n";
+ Artisan::call('optimize:clear');
+ Artisan::call('optimize');
+ }
+
+ private function update_user_emails()
+ {
+ try {
+ User::whereRaw('email ~ \'[A-Z]\'')->get()->each(fn (User $user) => $user->update(['email' => strtolower($user->email)]));
+ } catch (\Throwable $e) {
+ echo "Error in updating user emails: {$e->getMessage()}\n";
+ }
}
private function update_traefik_labels()
@@ -108,33 +139,27 @@ class Init extends Command
private function cleanup_unnecessary_dynamic_proxy_configuration()
{
- if (isCloud()) {
- foreach ($this->servers as $server) {
- try {
- if (! $server->isFunctional()) {
- continue;
- }
- if ($server->id === 0) {
- continue;
- }
- $file = $server->proxyPath().'/dynamic/coolify.yaml';
-
- return instant_remote_process([
- "rm -f $file",
- ], $server, false);
- } catch (\Throwable $e) {
- echo "Error in cleaning up unnecessary dynamic proxy configuration: {$e->getMessage()}\n";
+ foreach ($this->servers as $server) {
+ try {
+ if (! $server->isFunctional()) {
+ continue;
}
+ if ($server->id === 0) {
+ continue;
+ }
+ $file = $server->proxyPath().'/dynamic/coolify.yaml';
+ return instant_remote_process([
+ "rm -f $file",
+ ], $server, false);
+ } catch (\Throwable $e) {
+ echo "Error in cleaning up unnecessary dynamic proxy configuration: {$e->getMessage()}\n";
}
}
}
private function cleanup_unused_network_from_coolify_proxy()
{
- if (isCloud()) {
- return;
- }
foreach ($this->servers as $server) {
if (! $server->isFunctional()) {
continue;
@@ -175,73 +200,50 @@ class Init extends Command
private function restore_coolify_db_backup()
{
- try {
- $database = StandalonePostgresql::withTrashed()->find(0);
- if ($database && $database->trashed()) {
- echo "Restoring coolify db backup\n";
- $database->restore();
- $scheduledBackup = ScheduledDatabaseBackup::find(0);
- if (! $scheduledBackup) {
- ScheduledDatabaseBackup::create([
- 'id' => 0,
- 'enabled' => true,
- 'save_s3' => false,
- 'frequency' => '0 0 * * *',
- 'database_id' => $database->id,
- 'database_type' => 'App\Models\StandalonePostgresql',
- 'team_id' => 0,
- ]);
+ if (version_compare('4.0.0-beta.179', config('version'), '<=')) {
+ try {
+ $database = StandalonePostgresql::withTrashed()->find(0);
+ if ($database && $database->trashed()) {
+ echo "Restoring coolify db backup\n";
+ $database->restore();
+ $scheduledBackup = ScheduledDatabaseBackup::find(0);
+ if (! $scheduledBackup) {
+ ScheduledDatabaseBackup::create([
+ 'id' => 0,
+ 'enabled' => true,
+ 'save_s3' => false,
+ 'frequency' => '0 0 * * *',
+ 'database_id' => $database->id,
+ 'database_type' => \App\Models\StandalonePostgresql::class,
+ 'team_id' => 0,
+ ]);
+ }
}
- }
- } catch (\Throwable $e) {
- echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
- }
- }
-
- private function cleanup_stucked_helper_containers()
- {
- foreach ($this->servers as $server) {
- if ($server->isFunctional()) {
- CleanupHelperContainersJob::dispatch($server);
+ } catch (\Throwable $e) {
+ echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
}
}
}
- private function alive()
+ private function send_alive_signal()
{
$id = config('app.id');
$version = config('version');
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) {
- echo "Skipping alive as do_not_track is enabled\n";
+ echo "[2]: Skipping sending live signal as do_not_track is enabled\n";
return;
}
try {
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
- echo "I am alive!\n";
+ echo "[2]: Sending live signal!\n";
} catch (\Throwable $e) {
- echo "Error in alive: {$e->getMessage()}\n";
+ echo "[2]: Error in sending live signal: {$e->getMessage()}\n";
}
}
- // private function cleanup_ssh()
- // {
- // TODO: it will cleanup id.root@host.docker.internal
- // try {
- // $files = Storage::allFiles('ssh/keys');
- // foreach ($files as $file) {
- // Storage::delete($file);
- // }
- // $files = Storage::allFiles('ssh/mux');
- // foreach ($files as $file) {
- // Storage::delete($file);
- // }
- // } catch (\Throwable $e) {
- // echo "Error in cleaning ssh: {$e->getMessage()}\n";
- // }
- // }
private function cleanup_in_progress_application_deployments()
{
// Cleanup any failed deployments
@@ -251,7 +253,6 @@ class Init extends Command
}
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
foreach ($queued_inprogress_deployments as $deployment) {
- ray($deployment->id, $deployment->status);
echo "Cleaning up deployment: {$deployment->id}\n";
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
$deployment->save();
@@ -263,11 +264,13 @@ class Init extends Command
private function replace_slash_in_environment_name()
{
- $environments = Environment::all();
- foreach ($environments as $environment) {
- if (str_contains($environment->name, '/')) {
- $environment->name = str_replace('/', '-', $environment->name);
- $environment->save();
+ if (version_compare('4.0.0-beta.298', config('version'), '<=')) {
+ $environments = Environment::all();
+ foreach ($environments as $environment) {
+ if (str_contains($environment->name, '/')) {
+ $environment->name = str_replace('/', '-', $environment->name);
+ $environment->save();
+ }
}
}
}
diff --git a/app/Console/Commands/NotifyDemo.php b/app/Console/Commands/NotifyDemo.php
index 81333b868..f0131b7b2 100644
--- a/app/Console/Commands/NotifyDemo.php
+++ b/app/Console/Commands/NotifyDemo.php
@@ -36,8 +36,6 @@ class NotifyDemo extends Command
return;
}
-
- ray($channel);
}
private function showHelp()
diff --git a/app/Console/Commands/OpenApi.php b/app/Console/Commands/OpenApi.php
index e8d73ef47..6cbcb310c 100644
--- a/app/Console/Commands/OpenApi.php
+++ b/app/Console/Commands/OpenApi.php
@@ -15,12 +15,19 @@ class OpenApi extends Command
{
// Generate OpenAPI documentation
echo "Generating OpenAPI documentation.\n";
- $process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
+ // https://github.com/OAI/OpenAPI-Specification/releases
+ $process = Process::run([
+ '/var/www/html/vendor/bin/openapi',
+ 'app',
+ '-o',
+ 'openapi.yaml',
+ '--version',
+ '3.1.0',
+ ]);
$error = $process->errorOutput();
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
$error = preg_replace('/^\h*\v+/m', '', $error);
echo $error;
echo $process->output();
-
}
}
diff --git a/app/Console/Commands/Scheduler.php b/app/Console/Commands/Scheduler.php
index 304cb357d..9ee7b06e6 100644
--- a/app/Console/Commands/Scheduler.php
+++ b/app/Console/Commands/Scheduler.php
@@ -12,8 +12,8 @@ class Scheduler extends Command
public function handle()
{
- if (config('coolify.is_scheduler_enabled')) {
- $this->info('Scheduler is enabled. Starting.');
+ if (config('constants.horizon.is_scheduler_enabled')) {
+ $this->info('[x]: Scheduler is enabled. Starting.');
$this->call('schedule:work');
exit(0);
} else {
diff --git a/app/Console/Commands/ServicesGenerate.php b/app/Console/Commands/ServicesGenerate.php
index de64afefa..1559e5f6d 100644
--- a/app/Console/Commands/ServicesGenerate.php
+++ b/app/Console/Commands/ServicesGenerate.php
@@ -3,128 +3,82 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
+use Illuminate\Support\Arr;
use Symfony\Component\Yaml\Yaml;
class ServicesGenerate extends Command
{
/**
- * The name and signature of the console command.
- *
- * @var string
+ * {@inheritdoc}
*/
protected $signature = 'services:generate';
/**
- * The console command description.
- *
- * @var string
+ * {@inheritdoc}
*/
protected $description = 'Generate service-templates.yaml based on /templates/compose directory';
- /**
- * Execute the console command.
- */
- public function handle()
+ public function handle(): int
{
- $files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
- $files = array_filter($files, function ($file) {
- return strpos($file, '.yaml') !== false;
- });
- $serviceTemplatesJson = [];
- foreach ($files as $file) {
- $parsed = $this->process_file($file);
- if ($parsed) {
- $name = data_get($parsed, 'name');
- $parsed = data_forget($parsed, 'name');
- $serviceTemplatesJson[$name] = $parsed;
- }
- }
- $serviceTemplatesJson = json_encode($serviceTemplatesJson);
- file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
+ $serviceTemplatesJson = collect(glob(base_path('templates/compose/*.yaml')))
+ ->mapWithKeys(function ($file): array {
+ $file = basename($file);
+ $parsed = $this->processFile($file);
+
+ return $parsed === false ? [] : [
+ Arr::pull($parsed, 'name') => $parsed,
+ ];
+ })->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+
+ file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson.PHP_EOL);
+
+ return self::SUCCESS;
}
- private function process_file($file)
+ private function processFile(string $file): false|array
{
- $serviceName = str($file)->before('.yaml')->value();
$content = file_get_contents(base_path("templates/compose/$file"));
- // $this->info($content);
- $ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values();
- if ($ignore->count() > 0) {
- $ignore = (bool) str($ignore[0])->after('# ignore:')->trim()->value();
- } else {
- $ignore = false;
- }
- if ($ignore) {
+
+ $data = collect(explode(PHP_EOL, $content))->mapWithKeys(function ($line): array {
+ preg_match('/^#(?
Only use multiple domains if you know what you are doing.');
} else {
- $this->dispatch('success', 'Service saved.');
+ ! $warning && $this->dispatch('success', 'Service saved.');
}
- } catch (\Throwable $e) {
- return handleError($e, $this);
- } finally {
$this->application->service->parse();
$this->dispatch('refresh');
$this->dispatch('configurationChanged');
+ } catch (\Throwable $e) {
+ $originalFqdn = $this->application->getOriginal('fqdn');
+ if ($originalFqdn !== $this->application->fqdn) {
+ $this->application->fqdn = $originalFqdn;
+ }
+
+ return handleError($e, $this);
}
}
diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php
index 6cd54883e..4d070bc0c 100644
--- a/app/Livewire/Project/Service/FileStorage.php
+++ b/app/Livewire/Project/Service/FileStorage.php
@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Service;
use App\Models\Application;
+use App\Models\InstanceSettings;
use App\Models\LocalFileVolume;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@@ -14,6 +15,8 @@ use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class FileStorage extends Component
@@ -83,8 +86,16 @@ class FileStorage extends Component
}
}
- public function delete()
+ public function delete($password)
{
+ if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
+ if (! Hash::check($password, Auth::user()->password)) {
+ $this->addError('password', 'The provided password is incorrect.');
+
+ return;
+ }
+ }
+
try {
$message = 'File deleted.';
if ($this->fileStorage->is_directory) {
@@ -129,6 +140,13 @@ class FileStorage extends Component
public function render()
{
- return view('livewire.project.service.file-storage');
+ return view('livewire.project.service.file-storage', [
+ 'directoryDeletionCheckboxes' => [
+ ['id' => 'permanently_delete', 'label' => 'The selected directory and all its contents will be permantely deleted form the server.'],
+ ],
+ 'fileDeletionCheckboxes' => [
+ ['id' => 'permanently_delete', 'label' => 'The selected file will be permanently deleted form the server.'],
+ ],
+ ]);
}
}
diff --git a/app/Livewire/Project/Service/Index.php b/app/Livewire/Project/Service/Index.php
index 0a7b6ec90..ba4ebe2fc 100644
--- a/app/Livewire/Project/Service/Index.php
+++ b/app/Livewire/Project/Service/Index.php
@@ -48,7 +48,6 @@ class Index extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
}
-
}
public function generateDockerCompose()
diff --git a/app/Livewire/Project/Service/Navbar.php b/app/Livewire/Project/Service/Navbar.php
index e6bb6d9bf..ee43dc911 100644
--- a/app/Livewire/Project/Service/Navbar.php
+++ b/app/Livewire/Project/Service/Navbar.php
@@ -7,6 +7,7 @@ use App\Actions\Service\StopService;
use App\Actions\Shared\PullImage;
use App\Events\ServiceStatusChanged;
use App\Models\Service;
+use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use Spatie\Activitylog\Models\Activity;
@@ -20,12 +21,13 @@ class Navbar extends Component
public $isDeploymentProgress = false;
+ public $docker_cleanup = true;
+
public $title = 'Configuration';
public function mount()
{
if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
- ray('isConfigurationChanged init');
$this->service->isConfigurationChanged(true);
$this->dispatch('configurationChanged');
}
@@ -33,16 +35,17 @@ class Navbar extends Component
public function getListeners()
{
- $userId = auth()->user()->id;
+ $userId = Auth::id();
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted',
+ 'envsUpdated' => '$refresh',
];
}
public function serviceStarted()
{
- $this->dispatch('success', 'Service status changed.');
+ // $this->dispatch('success', 'Service status changed.');
if (is_null($this->service->config_hash) || $this->service->isConfigurationChanged()) {
$this->service->isConfigurationChanged(true);
$this->dispatch('configurationChanged');
@@ -62,11 +65,6 @@ class Navbar extends Component
$this->dispatch('success', 'Service status updated.');
}
- public function render()
- {
- return view('livewire.project.service.navbar');
- }
-
public function checkDeployments()
{
try {
@@ -79,7 +77,7 @@ class Navbar extends Component
} else {
$this->isDeploymentProgress = false;
}
- } catch (\Throwable $e) {
+ } catch (\Throwable) {
$this->isDeploymentProgress = false;
}
}
@@ -97,14 +95,9 @@ class Navbar extends Component
$this->dispatch('activityMonitor', $activity->id);
}
- public function stop(bool $forceCleanup = false)
+ public function stop()
{
- StopService::run($this->service);
- if ($forceCleanup) {
- $this->dispatch('success', 'Containers cleaned up.');
- } else {
- $this->dispatch('success', 'Service stopped.');
- }
+ StopService::run($this->service, false, $this->docker_cleanup);
ServiceStatusChanged::dispatch();
}
@@ -116,11 +109,35 @@ class Navbar extends Component
return;
}
- PullImage::run($this->service);
- StopService::run($this->service);
+ StopService::run(service: $this->service, dockerCleanup: false);
$this->service->parse();
$this->dispatch('imagePulled');
$activity = StartService::run($this->service);
$this->dispatch('activityMonitor', $activity->id);
}
+
+ public function pullAndRestartEvent()
+ {
+ $this->checkDeployments();
+ if ($this->isDeploymentProgress) {
+ $this->dispatch('error', 'There is a deployment in progress.');
+
+ return;
+ }
+ PullImage::run($this->service);
+ StopService::run(service: $this->service, dockerCleanup: false);
+ $this->service->parse();
+ $this->dispatch('imagePulled');
+ $activity = StartService::run($this->service);
+ $this->dispatch('activityMonitor', $activity->id);
+ }
+
+ public function render()
+ {
+ return view('livewire.project.service.navbar', [
+ 'checkboxes' => [
+ ['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
+ ],
+ ]);
+ }
}
diff --git a/app/Livewire/Project/Service/ServiceApplicationView.php b/app/Livewire/Project/Service/ServiceApplicationView.php
index e7d00c3dd..8324ee645 100644
--- a/app/Livewire/Project/Service/ServiceApplicationView.php
+++ b/app/Livewire/Project/Service/ServiceApplicationView.php
@@ -2,8 +2,12 @@
namespace App\Livewire\Project\Service;
+use App\Models\InstanceSettings;
use App\Models\ServiceApplication;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
use Livewire\Component;
+use Spatie\Url\Url;
class ServiceApplicationView extends Component
{
@@ -11,6 +15,10 @@ class ServiceApplicationView extends Component
public $parameters;
+ public $docker_cleanup = true;
+
+ public $delete_volumes = true;
+
protected $rules = [
'application.human_name' => 'nullable',
'application.description' => 'nullable',
@@ -23,22 +31,6 @@ class ServiceApplicationView extends Component
'application.is_stripprefix_enabled' => 'nullable|boolean',
];
- public function render()
- {
- return view('livewire.project.service.service-application-view');
- }
-
- public function updatedApplicationFqdn()
- {
- $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
- $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
- $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
- return str($domain)->trim()->lower();
- });
- $this->application->fqdn = $this->application->fqdn->unique()->implode(',');
- $this->application->save();
- }
-
public function instantSave()
{
$this->submit();
@@ -56,8 +48,16 @@ class ServiceApplicationView extends Component
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
}
- public function delete()
+ public function delete($password)
{
+ if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
+ if (! Hash::check($password, Auth::user()->password)) {
+ $this->addError('password', 'The provided password is incorrect.');
+
+ return;
+ }
+ }
+
try {
$this->application->delete();
$this->dispatch('success', 'Application deleted.');
@@ -76,6 +76,18 @@ class ServiceApplicationView extends Component
public function submit()
{
try {
+ $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
+ $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
+ $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
+ Url::fromString($domain, ['http', 'https']);
+
+ return str($domain)->trim()->lower();
+ });
+ $this->application->fqdn = $this->application->fqdn->unique()->implode(',');
+ $warning = sslipDomainWarning($this->application->fqdn);
+ if ($warning) {
+ $this->dispatch('warning', __('warning.sslipdomain'));
+ }
check_domain_usage(resource: $this->application);
$this->validate();
$this->application->save();
@@ -83,12 +95,29 @@ class ServiceApplicationView extends Component
if (str($this->application->fqdn)->contains(',')) {
$this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.
Only use multiple domains if you know what you are doing.');
} else {
- $this->dispatch('success', 'Service saved.');
+ ! $warning && $this->dispatch('success', 'Service saved.');
}
- } catch (\Throwable $e) {
- return handleError($e, $this);
- } finally {
$this->dispatch('generateDockerCompose');
+ } catch (\Throwable $e) {
+ $originalFqdn = $this->application->getOriginal('fqdn');
+ if ($originalFqdn !== $this->application->fqdn) {
+ $this->application->fqdn = $originalFqdn;
+ }
+
+ return handleError($e, $this);
}
}
+
+ public function render()
+ {
+ return view('livewire.project.service.service-application-view', [
+ 'checkboxes' => [
+ ['id' => 'delete_volumes', 'label' => __('resource.delete_volumes')],
+ ['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
+ // ['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this Ressource will be permanently deleted from local storage.'],
+ // ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected S3 Storage.'],
+ // ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected SFTP Storage.']
+ ],
+ ]);
+ }
}
diff --git a/app/Livewire/Project/Service/StackForm.php b/app/Livewire/Project/Service/StackForm.php
index 04bb136db..2c751aa92 100644
--- a/app/Livewire/Project/Service/StackForm.php
+++ b/app/Livewire/Project/Service/StackForm.php
@@ -33,7 +33,8 @@ class StackForm extends Component
$key = data_get($field, 'key');
$value = data_get($field, 'value');
$rules = data_get($field, 'rules', 'nullable');
- $isPassword = data_get($field, 'isPassword');
+ $isPassword = data_get($field, 'isPassword', false);
+ $customHelper = data_get($field, 'customHelper', false);
$this->fields->put($key, [
'serviceName' => $serviceName,
'key' => $key,
@@ -41,13 +42,22 @@ class StackForm extends Component
'value' => $value,
'isPassword' => $isPassword,
'rules' => $rules,
+ 'customHelper' => $customHelper,
]);
$this->rules["fields.$key.value"] = $rules;
$this->validationAttributes["fields.$key.value"] = $fieldKey;
}
}
- $this->fields = $this->fields->sortBy('name');
+ $this->fields = $this->fields->groupBy('serviceName')->map(function ($group) {
+ return $group->sortBy(function ($field) {
+ return data_get($field, 'isPassword') ? 1 : 0;
+ })->mapWithKeys(function ($field) {
+ return [$field['key'] => $field];
+ });
+ })->flatMap(function ($group) {
+ return $group;
+ });
}
public function saveCompose($raw)
diff --git a/app/Livewire/Project/Shared/Danger.php b/app/Livewire/Project/Shared/Danger.php
index 5f0178be4..a0b4ac2c4 100644
--- a/app/Livewire/Project/Shared/Danger.php
+++ b/app/Livewire/Project/Shared/Danger.php
@@ -3,6 +3,12 @@
namespace App\Livewire\Project\Shared;
use App\Jobs\DeleteResourceJob;
+use App\Models\InstanceSettings;
+use App\Models\Service;
+use App\Models\ServiceApplication;
+use App\Models\ServiceDatabase;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -10,6 +16,8 @@ class Danger extends Component
{
public $resource;
+ public $resourceName;
+
public $projectUuid;
public $environmentName;
@@ -18,22 +26,84 @@ class Danger extends Component
public bool $delete_volumes = true;
+ public bool $docker_cleanup = true;
+
+ public bool $delete_connected_networks = true;
+
public ?string $modalId = null;
+ public string $resourceDomain = '';
+
public function mount()
{
- $this->modalId = new Cuid2;
$parameters = get_route_parameters();
+ $this->modalId = new Cuid2;
$this->projectUuid = data_get($parameters, 'project_uuid');
$this->environmentName = data_get($parameters, 'environment_name');
+
+ if ($this->resource === null) {
+ if (isset($parameters['service_uuid'])) {
+ $this->resource = Service::where('uuid', $parameters['service_uuid'])->first();
+ } elseif (isset($parameters['stack_service_uuid'])) {
+ $this->resource = ServiceApplication::where('uuid', $parameters['stack_service_uuid'])->first()
+ ?? ServiceDatabase::where('uuid', $parameters['stack_service_uuid'])->first();
+ }
+ }
+
+ if ($this->resource === null) {
+ $this->resourceName = 'Unknown Resource';
+
+ return;
+ }
+
+ if (! method_exists($this->resource, 'type')) {
+ $this->resourceName = 'Unknown Resource';
+
+ return;
+ }
+
+ $this->resourceName = match ($this->resource->type()) {
+ 'application' => $this->resource->name ?? 'Application',
+ 'standalone-postgresql',
+ 'standalone-redis',
+ 'standalone-mongodb',
+ 'standalone-mysql',
+ 'standalone-mariadb',
+ 'standalone-keydb',
+ 'standalone-dragonfly',
+ 'standalone-clickhouse' => $this->resource->name ?? 'Database',
+ 'service' => $this->resource->name ?? 'Service',
+ 'service-application' => $this->resource->name ?? 'Service Application',
+ 'service-database' => $this->resource->name ?? 'Service Database',
+ default => 'Unknown Resource',
+ };
}
- public function delete()
+ public function delete($password)
{
+ if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
+ if (! Hash::check($password, Auth::user()->password)) {
+ $this->addError('password', 'The provided password is incorrect.');
+
+ return;
+ }
+ }
+
+ if (! $this->resource) {
+ $this->addError('resource', 'Resource not found.');
+
+ return;
+ }
+
try {
- // $this->authorize('delete', $this->resource);
$this->resource->delete();
- DeleteResourceJob::dispatch($this->resource, $this->delete_configurations, $this->delete_volumes);
+ DeleteResourceJob::dispatch(
+ $this->resource,
+ $this->delete_configurations,
+ $this->delete_volumes,
+ $this->docker_cleanup,
+ $this->delete_connected_networks
+ );
return redirect()->route('project.resource.index', [
'project_uuid' => $this->projectUuid,
@@ -43,4 +113,19 @@ class Danger extends Component
return handleError($e, $this);
}
}
+
+ public function render()
+ {
+ return view('livewire.project.shared.danger', [
+ 'checkboxes' => [
+ ['id' => 'delete_volumes', 'label' => __('resource.delete_volumes')],
+ ['id' => 'delete_connected_networks', 'label' => __('resource.delete_connected_networks')],
+ ['id' => 'delete_configurations', 'label' => __('resource.delete_configurations')],
+ ['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
+ // ['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this Ressource will be permanently deleted from local storage.'],
+ // ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected S3 Storage.'],
+ // ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected SFTP Storage.']
+ ],
+ ]);
+ }
}
diff --git a/app/Livewire/Project/Shared/Destination.php b/app/Livewire/Project/Shared/Destination.php
index a2c018beb..c305e817c 100644
--- a/app/Livewire/Project/Shared/Destination.php
+++ b/app/Livewire/Project/Shared/Destination.php
@@ -5,9 +5,11 @@ namespace App\Livewire\Project\Shared;
use App\Actions\Application\StopApplicationOneServer;
use App\Actions\Docker\GetContainersStatus;
use App\Events\ApplicationStatusChanged;
-use App\Jobs\ContainerStatusJob;
+use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\StandaloneDocker;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -115,8 +117,16 @@ class Destination extends Component
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
}
- public function removeServer(int $network_id, int $server_id)
+ public function removeServer(int $network_id, int $server_id, $password)
{
+ if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
+ if (! Hash::check($password, Auth::user()->password)) {
+ $this->addError('password', 'The provided password is incorrect.');
+
+ return;
+ }
+ }
+
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) {
$this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.');
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
index a859c90b0..0dbf0f957 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
@@ -48,14 +48,6 @@ class Add extends Component
public function submit()
{
$this->validate();
- // if (str($this->value)->startsWith('{{') && str($this->value)->endsWith('}}')) {
- // $type = str($this->value)->after('{{')->before('.')->value;
- // if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
- // $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
-
- // return;
- // }
- // }
$this->dispatch('saveKey', [
'key' => $this->key,
'value' => $this->value,
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php
index 055788b57..787d33a69 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php
@@ -35,7 +35,7 @@ class All extends Component
public function mount()
{
$this->resourceClass = get_class($this->resource);
- $resourceWithPreviews = ['App\Models\Application'];
+ $resourceWithPreviews = [\App\Models\Application::class];
$simpleDockerfile = ! is_null(data_get($this->resource, 'dockerfile'));
if (str($this->resourceClass)->contains($resourceWithPreviews) && ! $simpleDockerfile) {
$this->showPreview = true;
@@ -53,30 +53,16 @@ class All extends Component
public function sortEnvironmentVariables()
{
- if ($this->resource->type() === 'application') {
- $this->resource->load(['environment_variables', 'environment_variables_preview']);
- } else {
- $this->resource->load(['environment_variables']);
+ if (! data_get($this->resource, 'settings.is_env_sorting_enabled')) {
+ if ($this->resource->environment_variables) {
+ $this->resource->environment_variables = $this->resource->environment_variables->sortBy('order')->values();
+ }
+
+ if ($this->resource->environment_variables_preview) {
+ $this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('order')->values();
+ }
}
- $sortBy = data_get($this->resource, 'settings.is_env_sorting_enabled') ? 'key' : 'order';
-
- $sortFunction = function ($variables) use ($sortBy) {
- if (! $variables) {
- return $variables;
- }
- if ($sortBy === 'key') {
- return $variables->sortBy(function ($item) {
- return strtolower($item->key);
- }, SORT_NATURAL | SORT_FLAG_CASE)->values();
- } else {
- return $variables->sortBy('order')->values();
- }
- };
-
- $this->resource->environment_variables = $sortFunction($this->resource->environment_variables);
- $this->resource->environment_variables_preview = $sortFunction($this->resource->environment_variables_preview);
-
$this->getDevView();
}
@@ -121,6 +107,8 @@ class All extends Component
$this->sortEnvironmentVariables();
} catch (\Throwable $e) {
return handleError($e, $this);
+ } finally {
+ $this->refreshEnvs();
}
}
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
index 463ceecad..e71cd9f42 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
@@ -37,6 +37,7 @@ class Show extends Component
'env.is_literal' => 'required|boolean',
'env.is_shown_once' => 'required|boolean',
'env.real_value' => 'nullable',
+ 'env.is_required' => 'required|boolean',
];
protected $validationAttributes = [
@@ -46,6 +47,7 @@ class Show extends Component
'env.is_multiline' => 'Multiline',
'env.is_literal' => 'Literal',
'env.is_shown_once' => 'Shown Once',
+ 'env.is_required' => 'Required',
];
public function refresh()
@@ -56,7 +58,7 @@ class Show extends Component
public function mount()
{
- if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
+ if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) {
$this->isSharedVariable = true;
}
$this->modalId = new Cuid2;
@@ -78,7 +80,7 @@ class Show extends Component
public function serialize()
{
data_forget($this->env, 'real_value');
- if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
+ if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) {
data_forget($this->env, 'is_build_time');
}
}
@@ -109,15 +111,21 @@ class Show extends Component
} else {
$this->validate();
}
- // if (str($this->env->value)->startsWith('{{') && str($this->env->value)->endsWith('}}')) {
- // $type = str($this->env->value)->after('{{')->before('.')->value;
- // if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
- // $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
- // return;
- // }
- // }
+ if (! $this->isSharedVariable && $this->env->is_required && str($this->env->real_value)->isEmpty()) {
+ $oldValue = $this->env->getOriginal('value');
+ $this->env->value = $oldValue;
+ $this->dispatch('error', 'Required environment variable cannot be empty.');
+
+ return;
+ }
+
$this->serialize();
+
+ if ($this->isSharedVariable) {
+ unset($this->env->is_required);
+ }
+
$this->env->save();
$this->dispatch('success', 'Environment variable updated.');
$this->dispatch('envsUpdated');
diff --git a/app/Livewire/Project/Shared/ExecuteContainerCommand.php b/app/Livewire/Project/Shared/ExecuteContainerCommand.php
index d95443621..621ab1bac 100644
--- a/app/Livewire/Project/Shared/ExecuteContainerCommand.php
+++ b/app/Livewire/Project/Shared/ExecuteContainerCommand.php
@@ -11,6 +11,8 @@ use Livewire\Component;
class ExecuteContainerCommand extends Component
{
+ public $selected_container = 'default';
+
public $container;
public Collection $containers;
@@ -50,6 +52,7 @@ class ExecuteContainerCommand extends Component
$this->servers = $this->servers->push($server);
}
}
+ $this->loadContainers();
} elseif (data_get($this->parameters, 'database_uuid')) {
$this->type = 'database';
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id'));
@@ -60,12 +63,18 @@ class ExecuteContainerCommand extends Component
if ($this->resource->destination->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->destination->server);
}
+ $this->loadContainers();
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
if ($this->resource->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->server);
}
+ $this->loadContainers();
+ } elseif (data_get($this->parameters, 'server_uuid')) {
+ $this->type = 'server';
+ $this->resource = Server::where('uuid', $this->parameters['server_uuid'])->firstOrFail();
+ $this->server = $this->resource;
}
}
@@ -83,11 +92,14 @@ class ExecuteContainerCommand extends Component
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
}
foreach ($containers as $container) {
- $payload = [
- 'server' => $server,
- 'container' => $container,
- ];
- $this->containers = $this->containers->push($payload);
+ // if container state is running
+ if (data_get($container, 'State') === 'running') {
+ $payload = [
+ 'server' => $server,
+ 'container' => $container,
+ ];
+ $this->containers = $this->containers->push($payload);
+ }
}
} elseif (data_get($this->parameters, 'database_uuid')) {
if ($this->resource->isRunning()) {
@@ -100,7 +112,6 @@ class ExecuteContainerCommand extends Component
}
} 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,
@@ -121,19 +132,44 @@ class ExecuteContainerCommand extends Component
}
});
}
-
}
if ($this->containers->count() > 0) {
$this->container = $this->containers->first();
}
+ if ($this->containers->count() === 1) {
+ $this->selected_container = data_get($this->containers->first(), 'container.Names');
+ }
+ }
+
+ #[On('connectToServer')]
+ public function connectToServer()
+ {
+ try {
+ if ($this->server->isForceDisabled()) {
+ throw new \RuntimeException('Server is disabled.');
+ }
+ $this->dispatch(
+ 'send-terminal-command',
+ false,
+ data_get($this->server, 'name'),
+ data_get($this->server, 'uuid')
+ );
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
}
#[On('connectToContainer')]
public function connectToContainer()
{
+ if ($this->selected_container === 'default') {
+ $this->dispatch('error', 'Please select a container.');
+
+ return;
+ }
try {
- $container_name = data_get($this->container, 'container.Names');
- if (is_null($container_name)) {
+ $container = collect($this->containers)->firstWhere('container.Names', $this->selected_container);
+ if (is_null($container)) {
throw new \RuntimeException('Container not found.');
}
$server = data_get($this->container, 'server');
@@ -141,13 +177,12 @@ class ExecuteContainerCommand extends Component
if ($server->isForceDisabled()) {
throw new \RuntimeException('Server is disabled.');
}
-
- $this->dispatch('send-terminal-command',
- true,
- $container_name,
- $server->uuid,
+ $this->dispatch(
+ 'send-terminal-command',
+ isset($container),
+ data_get($container, 'container.Names'),
+ data_get($container, 'server.uuid')
);
-
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Project/Shared/GetLogs.php b/app/Livewire/Project/Shared/GetLogs.php
index deccc875c..43fd97c34 100644
--- a/app/Livewire/Project/Shared/GetLogs.php
+++ b/app/Livewire/Project/Shared/GetLogs.php
@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Shared;
+use App\Helpers\SshMultiplexingHelper;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
@@ -38,12 +39,12 @@ class GetLogs extends Component
public ?bool $showTimeStamps = true;
- public int $numberOfLines = 100;
+ public ?int $numberOfLines = 100;
public function mount()
{
if (! is_null($this->resource)) {
- if ($this->resource->getMorphClass() === 'App\Models\Application') {
+ if ($this->resource->getMorphClass() === \App\Models\Application::class) {
$this->showTimeStamps = $this->resource->settings->is_include_timestamps;
} else {
if ($this->servicesubtype) {
@@ -52,7 +53,7 @@ class GetLogs extends Component
$this->showTimeStamps = $this->resource->is_include_timestamps;
}
}
- if ($this->resource?->getMorphClass() === 'App\Models\Application') {
+ if ($this->resource?->getMorphClass() === \App\Models\Application::class) {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = 'Pull Request: '.str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
}
@@ -68,11 +69,11 @@ class GetLogs extends Component
public function instantSave()
{
if (! is_null($this->resource)) {
- if ($this->resource->getMorphClass() === 'App\Models\Application') {
+ if ($this->resource->getMorphClass() === \App\Models\Application::class) {
$this->resource->settings->is_include_timestamps = $this->showTimeStamps;
$this->resource->settings->save();
}
- if ($this->resource->getMorphClass() === 'App\Models\Service') {
+ if ($this->resource->getMorphClass() === \App\Models\Service::class) {
$serviceName = str($this->container)->beforeLast('-')->value();
$subType = $this->resource->applications()->where('name', $serviceName)->first();
if ($subType) {
@@ -94,10 +95,10 @@ class GetLogs extends Component
if (! $this->server->isFunctional()) {
return;
}
- if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) {
+ if (! $refresh && ($this->resource?->getMorphClass() === \App\Models\Service::class || str($this->container)->contains('-pr-'))) {
return;
}
- if ($this->numberOfLines <= 0) {
+ if ($this->numberOfLines <= 0 || is_null($this->numberOfLines)) {
$this->numberOfLines = 1000;
}
if ($this->container) {
@@ -108,14 +109,14 @@ class GetLogs extends Component
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
- $sshCommand = generateSshCommand($this->server, $command);
+ $sshCommand = SshMultiplexingHelper::generateSshCommand($this->server, $command);
} else {
$command = "docker logs -n {$this->numberOfLines} -t {$this->container}";
if ($this->server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
- $sshCommand = generateSshCommand($this->server, $command);
+ $sshCommand = SshMultiplexingHelper::generateSshCommand($this->server, $command);
}
} else {
if ($this->server->isSwarm()) {
@@ -124,14 +125,14 @@ class GetLogs extends Component
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
- $sshCommand = generateSshCommand($this->server, $command);
+ $sshCommand = SshMultiplexingHelper::generateSshCommand($this->server, $command);
} else {
$command = "docker logs -n {$this->numberOfLines} {$this->container}";
if ($this->server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
- $sshCommand = generateSshCommand($this->server, $command);
+ $sshCommand = SshMultiplexingHelper::generateSshCommand($this->server, $command);
}
}
if ($refresh) {
diff --git a/app/Livewire/Project/Shared/Logs.php b/app/Livewire/Project/Shared/Logs.php
index 5af0a6a50..12022b1ee 100644
--- a/app/Livewire/Project/Shared/Logs.php
+++ b/app/Livewire/Project/Shared/Logs.php
@@ -109,10 +109,7 @@ class Logs extends Component
$this->containers = $this->containers->filter(function ($container) {
return str_contains($container, $this->query['pull_request_id']);
});
- ray($this->containers);
-
}
-
} catch (\Exception $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Project/Shared/Metrics.php b/app/Livewire/Project/Shared/Metrics.php
index d9d7dd3ef..fdc35fc0f 100644
--- a/app/Livewire/Project/Shared/Metrics.php
+++ b/app/Livewire/Project/Shared/Metrics.php
@@ -31,13 +31,8 @@ class Metrics extends Component
public function loadData()
{
try {
- $metrics = $this->resource->getMetrics($this->interval);
- $cpuMetrics = collect($metrics)->map(function ($metric) {
- return [$metric[0], $metric[1]];
- });
- $memoryMetrics = collect($metrics)->map(function ($metric) {
- return [$metric[0], $metric[2]];
- });
+ $cpuMetrics = $this->resource->getCpuMetrics($this->interval);
+ $memoryMetrics = $this->resource->getMemoryMetrics($this->interval);
$this->dispatch("refreshChartData-{$this->chartId}-cpu", [
'seriesData' => $cpuMetrics,
]);
diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php
index ec09eb80f..e67df6aa9 100644
--- a/app/Livewire/Project/Shared/ResourceOperations.php
+++ b/app/Livewire/Project/Shared/ResourceOperations.php
@@ -41,7 +41,7 @@ class ResourceOperations extends Component
}
$uuid = (string) new Cuid2;
$server = $new_destination->server;
- if ($this->resource->getMorphClass() === 'App\Models\Application') {
+ if ($this->resource->getMorphClass() === \App\Models\Application::class) {
$new_resource = $this->resource->replicate()->fill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
@@ -78,14 +78,14 @@ class ResourceOperations extends Component
return redirect()->to($route);
} elseif (
- $this->resource->getMorphClass() === 'App\Models\StandalonePostgresql' ||
- $this->resource->getMorphClass() === 'App\Models\StandaloneMongodb' ||
- $this->resource->getMorphClass() === 'App\Models\StandaloneMysql' ||
- $this->resource->getMorphClass() === 'App\Models\StandaloneMariadb' ||
- $this->resource->getMorphClass() === 'App\Models\StandaloneRedis' ||
- $this->resource->getMorphClass() === 'App\Models\StandaloneKeydb' ||
- $this->resource->getMorphClass() === 'App\Models\StandaloneDragonfly' ||
- $this->resource->getMorphClass() === 'App\Models\StandaloneClickhouse'
+ $this->resource->getMorphClass() === \App\Models\StandalonePostgresql::class ||
+ $this->resource->getMorphClass() === \App\Models\StandaloneMongodb::class ||
+ $this->resource->getMorphClass() === \App\Models\StandaloneMysql::class ||
+ $this->resource->getMorphClass() === \App\Models\StandaloneMariadb::class ||
+ $this->resource->getMorphClass() === \App\Models\StandaloneRedis::class ||
+ $this->resource->getMorphClass() === \App\Models\StandaloneKeydb::class ||
+ $this->resource->getMorphClass() === \App\Models\StandaloneDragonfly::class ||
+ $this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class
) {
$uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate()->fill([
@@ -147,7 +147,6 @@ class ResourceOperations extends Component
return redirect()->to($route);
}
-
}
public function moveTo($environment_id)
diff --git a/app/Livewire/Project/Shared/ScheduledTask/Add.php b/app/Livewire/Project/Shared/ScheduledTask/Add.php
index f36b7b141..adfd59217 100644
--- a/app/Livewire/Project/Shared/ScheduledTask/Add.php
+++ b/app/Livewire/Project/Shared/ScheduledTask/Add.php
@@ -55,8 +55,8 @@ class Add extends Component
return;
}
- if (empty($this->container) || $this->container == 'null') {
- if ($this->type == 'service') {
+ if (empty($this->container) || $this->container === 'null') {
+ if ($this->type === 'service') {
$this->container = $this->subServiceName;
}
}
diff --git a/app/Livewire/Project/Shared/ScheduledTask/All.php b/app/Livewire/Project/Shared/ScheduledTask/All.php
index b383e294a..6ab8426f3 100644
--- a/app/Livewire/Project/Shared/ScheduledTask/All.php
+++ b/app/Livewire/Project/Shared/ScheduledTask/All.php
@@ -21,10 +21,10 @@ class All extends Component
public function mount()
{
$this->parameters = get_route_parameters();
- if ($this->resource->type() == 'service') {
+ if ($this->resource->type() === 'service') {
$this->containerNames = $this->resource->applications()->pluck('name');
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
- } elseif ($this->resource->type() == 'application') {
+ } elseif ($this->resource->type() === 'application') {
if ($this->resource->build_pack === 'dockercompose') {
$parsed = $this->resource->parse();
$containers = collect(data_get($parsed, 'services'))->keys();
diff --git a/app/Livewire/Project/Shared/ScheduledTask/Executions.php b/app/Livewire/Project/Shared/ScheduledTask/Executions.php
index 5bd6b4b9b..0710e37ff 100644
--- a/app/Livewire/Project/Shared/ScheduledTask/Executions.php
+++ b/app/Livewire/Project/Shared/ScheduledTask/Executions.php
@@ -2,21 +2,60 @@
namespace App\Livewire\Project\Shared\ScheduledTask;
+use App\Models\ScheduledTask;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Auth;
+use Livewire\Attributes\Locked;
use Livewire\Component;
class Executions extends Component
{
- public $executions = [];
- public $selectedKey;
- public $task;
+ public ScheduledTask $task;
+
+ #[Locked]
+ public int $taskId;
+
+ #[Locked]
+ public Collection $executions;
+
+ #[Locked]
+ public ?int $selectedKey = null;
+
+ #[Locked]
+ public ?string $serverTimezone = null;
public function getListeners()
{
+ $teamId = Auth::user()->currentTeam()->id;
+
return [
- 'selectTask',
+ "echo-private:team.{$teamId},ScheduledTaskDone" => 'refreshExecutions',
];
}
+ public function mount($taskId)
+ {
+ try {
+ $this->taskId = $taskId;
+ $this->task = ScheduledTask::findOrFail($taskId);
+ $this->executions = $this->task->executions()->take(20)->get();
+ $this->serverTimezone = data_get($this->task, 'application.destination.server.settings.server_timezone');
+ if (! $this->serverTimezone) {
+ $this->serverTimezone = data_get($this->task, 'service.destination.server.settings.server_timezone');
+ }
+ if (! $this->serverTimezone) {
+ $this->serverTimezone = 'UTC';
+ }
+ } catch (\Exception $e) {
+ return handleError($e);
+ }
+ }
+
+ public function refreshExecutions(): void
+ {
+ $this->executions = $this->task->executions()->take(20)->get();
+ }
+
public function selectTask($key): void
{
if ($key == $this->selectedKey) {
@@ -27,43 +66,16 @@ class Executions extends Component
$this->selectedKey = $key;
}
- public function server()
- {
- if (!$this->task) {
- return null;
- }
-
- if ($this->task->application) {
- if ($this->task->application->destination && $this->task->application->destination->server) {
- return $this->task->application->destination->server;
- }
- } elseif ($this->task->service) {
- if ($this->task->service->destination && $this->task->service->destination->server) {
- return $this->task->service->destination->server;
- }
- }
- return null;
- }
-
- public function getServerTimezone()
- {
- $server = $this->server();
- if (!$server) {
- return 'UTC';
- }
- $serverTimezone = $server->settings->server_timezone;
- return $serverTimezone;
- }
-
public function formatDateInServerTimezone($date)
{
- $serverTimezone = $this->getServerTimezone();
+ $serverTimezone = $this->serverTimezone;
$dateObj = new \DateTime($date);
try {
$dateObj->setTimezone(new \DateTimeZone($serverTimezone));
- } catch (\Exception $e) {
+ } catch (\Exception) {
$dateObj->setTimezone(new \DateTimeZone('UTC'));
}
+
return $dateObj->format('Y-m-d H:i:s T');
}
}
diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php
index 8be4ff643..0900a1d70 100644
--- a/app/Livewire/Project/Shared/ScheduledTask/Show.php
+++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php
@@ -2,71 +2,124 @@
namespace App\Livewire\Project\Shared\ScheduledTask;
+use App\Jobs\ScheduledTaskJob;
use App\Models\Application;
-use App\Models\ScheduledTask as ModelsScheduledTask;
+use App\Models\ScheduledTask;
use App\Models\Service;
+use Livewire\Attributes\Locked;
+use Livewire\Attributes\Validate;
use Livewire\Component;
-use Visus\Cuid2\Cuid2;
class Show extends Component
{
- public $parameters;
-
public Application|Service $resource;
- public ModelsScheduledTask $task;
+ public ScheduledTask $task;
- public ?string $modalId = null;
+ #[Locked]
+ public array $parameters;
+ #[Locked]
public string $type;
- protected $rules = [
- 'task.enabled' => 'required|boolean',
- 'task.name' => 'required|string',
- 'task.command' => 'required|string',
- 'task.frequency' => 'required|string',
- 'task.container' => 'nullable|string',
- ];
+ #[Validate(['boolean'])]
+ public bool $isEnabled = false;
- protected $validationAttributes = [
- 'name' => 'name',
- 'command' => 'command',
- 'frequency' => 'frequency',
- 'container' => 'container',
- ];
+ #[Validate(['string', 'required'])]
+ public string $name;
- public function mount()
+ #[Validate(['string', 'required'])]
+ public string $command;
+
+ #[Validate(['string', 'required'])]
+ public string $frequency;
+
+ #[Validate(['string', 'nullable'])]
+ public ?string $container = null;
+
+ #[Locked]
+ public ?string $application_uuid;
+
+ #[Locked]
+ public ?string $service_uuid;
+
+ #[Locked]
+ public string $task_uuid;
+
+ public function mount(string $task_uuid, string $project_uuid, string $environment_name, ?string $application_uuid = null, ?string $service_uuid = null)
{
- $this->parameters = get_route_parameters();
+ try {
+ $this->task_uuid = $task_uuid;
+ if ($application_uuid) {
+ $this->type = 'application';
+ $this->application_uuid = $application_uuid;
+ $this->resource = Application::ownedByCurrentTeam()->where('uuid', $application_uuid)->firstOrFail();
+ } elseif ($service_uuid) {
+ $this->type = 'service';
+ $this->service_uuid = $service_uuid;
+ $this->resource = Service::ownedByCurrentTeam()->where('uuid', $service_uuid)->firstOrFail();
+ }
+ $this->parameters = [
+ 'environment_name' => $environment_name,
+ 'project_uuid' => $project_uuid,
+ 'application_uuid' => $application_uuid,
+ 'service_uuid' => $service_uuid,
+ ];
- if (data_get($this->parameters, 'application_uuid')) {
- $this->type = 'application';
- $this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
- } elseif (data_get($this->parameters, 'service_uuid')) {
- $this->type = 'service';
- $this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
+ $this->task = $this->resource->scheduled_tasks()->where('uuid', $task_uuid)->firstOrFail();
+ $this->syncData();
+ } catch (\Exception $e) {
+ return handleError($e);
}
+ }
- $this->modalId = new Cuid2;
- $this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
+ public function syncData(bool $toModel = false)
+ {
+ if ($toModel) {
+ $this->validate();
+ $this->task->enabled = $this->isEnabled;
+ $this->task->name = str($this->name)->trim()->value();
+ $this->task->command = str($this->command)->trim()->value();
+ $this->task->frequency = str($this->frequency)->trim()->value();
+ $this->task->container = str($this->container)->trim()->value();
+ $this->task->save();
+ } else {
+ $this->isEnabled = $this->task->enabled;
+ $this->name = $this->task->name;
+ $this->command = $this->task->command;
+ $this->frequency = $this->task->frequency;
+ $this->container = $this->task->container;
+ }
}
public function instantSave()
{
- $this->validateOnly('task.enabled');
- $this->task->save(['enabled' => $this->task->enabled]);
- $this->dispatch('success', 'Scheduled task updated.');
- $this->dispatch('refreshTasks');
+ try {
+ $this->syncData(true);
+ $this->dispatch('success', 'Scheduled task updated.');
+ $this->refreshTasks();
+ } catch (\Exception $e) {
+ return handleError($e);
+ }
}
public function submit()
{
- $this->validate();
- $this->task->name = str($this->task->name)->trim()->value();
- $this->task->container = str($this->task->container)->trim()->value();
- $this->task->save();
- $this->dispatch('success', 'Scheduled task updated.');
- $this->dispatch('refreshTasks');
+ try {
+ $this->syncData(true);
+ $this->dispatch('success', 'Scheduled task updated.');
+ } catch (\Exception $e) {
+ return handleError($e);
+ }
+ }
+
+ public function refreshTasks()
+ {
+ try {
+ $this->task->refresh();
+ } catch (\Exception $e) {
+ return handleError($e);
+ }
}
public function delete()
@@ -74,13 +127,23 @@ class Show extends Component
try {
$this->task->delete();
- if ($this->type == 'application') {
- return redirect()->route('project.application.configuration', $this->parameters);
+ if ($this->type === 'application') {
+ return redirect()->route('project.application.configuration', $this->parameters, $this->task->name);
} else {
- return redirect()->route('project.service.configuration', $this->parameters);
+ return redirect()->route('project.service.configuration', $this->parameters, $this->task->name);
}
} catch (\Exception $e) {
return handleError($e);
}
}
+
+ public function executeNow()
+ {
+ try {
+ ScheduledTaskJob::dispatch($this->task);
+ $this->dispatch('success', 'Scheduled task executed.');
+ } catch (\Exception $e) {
+ return handleError($e);
+ }
+ }
}
diff --git a/app/Livewire/Project/Shared/Storages/Add.php b/app/Livewire/Project/Shared/Storages/Add.php
index 27e0c6e44..6e250bd90 100644
--- a/app/Livewire/Project/Shared/Storages/Add.php
+++ b/app/Livewire/Project/Shared/Storages/Add.php
@@ -83,7 +83,7 @@ class Add extends Component
]);
$this->file_storage_path = trim($this->file_storage_path);
$this->file_storage_path = str($this->file_storage_path)->start('/')->value();
- if ($this->resource->getMorphClass() === 'App\Models\Application') {
+ if ($this->resource->getMorphClass() === \App\Models\Application::class) {
$fs_path = application_configuration_dir().'/'.$this->resource->uuid.$this->file_storage_path;
}
LocalFileVolume::create(
@@ -100,7 +100,6 @@ class Add extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
}
-
}
public function submitFileStorageDirectory()
@@ -127,7 +126,6 @@ class Add extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
}
-
}
public function submitPersistentVolume()
@@ -144,7 +142,6 @@ class Add extends Component
'mount_path' => $this->mount_path,
'host_path' => $this->host_path,
]);
-
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Project/Shared/Storages/Show.php b/app/Livewire/Project/Shared/Storages/Show.php
index 08f51ce08..54b1be3af 100644
--- a/app/Livewire/Project/Shared/Storages/Show.php
+++ b/app/Livewire/Project/Shared/Storages/Show.php
@@ -2,7 +2,10 @@
namespace App\Livewire\Project\Shared\Storages;
+use App\Models\InstanceSettings;
use App\Models\LocalPersistentVolume;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class Show extends Component
@@ -36,8 +39,16 @@ class Show extends Component
$this->dispatch('success', 'Storage updated successfully');
}
- public function delete()
+ public function delete($password)
{
+ if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
+ if (! Hash::check($password, Auth::user()->password)) {
+ $this->addError('password', 'The provided password is incorrect.');
+
+ return;
+ }
+ }
+
$this->storage->delete();
$this->dispatch('refreshStorages');
}
diff --git a/app/Livewire/Project/Shared/Tags.php b/app/Livewire/Project/Shared/Tags.php
index dca6180ff..811859cb8 100644
--- a/app/Livewire/Project/Shared/Tags.php
+++ b/app/Livewire/Project/Shared/Tags.php
@@ -37,6 +37,7 @@ class Tags extends Component
$this->validate();
$tags = str($this->newTags)->trim()->explode(' ');
foreach ($tags as $tag) {
+ $tag = strip_tags($tag);
if (strlen($tag) < 2) {
$this->dispatch('error', 'Invalid tag.', "Tag $tag is invalid. Min length is 2.");
@@ -65,6 +66,7 @@ class Tags extends Component
public function addTag(string $id, string $name)
{
try {
+ $name = strip_tags($name);
if ($this->resource->tags()->where('id', $id)->exists()) {
$this->dispatch('error', 'Duplicate tags.', "Tag $name already added.");
diff --git a/app/Livewire/Project/Shared/Terminal.php b/app/Livewire/Project/Shared/Terminal.php
index 802e65a30..5af8f057e 100644
--- a/app/Livewire/Project/Shared/Terminal.php
+++ b/app/Livewire/Project/Shared/Terminal.php
@@ -2,16 +2,30 @@
namespace App\Livewire\Project\Shared;
+use App\Helpers\SshMultiplexingHelper;
use App\Models\Server;
use Livewire\Attributes\On;
use Livewire\Component;
class Terminal extends Component
{
+ public function getListeners()
+ {
+ $teamId = auth()->user()->currentTeam()->id;
+
+ return [
+ "echo-private:team.{$teamId},ApplicationStatusChanged" => 'closeTerminal',
+ ];
+ }
+
+ public function closeTerminal()
+ {
+ $this->dispatch('reloadWindow');
+ }
+
#[On('send-terminal-command')]
public function sendTerminalCommand($isContainer, $identifier, $serverUuid)
{
-
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
if ($isContainer) {
@@ -19,9 +33,9 @@ class Terminal extends Component
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'");
+ $command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && 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'");
+ $command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && 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
diff --git a/app/Livewire/Project/Shared/UploadConfig.php b/app/Livewire/Project/Shared/UploadConfig.php
new file mode 100644
index 000000000..1b10f588b
--- /dev/null
+++ b/app/Livewire/Project/Shared/UploadConfig.php
@@ -0,0 +1,46 @@
+config = '{
+ "build_pack": "nixpacks",
+ "base_directory": "/nodejs",
+ "publish_directory": "/",
+ "ports_exposes": "3000",
+ "settings": {
+ "is_static": false
+ }
+}';
+ }
+ }
+
+ public function uploadConfig()
+ {
+ try {
+ $application = Application::findOrFail($this->applicationId);
+ $application->setConfig($this->config);
+ $this->dispatch('success', 'Application settings updated');
+ } catch (\Exception $e) {
+ $this->dispatch('error', $e->getMessage());
+
+ return;
+ }
+ }
+
+ public function render()
+ {
+ return view('livewire.project.shared.upload-config');
+ }
+}
diff --git a/app/Livewire/Project/Show.php b/app/Livewire/Project/Show.php
index 1082f078c..2335519c7 100644
--- a/app/Livewire/Project/Show.php
+++ b/app/Livewire/Project/Show.php
@@ -2,27 +2,46 @@
namespace App\Livewire\Project;
+use App\Models\Environment;
use App\Models\Project;
+use Livewire\Attributes\Validate;
use Livewire\Component;
class Show extends Component
{
public Project $project;
- public $environments;
+ #[Validate(['required', 'string', 'min:3'])]
+ public string $name;
- public function mount()
+ #[Validate(['nullable', 'string'])]
+ public ?string $description = null;
+
+ public function mount(string $project_uuid)
{
- $projectUuid = request()->route('project_uuid');
- $teamId = currentTeam()->id;
-
- $project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
- if (! $project) {
- return redirect()->route('dashboard');
+ try {
+ $this->project = Project::where('team_id', currentTeam()->id)->where('uuid', $project_uuid)->firstOrFail();
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
}
+ }
- $this->environments = $project->environments->sortBy('created_at');
- $this->project = $project;
+ public function submit()
+ {
+ try {
+ $this->validate();
+ $environment = Environment::create([
+ 'name' => $this->name,
+ 'project_id' => $this->project->id,
+ ]);
+
+ return redirect()->route('project.resource.index', [
+ 'project_uuid' => $this->project->uuid,
+ 'environment_name' => $environment->name,
+ ]);
+ } catch (\Throwable $e) {
+ handleError($e, $this);
+ }
}
public function render()
diff --git a/app/Livewire/Security/ApiTokens.php b/app/Livewire/Security/ApiTokens.php
index ff8679d21..fe68a8ba5 100644
--- a/app/Livewire/Security/ApiTokens.php
+++ b/app/Livewire/Security/ApiTokens.php
@@ -2,6 +2,7 @@
namespace App\Livewire\Security;
+use App\Models\InstanceSettings;
use Livewire\Component;
class ApiTokens extends Component
@@ -14,8 +15,12 @@ class ApiTokens extends Component
public bool $readOnly = true;
+ public bool $rootAccess = false;
+
public array $permissions = ['read-only'];
+ public $isApiEnabled;
+
public function render()
{
return view('livewire.security.api-tokens');
@@ -23,6 +28,7 @@ class ApiTokens extends Component
public function mount()
{
+ $this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
$this->tokens = auth()->user()->tokens->sortByDesc('created_at');
}
@@ -31,12 +37,11 @@ class ApiTokens extends Component
if ($this->viewSensitiveData) {
$this->permissions[] = 'view:sensitive';
$this->permissions = array_diff($this->permissions, ['*']);
+ $this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
}
- if (count($this->permissions) == 0) {
- $this->permissions = ['*'];
- }
+ $this->makeSureOneIsSelected();
}
public function updatedReadOnly()
@@ -44,11 +49,30 @@ class ApiTokens extends Component
if ($this->readOnly) {
$this->permissions[] = 'read-only';
$this->permissions = array_diff($this->permissions, ['*']);
+ $this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['read-only']);
}
- if (count($this->permissions) == 0) {
+ $this->makeSureOneIsSelected();
+ }
+
+ public function updatedRootAccess()
+ {
+ if ($this->rootAccess) {
$this->permissions = ['*'];
+ $this->readOnly = false;
+ $this->viewSensitiveData = false;
+ } else {
+ $this->readOnly = true;
+ $this->permissions = ['read-only'];
+ }
+ }
+
+ public function makeSureOneIsSelected()
+ {
+ if (count($this->permissions) == 0) {
+ $this->permissions = ['read-only'];
+ $this->readOnly = true;
}
}
@@ -58,12 +82,6 @@ class ApiTokens extends Component
$this->validate([
'description' => 'required|min:3|max:255',
]);
- // if ($this->viewSensitiveData) {
- // $this->permissions[] = 'view:sensitive';
- // }
- // if ($this->readOnly) {
- // $this->permissions[] = 'read-only';
- // }
$token = auth()->user()->createToken($this->description, $this->permissions);
$this->tokens = auth()->user()->tokens;
session()->flash('token', $token->plainTextToken);
diff --git a/app/Livewire/Security/PrivateKey/Create.php b/app/Livewire/Security/PrivateKey/Create.php
index 32a67bbea..319cec192 100644
--- a/app/Livewire/Security/PrivateKey/Create.php
+++ b/app/Livewire/Security/PrivateKey/Create.php
@@ -3,17 +3,13 @@
namespace App\Livewire\Security\PrivateKey;
use App\Models\PrivateKey;
-use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Livewire\Component;
-use phpseclib3\Crypt\PublicKeyLoader;
class Create extends Component
{
- use WithRateLimiting;
+ public string $name = '';
- public string $name;
-
- public string $value;
+ public string $value = '';
public ?string $from = null;
@@ -26,72 +22,69 @@ class Create extends Component
'value' => 'required|string',
];
- protected $validationAttributes = [
- 'name' => 'name',
- 'value' => 'private Key',
- ];
-
public function generateNewRSAKey()
{
- try {
- $this->rateLimit(10);
- $this->name = generate_random_name();
- $this->description = 'Created by Coolify';
- ['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey();
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
+ $this->generateNewKey('rsa');
}
public function generateNewEDKey()
{
- try {
- $this->rateLimit(10);
- $this->name = generate_random_name();
- $this->description = 'Created by Coolify';
- ['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey('ed25519');
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
+ $this->generateNewKey('ed25519');
}
- public function updated($updateProperty)
+ private function generateNewKey($type)
{
- if ($updateProperty === 'value') {
- try {
- $this->publicKey = PublicKeyLoader::load($this->$updateProperty)->getPublicKey()->toString('OpenSSH', ['comment' => '']);
- } catch (\Throwable $e) {
- if ($this->$updateProperty === '') {
- $this->publicKey = '';
- } else {
- $this->publicKey = 'Invalid private key';
- }
- }
+ $keyData = PrivateKey::generateNewKeyPair($type);
+ $this->setKeyData($keyData);
+ }
+
+ public function updated($property)
+ {
+ if ($property === 'value') {
+ $this->validatePrivateKey();
}
- $this->validateOnly($updateProperty);
}
public function createPrivateKey()
{
$this->validate();
+
try {
- $this->value = trim($this->value);
- if (! str_ends_with($this->value, "\n")) {
- $this->value .= "\n";
- }
- $private_key = PrivateKey::create([
+ $privateKey = PrivateKey::createAndStore([
'name' => $this->name,
'description' => $this->description,
- 'private_key' => $this->value,
+ 'private_key' => trim($this->value)."\n",
'team_id' => currentTeam()->id,
]);
- if ($this->from === 'server') {
- return redirect()->route('dashboard');
- }
- return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
+ return $this->redirectAfterCreation($privateKey);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
+
+ private function setKeyData(array $keyData)
+ {
+ $this->name = $keyData['name'];
+ $this->description = $keyData['description'];
+ $this->value = $keyData['private_key'];
+ $this->publicKey = $keyData['public_key'];
+ }
+
+ private function validatePrivateKey()
+ {
+ $validationResult = PrivateKey::validateAndExtractPublicKey($this->value);
+ $this->publicKey = $validationResult['publicKey'];
+
+ if (! $validationResult['isValid']) {
+ $this->addError('value', 'Invalid private key');
+ }
+ }
+
+ private function redirectAfterCreation(PrivateKey $privateKey)
+ {
+ return $this->from === 'server'
+ ? redirect()->route('dashboard')
+ : redirect()->route('security.private-key.show', ['private_key_uuid' => $privateKey->uuid]);
+ }
}
diff --git a/app/Livewire/Security/PrivateKey/Index.php b/app/Livewire/Security/PrivateKey/Index.php
new file mode 100644
index 000000000..76441a67e
--- /dev/null
+++ b/app/Livewire/Security/PrivateKey/Index.php
@@ -0,0 +1,24 @@
+get();
+
+ return view('livewire.security.private-key.index', [
+ 'privateKeys' => $privateKeys,
+ ])->layout('components.layout');
+ }
+
+ public function cleanupUnusedKeys()
+ {
+ PrivateKey::cleanupUnusedKeys();
+ $this->dispatch('success', 'Unused keys have been cleaned up.');
+ }
+}
diff --git a/app/Livewire/Security/PrivateKey/Show.php b/app/Livewire/Security/PrivateKey/Show.php
index d86bd5d1e..b9195b543 100644
--- a/app/Livewire/Security/PrivateKey/Show.php
+++ b/app/Livewire/Security/PrivateKey/Show.php
@@ -28,26 +28,28 @@ class Show extends Component
{
try {
$this->private_key = PrivateKey::ownedByCurrentTeam(['name', 'description', 'private_key', 'is_git_related'])->whereUuid(request()->private_key_uuid)->firstOrFail();
- } catch (\Throwable $e) {
- return handleError($e, $this);
+ } catch (\Throwable) {
+ abort(404);
}
}
public function loadPublicKey()
{
- $this->public_key = $this->private_key->publicKey();
+ $this->public_key = $this->private_key->getPublicKey();
+ if ($this->public_key === 'Error loading private key') {
+ $this->dispatch('error', 'Failed to load public key. The private key may be invalid.');
+ }
}
public function delete()
{
try {
- if ($this->private_key->isEmpty()) {
- $this->private_key->delete();
- currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
+ $this->private_key->safeDelete();
+ currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
- return redirect()->route('security.private-key.index');
- }
- $this->dispatch('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
+ return redirect()->route('security.private-key.index');
+ } catch (\Exception $e) {
+ $this->dispatch('error', $e->getMessage());
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -56,8 +58,9 @@ class Show extends Component
public function changePrivateKey()
{
try {
- $this->private_key->private_key = formatPrivateKey($this->private_key->private_key);
- $this->private_key->save();
+ $this->private_key->updatePrivateKey([
+ 'private_key' => formatPrivateKey($this->private_key->private_key),
+ ]);
refresh_server_connection($this->private_key);
$this->dispatch('success', 'Private key updated.');
} catch (\Throwable $e) {
diff --git a/app/Livewire/Server/Advanced.php b/app/Livewire/Server/Advanced.php
new file mode 100644
index 000000000..0852abebf
--- /dev/null
+++ b/app/Livewire/Server/Advanced.php
@@ -0,0 +1,115 @@
+server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
+ $this->parameters = get_route_parameters();
+ $this->syncData();
+ } catch (\Throwable) {
+ return redirect()->route('server.show');
+ }
+ }
+
+ public function syncData(bool $toModel = false)
+ {
+ if ($toModel) {
+ $this->validate();
+ $this->server->settings->concurrent_builds = $this->concurrentBuilds;
+ $this->server->settings->dynamic_timeout = $this->dynamicTimeout;
+ $this->server->settings->force_docker_cleanup = $this->forceDockerCleanup;
+ $this->server->settings->docker_cleanup_frequency = $this->dockerCleanupFrequency;
+ $this->server->settings->docker_cleanup_threshold = $this->dockerCleanupThreshold;
+ $this->server->settings->server_disk_usage_notification_threshold = $this->serverDiskUsageNotificationThreshold;
+ $this->server->settings->delete_unused_volumes = $this->deleteUnusedVolumes;
+ $this->server->settings->delete_unused_networks = $this->deleteUnusedNetworks;
+ $this->server->settings->save();
+ } else {
+ $this->concurrentBuilds = $this->server->settings->concurrent_builds;
+ $this->dynamicTimeout = $this->server->settings->dynamic_timeout;
+ $this->forceDockerCleanup = $this->server->settings->force_docker_cleanup;
+ $this->dockerCleanupFrequency = $this->server->settings->docker_cleanup_frequency;
+ $this->dockerCleanupThreshold = $this->server->settings->docker_cleanup_threshold;
+ $this->serverDiskUsageNotificationThreshold = $this->server->settings->server_disk_usage_notification_threshold;
+ $this->deleteUnusedVolumes = $this->server->settings->delete_unused_volumes;
+ $this->deleteUnusedNetworks = $this->server->settings->delete_unused_networks;
+ }
+ }
+
+ public function instantSave()
+ {
+ try {
+ $this->syncData(true);
+ $this->dispatch('success', 'Server updated.');
+ // $this->dispatch('refreshServerShow');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function manualCleanup()
+ {
+ try {
+ DockerCleanupJob::dispatch($this->server, true);
+ $this->dispatch('success', 'Manual cleanup job started. Depending on the amount of data, this might take a while.');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function submit()
+ {
+ try {
+ if (! validate_cron_expression($this->dockerCleanupFrequency)) {
+ $this->dockerCleanupFrequency = $this->server->settings->getOriginal('docker_cleanup_frequency');
+ throw new \Exception('Invalid Cron / Human expression for Docker Cleanup Frequency.');
+ }
+ $this->syncData(true);
+ $this->dispatch('success', 'Server updated.');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function render()
+ {
+ return view('livewire.server.advanced');
+ }
+}
diff --git a/app/Livewire/Server/Charts.php b/app/Livewire/Server/Charts.php
index 0921c7fa4..d0db87f57 100644
--- a/app/Livewire/Server/Charts.php
+++ b/app/Livewire/Server/Charts.php
@@ -19,6 +19,15 @@ class Charts extends Component
public bool $poll = true;
+ public function mount(string $server_uuid)
+ {
+ try {
+ $this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
public function pollData()
{
if ($this->poll || $this->interval <= 10) {
@@ -34,19 +43,12 @@ class Charts extends Component
try {
$cpuMetrics = $this->server->getCpuMetrics($this->interval);
$memoryMetrics = $this->server->getMemoryMetrics($this->interval);
- $cpuMetrics = collect($cpuMetrics)->map(function ($metric) {
- return [$metric[0], $metric[1]];
- });
- $memoryMetrics = collect($memoryMetrics)->map(function ($metric) {
- return [$metric[0], $metric[1]];
- });
$this->dispatch("refreshChartData-{$this->chartId}-cpu", [
'seriesData' => $cpuMetrics,
]);
$this->dispatch("refreshChartData-{$this->chartId}-memory", [
'seriesData' => $memoryMetrics,
]);
-
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Server/CloudflareTunnels.php b/app/Livewire/Server/CloudflareTunnels.php
new file mode 100644
index 000000000..f69fc8655
--- /dev/null
+++ b/app/Livewire/Server/CloudflareTunnels.php
@@ -0,0 +1,54 @@
+server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
+ if ($this->server->isLocalhost()) {
+ return redirect()->route('server.show', ['server_uuid' => $server_uuid]);
+ }
+ $this->isCloudflareTunnelsEnabled = $this->server->settings->is_cloudflare_tunnel;
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function instantSave()
+ {
+ try {
+ $this->validate();
+ $this->server->settings->is_cloudflare_tunnel = $this->isCloudflareTunnelsEnabled;
+ $this->server->settings->save();
+ $this->dispatch('success', 'Server updated.');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function manualCloudflareConfig()
+ {
+ $this->isCloudflareTunnelsEnabled = true;
+ $this->server->settings->is_cloudflare_tunnel = true;
+ $this->server->settings->save();
+ $this->server->refresh();
+ $this->dispatch('success', 'Cloudflare Tunnels enabled.');
+ }
+
+ public function render()
+ {
+ return view('livewire.server.cloudflare-tunnels');
+ }
+}
diff --git a/app/Livewire/Server/ConfigureCloudflareTunnels.php b/app/Livewire/Server/ConfigureCloudflareTunnels.php
index f7306a5b5..f58d7b6be 100644
--- a/app/Livewire/Server/ConfigureCloudflareTunnels.php
+++ b/app/Livewire/Server/ConfigureCloudflareTunnels.php
@@ -30,14 +30,18 @@ class ConfigureCloudflareTunnels extends Component
public function submit()
{
try {
+ if (str($this->ssh_domain)->contains('https://')) {
+ $this->ssh_domain = str($this->ssh_domain)->replace('https://', '')->replace('http://', '')->trim();
+ // remove / from the end
+ $this->ssh_domain = str($this->ssh_domain)->replace('/', '');
+ }
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
- ConfigureCloudflared::run($server, $this->cloudflare_token);
+ ConfigureCloudflared::dispatch($server, $this->cloudflare_token);
$server->settings->is_cloudflare_tunnel = true;
$server->ip = $this->ssh_domain;
$server->save();
$server->settings->save();
- $this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
- $this->dispatch('refreshServerShow');
+ $this->dispatch('warning', 'Cloudflare Tunnels configuration started.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Server/Delete.php b/app/Livewire/Server/Delete.php
index 3beec0c91..b9e3944b5 100644
--- a/app/Livewire/Server/Delete.php
+++ b/app/Livewire/Server/Delete.php
@@ -2,17 +2,38 @@
namespace App\Livewire\Server;
+use App\Actions\Server\DeleteServer;
+use App\Models\InstanceSettings;
+use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class Delete extends Component
{
use AuthorizesRequests;
- public $server;
+ public Server $server;
- public function delete()
+ public function mount(string $server_uuid)
{
+ try {
+ $this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function delete($password)
+ {
+ if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
+ if (! Hash::check($password, Auth::user()->password)) {
+ $this->addError('password', 'The provided password is incorrect.');
+
+ return;
+ }
+ }
try {
$this->authorize('delete', $this->server);
if ($this->server->hasDefinedResources()) {
@@ -21,6 +42,7 @@ class Delete extends Component
return;
}
$this->server->delete();
+ DeleteServer::dispatch($this->server);
return redirect()->route('server.index');
} catch (\Throwable $e) {
diff --git a/app/Livewire/Server/Destination/Show.php b/app/Livewire/Server/Destination/Show.php
deleted file mode 100644
index 986e16cbf..000000000
--- a/app/Livewire/Server/Destination/Show.php
+++ /dev/null
@@ -1,31 +0,0 @@
-parameters = get_route_parameters();
- try {
- $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
- if (is_null($this->server)) {
- return redirect()->route('server.index');
- }
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
-
- public function render()
- {
- return view('livewire.server.destination.show');
- }
-}
diff --git a/app/Livewire/Server/Destinations.php b/app/Livewire/Server/Destinations.php
new file mode 100644
index 000000000..dbab6e03f
--- /dev/null
+++ b/app/Livewire/Server/Destinations.php
@@ -0,0 +1,90 @@
+networks = collect();
+ $this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ private function createNetworkAndAttachToProxy()
+ {
+ $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
+ instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
+ }
+
+ public function add($name)
+ {
+ if ($this->server->isSwarm()) {
+ $found = $this->server->swarmDockers()->where('network', $name)->first();
+ if ($found) {
+ $this->dispatch('error', 'Network already added to this server.');
+
+ return;
+ } else {
+ SwarmDocker::create([
+ 'name' => $this->server->name.'-'.$name,
+ 'network' => $this->name,
+ 'server_id' => $this->server->id,
+ ]);
+ }
+ } else {
+ $found = $this->server->standaloneDockers()->where('network', $name)->first();
+ if ($found) {
+ $this->dispatch('error', 'Network already added to this server.');
+
+ return;
+ } else {
+ StandaloneDocker::create([
+ 'name' => $this->server->name.'-'.$name,
+ 'network' => $name,
+ 'server_id' => $this->server->id,
+ ]);
+ }
+ $this->createNetworkAndAttachToProxy();
+ }
+ }
+
+ public function scan()
+ {
+ if ($this->server->isSwarm()) {
+ $alreadyAddedNetworks = $this->server->swarmDockers;
+ } else {
+ $alreadyAddedNetworks = $this->server->standaloneDockers;
+ }
+ $networks = instant_remote_process(['docker network ls --format "{{json .}}"'], $this->server, false);
+ $this->networks = format_docker_command_output_to_json($networks)->filter(function ($network) {
+ return $network['Name'] !== 'bridge' && $network['Name'] !== 'host' && $network['Name'] !== 'none';
+ })->filter(function ($network) use ($alreadyAddedNetworks) {
+ return ! $alreadyAddedNetworks->contains('network', $network['Name']);
+ });
+ if ($this->networks->count() === 0) {
+ $this->dispatch('success', 'No new destinations found on this server.');
+
+ return;
+ }
+ $this->dispatch('success', 'Scan done.');
+ }
+
+ public function render()
+ {
+ return view('livewire.server.destinations');
+ }
+}
diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php
deleted file mode 100644
index 3b3747a81..000000000
--- a/app/Livewire/Server/Form.php
+++ /dev/null
@@ -1,241 +0,0 @@
- 'serverInstalled',
- 'revalidate' => '$refresh',
- ];
-
- protected $rules = [
- 'server.name' => 'required',
- 'server.description' => 'nullable',
- 'server.ip' => 'required',
- 'server.user' => 'required',
- 'server.port' => 'required',
- 'server.settings.is_cloudflare_tunnel' => 'required|boolean',
- 'server.settings.is_reachable' => 'required',
- 'server.settings.is_swarm_manager' => 'required|boolean',
- 'server.settings.is_swarm_worker' => 'required|boolean',
- 'server.settings.is_build_server' => 'required|boolean',
- 'server.settings.concurrent_builds' => 'required|integer|min:1',
- 'server.settings.dynamic_timeout' => 'required|integer|min:1',
- 'server.settings.is_metrics_enabled' => 'required|boolean',
- 'server.settings.metrics_token' => 'required',
- 'server.settings.metrics_refresh_rate_seconds' => 'required|integer|min:1',
- 'server.settings.metrics_history_days' => 'required|integer|min:1',
- 'wildcard_domain' => 'nullable|url',
- 'server.settings.is_server_api_enabled' => 'required|boolean',
- 'server.settings.server_timezone' => 'required|string|timezone',
- 'server.settings.force_docker_cleanup' => 'required|boolean',
- 'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string',
- 'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100',
- ];
-
- protected $validationAttributes = [
- 'server.name' => 'Name',
- 'server.description' => 'Description',
- 'server.ip' => 'IP address/Domain',
- 'server.user' => 'User',
- 'server.port' => 'Port',
- 'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
- 'server.settings.is_reachable' => 'Is reachable',
- 'server.settings.is_swarm_manager' => 'Swarm Manager',
- 'server.settings.is_swarm_worker' => 'Swarm Worker',
- 'server.settings.is_build_server' => 'Build Server',
- 'server.settings.concurrent_builds' => 'Concurrent Builds',
- 'server.settings.dynamic_timeout' => 'Dynamic Timeout',
- 'server.settings.is_metrics_enabled' => 'Metrics',
- 'server.settings.metrics_token' => 'Metrics Token',
- 'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval',
- 'server.settings.metrics_history_days' => 'Metrics History',
- 'server.settings.is_server_api_enabled' => 'Server API',
- 'server.settings.server_timezone' => 'Server Timezone',
- ];
-
- public function mount(Server $server)
- {
- $this->server = $server;
- $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
- $this->wildcard_domain = $this->server->settings->wildcard_domain;
- $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
- $this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
- }
-
- public function updated($field)
- {
- if ($field === 'server.settings.docker_cleanup_frequency') {
- $frequency = $this->server->settings->docker_cleanup_frequency;
- if (empty($frequency) || ! validate_cron_expression($frequency)) {
- $this->dispatch('error', 'Invalid Cron / Human expression for Docker Cleanup Frequency. Resetting to default 10 minutes.');
- $this->server->settings->docker_cleanup_frequency = '*/10 * * * *';
- }
- }
- }
-
- public function serverInstalled()
- {
- $this->server->refresh();
- $this->server->settings->refresh();
- }
-
- public function updatedServerSettingsIsBuildServer()
- {
- $this->dispatch('refreshServerShow');
- $this->dispatch('serverRefresh');
- $this->dispatch('proxyStatusUpdated');
- }
-
- public function checkPortForServerApi()
- {
- try {
- if ($this->server->settings->is_server_api_enabled === true) {
- $this->server->checkServerApi();
- $this->dispatch('success', 'Server API is reachable.');
- }
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
-
- public function instantSave()
- {
- try {
- refresh_server_connection($this->server->privateKey);
- $this->validateServer(false);
- $this->server->settings->save();
- $this->server->save();
- $this->dispatch('success', 'Server updated.');
- $this->dispatch('refreshServerShow');
- if ($this->server->isSentinelEnabled()) {
- PullSentinelImageJob::dispatchSync($this->server);
- ray('Sentinel is enabled');
- if ($this->server->settings->isDirty('is_metrics_enabled')) {
- $this->dispatch('reloadWindow');
- }
- if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) {
- ray('Starting sentinel');
- }
- } else {
- ray('Sentinel is not enabled');
- StopSentinel::dispatch($this->server);
- }
- // $this->checkPortForServerApi();
-
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
-
- public function restartSentinel()
- {
- try {
- $version = get_latest_sentinel_version();
- StartSentinel::run($this->server, $version, true);
- $this->dispatch('success', 'Sentinel restarted.');
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
-
- public function revalidate()
- {
- $this->revalidate = true;
- }
-
- public function checkLocalhostConnection()
- {
- $this->submit();
- ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
- if ($uptime) {
- $this->dispatch('success', 'Server is reachable.');
- $this->server->settings->is_reachable = true;
- $this->server->settings->is_usable = true;
- $this->server->settings->save();
- $this->dispatch('proxyStatusUpdated');
- } else {
- $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.
Check this documentation for further help.
Error: '.$error);
-
- return;
- }
- }
-
- public function validateServer($install = true)
- {
- $this->server->update([
- 'validation_logs' => null,
- ]);
- $this->dispatch('init', $install);
- }
-
- public function submit()
- {
- try {
- if (isCloud() && ! isDev()) {
- $this->validate();
- $this->validate([
- 'server.ip' => 'required',
- ]);
- } else {
- $this->validate();
- }
- $uniqueIPs = Server::all()->reject(function (Server $server) {
- return $server->id === $this->server->id;
- })->pluck('ip')->toArray();
- if (in_array($this->server->ip, $uniqueIPs)) {
- $this->dispatch('error', 'IP address is already in use by another team.');
-
- return;
- }
- refresh_server_connection($this->server->privateKey);
- $this->server->settings->wildcard_domain = $this->wildcard_domain;
- if ($this->server->settings->force_docker_cleanup) {
- $this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
- } else {
- $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
- }
- $currentTimezone = $this->server->settings->getOriginal('server_timezone');
- $newTimezone = $this->server->settings->server_timezone;
- if ($currentTimezone !== $newTimezone || $currentTimezone === '') {
- $this->server->settings->server_timezone = $newTimezone;
- $this->server->settings->save();
- }
-
- $this->server->settings->save();
- $this->server->save();
- $this->dispatch('success', 'Server updated.');
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
-
- public function updatedServerSettingsServerTimezone($value)
- {
- $this->server->settings->server_timezone = $value;
- $this->server->settings->save();
- $this->dispatch('success', 'Server timezone updated.');
- }
-}
diff --git a/app/Livewire/Server/LogDrains.php b/app/Livewire/Server/LogDrains.php
index 6e09eecdd..6599149c4 100644
--- a/app/Livewire/Server/LogDrains.php
+++ b/app/Livewire/Server/LogDrains.php
@@ -2,84 +2,132 @@
namespace App\Livewire\Server;
-use App\Actions\Server\InstallLogDrain;
+use App\Actions\Server\StartLogDrain;
use App\Actions\Server\StopLogDrain;
use App\Models\Server;
+use Livewire\Attributes\Validate;
use Livewire\Component;
class LogDrains extends Component
{
public Server $server;
- public $parameters = [];
+ #[Validate(['boolean'])]
+ public bool $isLogDrainNewRelicEnabled = false;
- protected $rules = [
- 'server.settings.is_logdrain_newrelic_enabled' => 'required|boolean',
- 'server.settings.logdrain_newrelic_license_key' => 'required|string',
- 'server.settings.logdrain_newrelic_base_uri' => 'required|string',
- 'server.settings.is_logdrain_highlight_enabled' => 'required|boolean',
- 'server.settings.logdrain_highlight_project_id' => 'required|string',
- 'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
- 'server.settings.logdrain_axiom_dataset_name' => 'required|string',
- 'server.settings.logdrain_axiom_api_key' => 'required|string',
- 'server.settings.is_logdrain_custom_enabled' => 'required|boolean',
- 'server.settings.logdrain_custom_config' => 'required|string',
- 'server.settings.logdrain_custom_config_parser' => 'nullable',
- ];
+ #[Validate(['boolean'])]
+ public bool $isLogDrainCustomEnabled = false;
- protected $validationAttributes = [
- 'server.settings.is_logdrain_newrelic_enabled' => 'New Relic log drain',
- 'server.settings.logdrain_newrelic_license_key' => 'New Relic license key',
- 'server.settings.logdrain_newrelic_base_uri' => 'New Relic base URI',
- 'server.settings.is_logdrain_highlight_enabled' => 'Highlight log drain',
- 'server.settings.logdrain_highlight_project_id' => 'Highlight project ID',
- 'server.settings.is_logdrain_axiom_enabled' => 'Axiom log drain',
- 'server.settings.logdrain_axiom_dataset_name' => 'Axiom dataset name',
- 'server.settings.logdrain_axiom_api_key' => 'Axiom API key',
- 'server.settings.is_logdrain_custom_enabled' => 'Custom log drain',
- 'server.settings.logdrain_custom_config' => 'Custom log drain configuration',
- 'server.settings.logdrain_custom_config_parser' => 'Custom log drain configuration parser',
- ];
+ #[Validate(['boolean'])]
+ public bool $isLogDrainAxiomEnabled = false;
- public function mount()
+ #[Validate(['string', 'nullable'])]
+ public ?string $logDrainNewRelicLicenseKey = null;
+
+ #[Validate(['url', 'nullable'])]
+ public ?string $logDrainNewRelicBaseUri = null;
+
+ #[Validate(['string', 'nullable'])]
+ public ?string $logDrainAxiomDatasetName = null;
+
+ #[Validate(['string', 'nullable'])]
+ public ?string $logDrainAxiomApiKey = null;
+
+ #[Validate(['string', 'nullable'])]
+ public ?string $logDrainCustomConfig = null;
+
+ #[Validate(['string', 'nullable'])]
+ public ?string $logDrainCustomConfigParser = null;
+
+ public function mount(string $server_uuid)
{
- $this->parameters = get_route_parameters();
try {
- $server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
- if (is_null($server)) {
- return redirect()->route('server.index');
- }
- $this->server = $server;
+ $this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
+ $this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
- public function configureLogDrain()
+ public function syncData(bool $toModel = false)
+ {
+ if ($toModel) {
+ $this->customValidation();
+ $this->server->settings->is_logdrain_newrelic_enabled = $this->isLogDrainNewRelicEnabled;
+ $this->server->settings->is_logdrain_axiom_enabled = $this->isLogDrainAxiomEnabled;
+ $this->server->settings->is_logdrain_custom_enabled = $this->isLogDrainCustomEnabled;
+
+ $this->server->settings->logdrain_newrelic_license_key = $this->logDrainNewRelicLicenseKey;
+ $this->server->settings->logdrain_newrelic_base_uri = $this->logDrainNewRelicBaseUri;
+ $this->server->settings->logdrain_axiom_dataset_name = $this->logDrainAxiomDatasetName;
+ $this->server->settings->logdrain_axiom_api_key = $this->logDrainAxiomApiKey;
+ $this->server->settings->logdrain_custom_config = $this->logDrainCustomConfig;
+ $this->server->settings->logdrain_custom_config_parser = $this->logDrainCustomConfigParser;
+
+ $this->server->settings->save();
+ } else {
+ $this->isLogDrainNewRelicEnabled = $this->server->settings->is_logdrain_newrelic_enabled;
+ $this->isLogDrainAxiomEnabled = $this->server->settings->is_logdrain_axiom_enabled;
+ $this->isLogDrainCustomEnabled = $this->server->settings->is_logdrain_custom_enabled;
+
+ $this->logDrainNewRelicLicenseKey = $this->server->settings->logdrain_newrelic_license_key;
+ $this->logDrainNewRelicBaseUri = $this->server->settings->logdrain_newrelic_base_uri;
+ $this->logDrainAxiomDatasetName = $this->server->settings->logdrain_axiom_dataset_name;
+ $this->logDrainAxiomApiKey = $this->server->settings->logdrain_axiom_api_key;
+ $this->logDrainCustomConfig = $this->server->settings->logdrain_custom_config;
+ $this->logDrainCustomConfigParser = $this->server->settings->logdrain_custom_config_parser;
+ }
+ }
+
+ public function customValidation()
+ {
+ if ($this->isLogDrainNewRelicEnabled) {
+ try {
+ $this->validate([
+ 'logDrainNewRelicLicenseKey' => ['required'],
+ 'logDrainNewRelicBaseUri' => ['required', 'url'],
+ ]);
+ } catch (\Throwable $e) {
+ $this->isLogDrainNewRelicEnabled = false;
+
+ throw $e;
+ }
+ } elseif ($this->isLogDrainAxiomEnabled) {
+ try {
+ $this->validate([
+ 'logDrainAxiomDatasetName' => ['required'],
+ 'logDrainAxiomApiKey' => ['required'],
+ ]);
+ } catch (\Throwable $e) {
+ $this->isLogDrainAxiomEnabled = false;
+
+ throw $e;
+ }
+ } elseif ($this->isLogDrainCustomEnabled) {
+ try {
+ $this->validate([
+ 'logDrainCustomConfig' => ['required'],
+ 'logDrainCustomConfigParser' => ['string', 'nullable'],
+ ]);
+ } catch (\Throwable $e) {
+ $this->isLogDrainCustomEnabled = false;
+
+ throw $e;
+ }
+ }
+ }
+
+ public function instantSave()
{
try {
- InstallLogDrain::run($this->server);
- if (! $this->server->isLogDrainEnabled()) {
- $this->dispatch('serverRefresh');
+ $this->syncData(true);
+ if ($this->server->isLogDrainEnabled()) {
+ StartLogDrain::run($this->server);
+ $this->dispatch('success', 'Log drain service started.');
+ } else {
+ StopLogDrain::run($this->server);
$this->dispatch('success', 'Log drain service stopped.');
-
- return;
}
- $this->dispatch('serverRefresh');
- $this->dispatch('success', 'Log drain service started.');
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
-
- public function instantSave(string $type)
- {
- try {
- $ok = $this->submit($type);
- if (! $ok) {
- return;
- }
- $this->configureLogDrain();
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -88,79 +136,10 @@ class LogDrains extends Component
public function submit(string $type)
{
try {
- $this->resetErrorBag();
- if ($type === 'newrelic') {
- $this->validate([
- 'server.settings.is_logdrain_newrelic_enabled' => 'required|boolean',
- 'server.settings.logdrain_newrelic_license_key' => 'required|string',
- 'server.settings.logdrain_newrelic_base_uri' => 'required|string',
- ]);
- $this->server->settings->update([
- 'is_logdrain_highlight_enabled' => false,
- 'is_logdrain_axiom_enabled' => false,
- 'is_logdrain_custom_enabled' => false,
- ]);
- } elseif ($type === 'highlight') {
- $this->validate([
- 'server.settings.is_logdrain_highlight_enabled' => 'required|boolean',
- 'server.settings.logdrain_highlight_project_id' => 'required|string',
- ]);
- $this->server->settings->update([
- 'is_logdrain_newrelic_enabled' => false,
- 'is_logdrain_axiom_enabled' => false,
- 'is_logdrain_custom_enabled' => false,
- ]);
- } elseif ($type === 'axiom') {
- $this->validate([
- 'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
- 'server.settings.logdrain_axiom_dataset_name' => 'required|string',
- 'server.settings.logdrain_axiom_api_key' => 'required|string',
- ]);
- $this->server->settings->update([
- 'is_logdrain_newrelic_enabled' => false,
- 'is_logdrain_highlight_enabled' => false,
- 'is_logdrain_custom_enabled' => false,
- ]);
- } elseif ($type === 'custom') {
- $this->validate([
- 'server.settings.is_logdrain_custom_enabled' => 'required|boolean',
- 'server.settings.logdrain_custom_config' => 'required|string',
- 'server.settings.logdrain_custom_config_parser' => 'nullable',
- ]);
- $this->server->settings->update([
- 'is_logdrain_newrelic_enabled' => false,
- 'is_logdrain_highlight_enabled' => false,
- 'is_logdrain_axiom_enabled' => false,
- ]);
- }
- if (! $this->server->isLogDrainEnabled()) {
- StopLogDrain::dispatch($this->server);
- }
- $this->server->settings->save();
+ $this->syncData(true);
$this->dispatch('success', 'Settings saved.');
-
- return true;
} catch (\Throwable $e) {
- if ($type === 'newrelic') {
- $this->server->settings->update([
- 'is_logdrain_newrelic_enabled' => false,
- ]);
- } elseif ($type === 'highlight') {
- $this->server->settings->update([
- 'is_logdrain_highlight_enabled' => false,
- ]);
- } elseif ($type === 'axiom') {
- $this->server->settings->update([
- 'is_logdrain_axiom_enabled' => false,
- ]);
- } elseif ($type === 'custom') {
- $this->server->settings->update([
- 'is_logdrain_custom_enabled' => false,
- ]);
- }
- handleError($e, $this);
-
- return false;
+ return handleError($e, $this);
}
}
diff --git a/app/Livewire/Server/New/ByIp.php b/app/Livewire/Server/New/ByIp.php
index f80152435..5f60c5db5 100644
--- a/app/Livewire/Server/New/ByIp.php
+++ b/app/Livewire/Server/New/ByIp.php
@@ -6,64 +6,60 @@ use App\Enums\ProxyTypes;
use App\Models\Server;
use App\Models\Team;
use Illuminate\Support\Collection;
+use Livewire\Attributes\Locked;
+use Livewire\Attributes\Validate;
use Livewire\Component;
class ByIp extends Component
{
+ #[Locked]
public $private_keys;
+ #[Locked]
public $limit_reached;
+ #[Validate('nullable|integer', as: 'Private Key')]
public ?int $private_key_id = null;
+ #[Validate('nullable|string', as: 'Private Key Name')]
public $new_private_key_name;
+ #[Validate('nullable|string', as: 'Private Key Description')]
public $new_private_key_description;
+ #[Validate('nullable|string', as: 'Private Key Value')]
public $new_private_key_value;
+ #[Validate('required|string', as: 'Name')]
public string $name;
+ #[Validate('nullable|string', as: 'Description')]
public ?string $description = null;
+ #[Validate('required|string', as: 'IP Address/Domain')]
public string $ip;
+ #[Validate('required|string', as: 'User')]
public string $user = 'root';
+ #[Validate('required|integer|between:1,65535', as: 'Port')]
public int $port = 22;
+ #[Validate('required|boolean', as: 'Swarm Manager')]
public bool $is_swarm_manager = false;
+ #[Validate('required|boolean', as: 'Swarm Worker')]
public bool $is_swarm_worker = false;
+ #[Validate('nullable|integer', as: 'Swarm Cluster')]
public $selected_swarm_cluster = null;
+ #[Validate('required|boolean', as: 'Build Server')]
public bool $is_build_server = false;
+ #[Locked]
public Collection $swarm_managers;
- protected $rules = [
- 'name' => 'required|string',
- 'description' => 'nullable|string',
- 'ip' => 'required',
- 'user' => 'required|string',
- 'port' => 'required|integer',
- 'is_swarm_manager' => 'required|boolean',
- 'is_swarm_worker' => 'required|boolean',
- 'is_build_server' => 'required|boolean',
- ];
-
- protected $validationAttributes = [
- 'name' => 'Name',
- 'description' => 'Description',
- 'ip' => 'IP Address/Domain',
- 'user' => 'User',
- 'port' => 'Port',
- 'is_swarm_manager' => 'Swarm Manager',
- 'is_swarm_worker' => 'Swarm Worker',
- 'is_build_server' => 'Build Server',
- ];
-
public function mount()
{
$this->name = generate_random_name();
@@ -88,6 +84,12 @@ class ByIp extends Component
{
$this->validate();
try {
+ if (Server::where('team_id', currentTeam()->id)
+ ->where('ip', $this->ip)
+ ->exists()) {
+ return $this->dispatch('error', 'This IP/Domain is already in use by another server in your team.');
+ }
+
if (is_null($this->private_key_id)) {
return $this->dispatch('error', 'You must select a private key');
}
diff --git a/app/Livewire/Server/PrivateKey/Show.php b/app/Livewire/Server/PrivateKey/Show.php
index 0ad820428..64aa1884b 100644
--- a/app/Livewire/Server/PrivateKey/Show.php
+++ b/app/Livewire/Server/PrivateKey/Show.php
@@ -8,26 +8,63 @@ use Livewire\Component;
class Show extends Component
{
- public ?Server $server = null;
+ public Server $server;
public $privateKeys = [];
public $parameters = [];
- public function mount()
+ public function mount(string $server_uuid)
{
- $this->parameters = get_route_parameters();
try {
- $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
- if (is_null($this->server)) {
- return redirect()->route('server.index');
- }
+ $this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
+ public function setPrivateKey($privateKeyId)
+ {
+ $ownedPrivateKey = PrivateKey::ownedByCurrentTeam()->find($privateKeyId);
+ if (is_null($ownedPrivateKey)) {
+ $this->dispatch('error', 'You are not allowed to use this private key.');
+
+ return;
+ }
+
+ $originalPrivateKeyId = $this->server->getOriginal('private_key_id');
+ try {
+ $this->server->update(['private_key_id' => $privateKeyId]);
+ ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection(justCheckingNewKey: true);
+ if ($uptime) {
+ $this->dispatch('success', 'Private key updated successfully.');
+ } else {
+ throw new \Exception($error);
+ }
+ } catch (\Exception $e) {
+ $this->server->update(['private_key_id' => $originalPrivateKeyId]);
+ $this->server->validateConnection();
+ $this->dispatch('error', $e->getMessage());
+ }
+ }
+
+ public function checkConnection()
+ {
+ try {
+ ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
+ if ($uptime) {
+ $this->dispatch('success', 'Server is reachable.');
+ } else {
+ $this->dispatch('error', 'Server is not reachable.
Check this documentation for further help.
Error: '.$error);
+
+ return;
+ }
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
public function render()
{
return view('livewire.server.private-key.show');
diff --git a/app/Livewire/Server/Proxy.php b/app/Livewire/Server/Proxy.php
index 123b29d70..0b069ddb9 100644
--- a/app/Livewire/Server/Proxy.php
+++ b/app/Livewire/Server/Proxy.php
@@ -4,7 +4,6 @@ namespace App\Livewire\Server;
use App\Actions\Proxy\CheckConfiguration;
use App\Actions\Proxy\SaveConfiguration;
-use App\Actions\Proxy\StartProxy;
use App\Models\Server;
use Livewire\Component;
@@ -39,18 +38,18 @@ class Proxy extends Component
{
$this->server->proxy = null;
$this->server->save();
+ $this->dispatch('proxyChanged');
}
public function selectProxy($proxy_type)
{
- $this->server->proxy->set('status', 'exited');
- $this->server->proxy->set('type', $proxy_type);
- $this->server->save();
- $this->selectedProxy = $this->server->proxy->type;
- if ($this->selectedProxy !== 'NONE') {
- StartProxy::run($this->server, false);
+ try {
+ $this->server->changeProxy($proxy_type, async: false);
+ $this->selectedProxy = $this->server->proxy->type;
+ $this->dispatch('proxyStatusUpdated');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
}
- $this->dispatch('proxyStatusUpdated');
}
public function instantSave()
@@ -98,7 +97,6 @@ class Proxy extends Component
} else {
$this->dispatch('traefikDashboardAvailable', false);
}
-
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Server/Proxy/Deploy.php b/app/Livewire/Server/Proxy/Deploy.php
index 2279951ee..8fcff85d6 100644
--- a/app/Livewire/Server/Proxy/Deploy.php
+++ b/app/Livewire/Server/Proxy/Deploy.php
@@ -6,6 +6,9 @@ use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Events\ProxyStatusChanged;
use App\Models\Server;
+use Carbon\Carbon;
+use Illuminate\Process\InvokedProcess;
+use Illuminate\Support\Facades\Process;
use Livewire\Component;
class Deploy extends Component
@@ -29,6 +32,7 @@ class Deploy extends Component
'serverRefresh' => 'proxyStatusUpdated',
'checkProxy',
'startProxy',
+ 'proxyChanged' => 'proxyStatusUpdated',
];
}
@@ -94,21 +98,43 @@ class Deploy extends Component
public function stop(bool $forceStop = true)
{
try {
- if ($this->server->isSwarm()) {
- instant_remote_process([
- 'docker service rm coolify-proxy_traefik',
- ], $this->server);
- } else {
- instant_remote_process([
- 'docker rm -f coolify-proxy',
- ], $this->server);
+ $containerName = $this->server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
+ $timeout = 30;
+
+ $process = $this->stopContainer($containerName, $timeout);
+
+ $startTime = Carbon::now()->getTimestamp();
+ while ($process->running()) {
+ if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
+ $this->forceStopContainer($containerName);
+ break;
+ }
+ usleep(100000);
}
- $this->server->proxy->status = 'exited';
- $this->server->proxy->force_stop = $forceStop;
- $this->server->save();
- $this->dispatch('proxyStatusUpdated');
+
+ $this->removeContainer($containerName);
} catch (\Throwable $e) {
return handleError($e, $this);
+ } finally {
+ $this->server->proxy->force_stop = $forceStop;
+ $this->server->proxy->status = 'exited';
+ $this->server->save();
+ $this->dispatch('proxyStatusUpdated');
}
}
+
+ private function stopContainer(string $containerName, int $timeout): InvokedProcess
+ {
+ return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
+ }
+
+ private function forceStopContainer(string $containerName)
+ {
+ instant_remote_process(["docker kill $containerName"], $this->server, throwError: false);
+ }
+
+ private function removeContainer(string $containerName)
+ {
+ instant_remote_process(["docker rm -f $containerName"], $this->server, throwError: false);
+ }
}
diff --git a/app/Livewire/Server/Proxy/Modal.php b/app/Livewire/Server/Proxy/Modal.php
deleted file mode 100644
index 5679944d0..000000000
--- a/app/Livewire/Server/Proxy/Modal.php
+++ /dev/null
@@ -1,16 +0,0 @@
-dispatch('proxyStatusUpdated');
- }
-}
diff --git a/app/Livewire/Server/Proxy/Show.php b/app/Livewire/Server/Proxy/Show.php
index cef909a45..5ecb56a69 100644
--- a/app/Livewire/Server/Proxy/Show.php
+++ b/app/Livewire/Server/Proxy/Show.php
@@ -11,7 +11,7 @@ class Show extends Component
public $parameters = [];
- protected $listeners = ['proxyStatusUpdated'];
+ protected $listeners = ['proxyStatusUpdated', 'proxyChanged' => 'proxyStatusUpdated'];
public function proxyStatusUpdated()
{
@@ -22,10 +22,7 @@ class Show extends Component
{
$this->parameters = get_route_parameters();
try {
- $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
- if (is_null($this->server)) {
- return redirect()->route('server.index');
- }
+ $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail();
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Server/Proxy/Status.php b/app/Livewire/Server/Proxy/Status.php
index d23d7fc20..f4f18381f 100644
--- a/app/Livewire/Server/Proxy/Status.php
+++ b/app/Livewire/Server/Proxy/Status.php
@@ -4,7 +4,7 @@ namespace App\Livewire\Server\Proxy;
use App\Actions\Docker\GetContainersStatus;
use App\Actions\Proxy\CheckProxy;
-use App\Jobs\ContainerStatusJob;
+use App\Actions\Proxy\StartProxy;
use App\Models\Server;
use Livewire\Component;
@@ -44,11 +44,18 @@ class Status extends Component
}
$this->numberOfPolls++;
}
- CheckProxy::run($this->server, true);
+ $shouldStart = CheckProxy::run($this->server, true);
+ if ($shouldStart) {
+ StartProxy::run($this->server, false);
+ }
$this->dispatch('proxyStatusUpdated');
if ($this->server->proxy->status === 'running') {
$this->polling = false;
$notification && $this->dispatch('success', 'Proxy is running.');
+ } elseif ($this->server->proxy->status === 'exited' and ! $this->server->proxy->force_stop) {
+ $notification && $this->dispatch('error', 'Proxy has exited.');
+ } elseif ($this->server->proxy->force_stop) {
+ $notification && $this->dispatch('error', 'Proxy is stopped manually.');
} else {
$notification && $this->dispatch('error', 'Proxy is not running.');
}
diff --git a/app/Livewire/Server/Resources.php b/app/Livewire/Server/Resources.php
index 800344ac3..f549b43cb 100644
--- a/app/Livewire/Server/Resources.php
+++ b/app/Livewire/Server/Resources.php
@@ -15,7 +15,9 @@ class Resources extends Component
public $parameters = [];
- public Collection $unmanagedContainers;
+ public Collection $containers;
+
+ public $activeTab = 'managed';
public function getListeners()
{
@@ -50,14 +52,29 @@ class Resources extends Component
public function refreshStatus()
{
$this->server->refresh();
- $this->loadUnmanagedContainers();
+ if ($this->activeTab === 'managed') {
+ $this->loadManagedContainers();
+ } else {
+ $this->loadUnmanagedContainers();
+ }
$this->dispatch('success', 'Resource statuses refreshed.');
}
+ public function loadManagedContainers()
+ {
+ try {
+ $this->activeTab = 'managed';
+ $this->containers = $this->server->refresh()->definedResources();
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
public function loadUnmanagedContainers()
{
+ $this->activeTab = 'unmanaged';
try {
- $this->unmanagedContainers = $this->server->loadUnmanagedContainers();
+ $this->containers = $this->server->loadUnmanagedContainers();
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -65,13 +82,14 @@ class Resources extends Component
public function mount()
{
- $this->unmanagedContainers = collect();
+ $this->containers = collect();
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.index');
}
+ $this->loadManagedContainers();
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php
index a5e94a19a..a5544489d 100644
--- a/app/Livewire/Server/Show.php
+++ b/app/Livewire/Server/Show.php
@@ -2,42 +2,263 @@
namespace App\Livewire\Server;
+use App\Actions\Server\StartSentinel;
+use App\Actions\Server\StopSentinel;
use App\Models\Server;
-use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
+use Livewire\Attributes\Locked;
+use Livewire\Attributes\Validate;
use Livewire\Component;
class Show extends Component
{
- use AuthorizesRequests;
+ public Server $server;
- public ?Server $server = null;
+ #[Validate(['required'])]
+ public string $name;
- public $parameters = [];
+ #[Validate(['nullable'])]
+ public ?string $description = null;
- protected $listeners = ['refreshServerShow'];
+ #[Validate(['required'])]
+ public string $ip;
- public function mount()
+ #[Validate(['required'])]
+ public string $user;
+
+ #[Validate(['required'])]
+ public string $port;
+
+ #[Validate(['nullable'])]
+ public ?string $validationLogs = null;
+
+ #[Validate(['nullable', 'url'])]
+ public ?string $wildcardDomain = null;
+
+ #[Validate(['required'])]
+ public bool $isReachable;
+
+ #[Validate(['required'])]
+ public bool $isUsable;
+
+ #[Validate(['required'])]
+ public bool $isSwarmManager;
+
+ #[Validate(['required'])]
+ public bool $isSwarmWorker;
+
+ #[Validate(['required'])]
+ public bool $isBuildServer;
+
+ #[Validate(['required'])]
+ public bool $isMetricsEnabled;
+
+ #[Validate(['required'])]
+ public string $sentinelToken;
+
+ #[Validate(['nullable'])]
+ public ?string $sentinelUpdatedAt = null;
+
+ #[Validate(['required', 'integer', 'min:1'])]
+ public int $sentinelMetricsRefreshRateSeconds;
+
+ #[Validate(['required', 'integer', 'min:1'])]
+ public int $sentinelMetricsHistoryDays;
+
+ #[Validate(['required', 'integer', 'min:10'])]
+ public int $sentinelPushIntervalSeconds;
+
+ #[Validate(['nullable', 'url'])]
+ public ?string $sentinelCustomUrl = null;
+
+ #[Validate(['required'])]
+ public bool $isSentinelEnabled;
+
+ #[Validate(['required'])]
+ public bool $isSentinelDebugEnabled;
+
+ #[Validate(['required'])]
+ public string $serverTimezone;
+
+ #[Locked]
+ public array $timezones;
+
+ public function getListeners()
+ {
+ $teamId = auth()->user()->currentTeam()->id;
+
+ return [
+ "echo-private:team.{$teamId},CloudflareTunnelConfigured" => 'refresh',
+ 'refreshServerShow' => 'refresh',
+ ];
+ }
+
+ public function mount(string $server_uuid)
{
- $this->parameters = get_route_parameters();
try {
- $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
- if (is_null($this->server)) {
- return redirect()->route('server.index');
- }
+ $this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
+ $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
+ $this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
- public function refreshServerShow()
+ public function syncData(bool $toModel = false)
{
- $this->server->refresh();
+ if ($toModel) {
+ $this->validate();
+
+ if (Server::where('team_id', currentTeam()->id)
+ ->where('ip', $this->ip)
+ ->where('id', '!=', $this->server->id)
+ ->exists()) {
+ $this->ip = $this->server->ip;
+ throw new \Exception('This IP/Domain is already in use by another server in your team.');
+ }
+
+ $this->server->name = $this->name;
+ $this->server->description = $this->description;
+ $this->server->ip = $this->ip;
+ $this->server->user = $this->user;
+ $this->server->port = $this->port;
+ $this->server->validation_logs = $this->validationLogs;
+ $this->server->save();
+
+ $this->server->settings->is_swarm_manager = $this->isSwarmManager;
+ $this->server->settings->wildcard_domain = $this->wildcardDomain;
+ $this->server->settings->is_swarm_worker = $this->isSwarmWorker;
+ $this->server->settings->is_build_server = $this->isBuildServer;
+ $this->server->settings->is_metrics_enabled = $this->isMetricsEnabled;
+ $this->server->settings->sentinel_token = $this->sentinelToken;
+ $this->server->settings->sentinel_metrics_refresh_rate_seconds = $this->sentinelMetricsRefreshRateSeconds;
+ $this->server->settings->sentinel_metrics_history_days = $this->sentinelMetricsHistoryDays;
+ $this->server->settings->sentinel_push_interval_seconds = $this->sentinelPushIntervalSeconds;
+ $this->server->settings->sentinel_custom_url = $this->sentinelCustomUrl;
+ $this->server->settings->is_sentinel_enabled = $this->isSentinelEnabled;
+ $this->server->settings->is_sentinel_debug_enabled = $this->isSentinelDebugEnabled;
+
+ if (! validate_timezone($this->serverTimezone)) {
+ $this->serverTimezone = config('app.timezone');
+ throw new \Exception('Invalid timezone.');
+ } else {
+ $this->server->settings->server_timezone = $this->serverTimezone;
+ }
+
+ $this->server->settings->save();
+ } else {
+ $this->name = $this->server->name;
+ $this->description = $this->server->description;
+ $this->ip = $this->server->ip;
+ $this->user = $this->server->user;
+ $this->port = $this->server->port;
+
+ $this->wildcardDomain = $this->server->settings->wildcard_domain;
+ $this->isReachable = $this->server->settings->is_reachable;
+ $this->isUsable = $this->server->settings->is_usable;
+ $this->isSwarmManager = $this->server->settings->is_swarm_manager;
+ $this->isSwarmWorker = $this->server->settings->is_swarm_worker;
+ $this->isBuildServer = $this->server->settings->is_build_server;
+ $this->isMetricsEnabled = $this->server->settings->is_metrics_enabled;
+ $this->sentinelToken = $this->server->settings->sentinel_token;
+ $this->sentinelMetricsRefreshRateSeconds = $this->server->settings->sentinel_metrics_refresh_rate_seconds;
+ $this->sentinelMetricsHistoryDays = $this->server->settings->sentinel_metrics_history_days;
+ $this->sentinelPushIntervalSeconds = $this->server->settings->sentinel_push_interval_seconds;
+ $this->sentinelCustomUrl = $this->server->settings->sentinel_custom_url;
+ $this->isSentinelEnabled = $this->server->settings->is_sentinel_enabled;
+ $this->isSentinelDebugEnabled = $this->server->settings->is_sentinel_debug_enabled;
+ $this->sentinelUpdatedAt = $this->server->settings->updated_at;
+ $this->serverTimezone = $this->server->settings->server_timezone;
+ }
+ }
+
+ public function refresh()
+ {
+ $this->syncData();
$this->dispatch('$refresh');
}
+ public function validateServer($install = true)
+ {
+ try {
+ $this->validationLogs = $this->server->validation_logs = null;
+ $this->server->save();
+ $this->dispatch('init', $install);
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function checkLocalhostConnection()
+ {
+ $this->syncData(true);
+ ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
+ if ($uptime) {
+ $this->dispatch('success', 'Server is reachable.');
+ $this->server->settings->is_reachable = $this->isReachable = true;
+ $this->server->settings->is_usable = $this->isUsable = true;
+ $this->server->settings->save();
+ $this->dispatch('proxyStatusUpdated');
+ } else {
+ $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.
Check this documentation for further help.
Error: '.$error);
+
+ return;
+ }
+ }
+
+ public function restartSentinel()
+ {
+ $this->server->restartSentinel();
+ $this->dispatch('success', 'Sentinel restarted.');
+ }
+
+ public function updatedIsSentinelDebugEnabled($value)
+ {
+ $this->submit();
+ $this->restartSentinel();
+ }
+
+ public function updatedIsMetricsEnabled($value)
+ {
+ $this->submit();
+ $this->restartSentinel();
+ }
+
+ public function updatedIsSentinelEnabled($value)
+ {
+ if ($value === true) {
+ StartSentinel::run($this->server, true);
+ } else {
+ $this->isMetricsEnabled = false;
+ $this->isSentinelDebugEnabled = false;
+ StopSentinel::dispatch($this->server);
+ }
+ $this->submit();
+
+ }
+
+ public function regenerateSentinelToken()
+ {
+ try {
+ $this->server->settings->generateSentinelToken();
+ $this->dispatch('success', 'Token regenerated & Sentinel restarted.');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function instantSave()
+ {
+ $this->submit();
+ }
+
public function submit()
{
- $this->dispatch('serverRefresh', false);
+ try {
+ $this->syncData(true);
+ $this->dispatch('success', 'Server updated.');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
}
public function render()
diff --git a/app/Livewire/Server/ShowPrivateKey.php b/app/Livewire/Server/ShowPrivateKey.php
deleted file mode 100644
index 578a08967..000000000
--- a/app/Livewire/Server/ShowPrivateKey.php
+++ /dev/null
@@ -1,59 +0,0 @@
-server->private_key_id;
- refresh_server_connection($this->server->privateKey);
- $this->server->update([
- 'private_key_id' => $newPrivateKeyId,
- ]);
- $this->server->refresh();
- refresh_server_connection($this->server->privateKey);
- $this->checkConnection();
- } catch (\Throwable $e) {
- $this->server->update([
- 'private_key_id' => $oldPrivateKeyId,
- ]);
- $this->server->refresh();
- refresh_server_connection($this->server->privateKey);
-
- return handleError($e, $this);
- }
- }
-
- public function checkConnection()
- {
- try {
- ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
- if ($uptime) {
- $this->dispatch('success', 'Server is reachable.');
- } else {
- ray($error);
- $this->dispatch('error', 'Server is not reachable.
Please validate your configuration and connection.
Check this documentation for further help.');
-
- return;
- }
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
-
- public function mount()
- {
- $this->parameters = get_route_parameters();
- }
-}
diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php
index 8c5bc23ed..791ef9350 100644
--- a/app/Livewire/Server/ValidateAndInstall.php
+++ b/app/Livewire/Server/ValidateAndInstall.php
@@ -159,7 +159,8 @@ class ValidateAndInstall extends Component
$this->dispatch('refreshBoardingIndex');
$this->dispatch('success', 'Server validated.');
} else {
- $this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: documentation.';
+ $requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.');
+ $this->error = 'Minimum Docker Engine version '.$requiredDockerVersion.' is not instaled. Please install Docker manually before continuing: documentation.';
$this->server->update([
'validation_logs' => $this->error,
]);
diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php
index c52970258..55ba49867 100644
--- a/app/Livewire/Settings/Index.php
+++ b/app/Livewire/Settings/Index.php
@@ -5,62 +5,95 @@ namespace App\Livewire\Settings;
use App\Jobs\CheckForUpdatesJob;
use App\Models\InstanceSettings;
use App\Models\Server;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
+use Livewire\Attributes\Locked;
+use Livewire\Attributes\Validate;
use Livewire\Component;
class Index extends Component
{
public InstanceSettings $settings;
- public bool $do_not_track;
-
- public bool $is_auto_update_enabled;
-
- public bool $is_registration_enabled;
-
- public bool $is_dns_validation_enabled;
-
- public bool $is_api_enabled;
-
- public string $auto_update_frequency;
-
- public string $update_check_frequency;
-
- protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
-
protected Server $server;
- protected $rules = [
- 'settings.fqdn' => 'nullable',
- 'settings.resale_license' => 'nullable',
- 'settings.public_port_min' => 'required',
- 'settings.public_port_max' => 'required',
- 'settings.custom_dns_servers' => 'nullable',
- 'settings.instance_name' => 'nullable',
- 'settings.allowed_ips' => 'nullable',
- 'settings.is_auto_update_enabled' => 'boolean',
- 'auto_update_frequency' => 'string',
- 'update_check_frequency' => 'string',
- 'settings.instance_timezone' => 'required|string|timezone',
- ];
-
- protected $validationAttributes = [
- 'settings.fqdn' => 'FQDN',
- 'settings.resale_license' => 'Resale License',
- 'settings.public_port_min' => 'Public port min',
- 'settings.public_port_max' => 'Public port max',
- 'settings.custom_dns_servers' => 'Custom DNS servers',
- 'settings.allowed_ips' => 'Allowed IPs',
- 'settings.is_auto_update_enabled' => 'Auto Update Enabled',
- 'auto_update_frequency' => 'Auto Update Frequency',
- 'update_check_frequency' => 'Update Check Frequency',
- ];
-
+ #[Locked]
public $timezones;
+ #[Validate('boolean')]
+ public bool $is_auto_update_enabled;
+
+ #[Validate('nullable|string|max:255')]
+ public ?string $fqdn = null;
+
+ #[Validate('nullable|string|max:255')]
+ public ?string $resale_license = null;
+
+ #[Validate('required|integer|min:1025|max:65535')]
+ public int $public_port_min;
+
+ #[Validate('required|integer|min:1025|max:65535')]
+ public int $public_port_max;
+
+ #[Validate('nullable|string')]
+ public ?string $custom_dns_servers = null;
+
+ #[Validate('nullable|string|max:255')]
+ public ?string $instance_name = null;
+
+ #[Validate('nullable|string')]
+ public ?string $allowed_ips = null;
+
+ #[Validate('nullable|string')]
+ public ?string $public_ipv4 = null;
+
+ #[Validate('nullable|string')]
+ public ?string $public_ipv6 = null;
+
+ #[Validate('string')]
+ public string $auto_update_frequency;
+
+ #[Validate('string')]
+ public string $update_check_frequency;
+
+ #[Validate('required|string|timezone')]
+ public string $instance_timezone;
+
+ #[Validate('boolean')]
+ public bool $do_not_track;
+
+ #[Validate('boolean')]
+ public bool $is_registration_enabled;
+
+ #[Validate('boolean')]
+ public bool $is_dns_validation_enabled;
+
+ #[Validate('boolean')]
+ public bool $is_api_enabled;
+
+ #[Validate('boolean')]
+ public bool $disable_two_step_confirmation;
+
+ public function render()
+ {
+ return view('livewire.settings.index');
+ }
+
public function mount()
{
- if (isInstanceAdmin()) {
- $this->settings = InstanceSettings::get();
+ if (! isInstanceAdmin()) {
+ return redirect()->route('dashboard');
+ } else {
+ $this->settings = instanceSettings();
+ $this->fqdn = $this->settings->fqdn;
+ $this->resale_license = $this->settings->resale_license;
+ $this->public_port_min = $this->settings->public_port_min;
+ $this->public_port_max = $this->settings->public_port_max;
+ $this->custom_dns_servers = $this->settings->custom_dns_servers;
+ $this->instance_name = $this->settings->instance_name;
+ $this->allowed_ips = $this->settings->allowed_ips;
+ $this->public_ipv4 = $this->settings->public_ipv4;
+ $this->public_ipv6 = $this->settings->public_ipv6;
$this->do_not_track = $this->settings->do_not_track;
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
$this->is_registration_enabled = $this->settings->is_registration_enabled;
@@ -69,13 +102,22 @@ class Index extends Component
$this->auto_update_frequency = $this->settings->auto_update_frequency;
$this->update_check_frequency = $this->settings->update_check_frequency;
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
- } else {
- return redirect()->route('dashboard');
+ $this->instance_timezone = $this->settings->instance_timezone;
+ $this->disable_two_step_confirmation = $this->settings->disable_two_step_confirmation;
}
}
- public function instantSave()
+ public function instantSave($isSave = true)
{
+ $this->settings->fqdn = $this->fqdn;
+ $this->settings->resale_license = $this->resale_license;
+ $this->settings->public_port_min = $this->public_port_min;
+ $this->settings->public_port_max = $this->public_port_max;
+ $this->settings->custom_dns_servers = $this->custom_dns_servers;
+ $this->settings->instance_name = $this->instance_name;
+ $this->settings->allowed_ips = $this->allowed_ips;
+ $this->settings->public_ipv4 = $this->public_ipv4;
+ $this->settings->public_ipv6 = $this->public_ipv6;
$this->settings->do_not_track = $this->do_not_track;
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
@@ -83,8 +125,12 @@ class Index extends Component
$this->settings->is_api_enabled = $this->is_api_enabled;
$this->settings->auto_update_frequency = $this->auto_update_frequency;
$this->settings->update_check_frequency = $this->update_check_frequency;
- $this->settings->save();
- $this->dispatch('success', 'Settings updated!');
+ $this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation;
+ $this->settings->instance_timezone = $this->instance_timezone;
+ if ($isSave) {
+ $this->settings->save();
+ $this->dispatch('success', 'Settings updated!');
+ }
}
public function submit()
@@ -93,6 +139,14 @@ class Index extends Component
$error_show = false;
$this->server = Server::findOrFail(0);
$this->resetErrorBag();
+
+ if (! validate_timezone($this->instance_timezone)) {
+ $this->instance_timezone = config('app.timezone');
+ throw new \Exception('Invalid timezone.');
+ } else {
+ $this->settings->instance_timezone = $this->instance_timezone;
+ }
+
if ($this->settings->public_port_min > $this->settings->public_port_max) {
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
@@ -141,13 +195,8 @@ class Index extends Component
$this->settings->allowed_ips = $this->settings->allowed_ips->unique();
$this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
- $this->settings->do_not_track = $this->do_not_track;
- $this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
- $this->settings->is_registration_enabled = $this->is_registration_enabled;
- $this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
- $this->settings->is_api_enabled = $this->is_api_enabled;
- $this->settings->auto_update_frequency = $this->auto_update_frequency;
- $this->settings->update_check_frequency = $this->update_check_frequency;
+ $this->instantSave(isSave: false);
+
$this->settings->save();
$this->server->setupDynamicProxyConfiguration();
if (! $error_show) {
@@ -162,7 +211,7 @@ class Index extends Component
{
CheckForUpdatesJob::dispatchSync();
$this->dispatch('updateAvailable');
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
if ($settings->new_version_available) {
$this->dispatch('success', 'New version available!');
} else {
@@ -170,15 +219,16 @@ class Index extends Component
}
}
- public function updatedSettingsInstanceTimezone($value)
+ public function toggleTwoStepConfirmation($password)
{
- $this->settings->instance_timezone = $value;
- $this->settings->save();
- $this->dispatch('success', 'Instance timezone updated.');
- }
+ if (! Hash::check($password, Auth::user()->password)) {
+ $this->addError('password', 'The provided password is incorrect.');
- public function render()
- {
- return view('livewire.settings.index');
+ return;
+ }
+
+ $this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation = true;
+ $this->settings->save();
+ $this->dispatch('success', 'Two step confirmation has been disabled.');
}
}
diff --git a/app/Livewire/Settings/License.php b/app/Livewire/Settings/License.php
deleted file mode 100644
index f9402fd7b..000000000
--- a/app/Livewire/Settings/License.php
+++ /dev/null
@@ -1,56 +0,0 @@
- 'nullable',
- 'settings.is_resale_license_active' => 'nullable',
- ];
-
- protected $validationAttributes = [
- 'settings.resale_license' => 'License',
- 'instance_id' => 'Instance Id (Do not change this)',
- 'settings.is_resale_license_active' => 'Is License Active',
- ];
-
- public function mount()
- {
- if (! isCloud()) {
- abort(404);
- }
- $this->instance_id = config('app.id');
- $this->settings = \App\Models\InstanceSettings::get();
- }
-
- public function render()
- {
- return view('livewire.settings.license');
- }
-
- public function submit()
- {
- $this->validate();
- $this->settings->save();
- if ($this->settings->resale_license) {
- try {
- CheckResaleLicense::run();
- $this->dispatch('reloadWindow');
- } catch (\Throwable $e) {
- session()->flash('error', 'Something went wrong. Please contact support.
Error: '.$e->getMessage());
- ray($e->getMessage());
-
- return redirect()->route('settings.license');
- }
- }
- }
-}
diff --git a/app/Livewire/SettingsBackup.php b/app/Livewire/SettingsBackup.php
index 99b8f8d49..1b0599ffe 100644
--- a/app/Livewire/SettingsBackup.php
+++ b/app/Livewire/SettingsBackup.php
@@ -2,50 +2,59 @@
namespace App\Livewire;
-use App\Jobs\DatabaseBackupJob;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
+use Livewire\Attributes\Locked;
+use Livewire\Attributes\Validate;
use Livewire\Component;
class SettingsBackup extends Component
{
public InstanceSettings $settings;
- public $s3s;
-
public ?StandalonePostgresql $database = null;
public ScheduledDatabaseBackup|null|array $backup = [];
+ #[Locked]
+ public $s3s;
+
+ #[Locked]
public $executions = [];
- protected $rules = [
- 'database.uuid' => 'required',
- 'database.name' => 'required',
- 'database.description' => 'nullable',
- 'database.postgres_user' => 'required',
- 'database.postgres_password' => 'required',
+ #[Validate(['required'])]
+ public string $uuid;
- ];
+ #[Validate(['required'])]
+ public string $name;
- protected $validationAttributes = [
- 'database.uuid' => 'uuid',
- 'database.name' => 'name',
- 'database.description' => 'description',
- 'database.postgres_user' => 'postgres user',
- 'database.postgres_password' => 'postgres password',
- ];
+ #[Validate(['nullable'])]
+ public ?string $description = null;
+
+ #[Validate(['required'])]
+ public string $postgres_user;
+
+ #[Validate(['required'])]
+ public string $postgres_password;
public function mount()
{
- if (isInstanceAdmin()) {
- $settings = InstanceSettings::get();
+ if (! isInstanceAdmin()) {
+ return redirect()->route('dashboard');
+ } else {
+ $settings = instanceSettings();
$this->database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($this->database) {
+ $this->uuid = $this->database->uuid;
+ $this->name = $this->database->name;
+ $this->description = $this->database->description;
+ $this->postgres_user = $this->database->postgres_user;
+ $this->postgres_password = $this->database->postgres_password;
+
if ($this->database->status !== 'running') {
$this->database->status = 'running';
$this->database->save();
@@ -55,13 +64,10 @@ class SettingsBackup extends Component
}
$this->settings = $settings;
$this->s3s = $s3s;
-
- } else {
- return redirect()->route('dashboard');
}
}
- public function add_coolify_database()
+ public function addCoolifyDatabase()
{
try {
$server = Server::findOrFail(0);
@@ -78,7 +84,7 @@ class SettingsBackup extends Component
'postgres_password' => $postgres_password,
'postgres_db' => $postgres_db,
'status' => 'running',
- 'destination_type' => 'App\Models\StandaloneDocker',
+ 'destination_type' => \App\Models\StandaloneDocker::class,
'destination_id' => 0,
]);
$this->backup = ScheduledDatabaseBackup::create([
@@ -87,27 +93,33 @@ class SettingsBackup extends Component
'save_s3' => false,
'frequency' => '0 0 * * *',
'database_id' => $this->database->id,
- 'database_type' => 'App\Models\StandalonePostgresql',
+ 'database_type' => \App\Models\StandalonePostgresql::class,
'team_id' => currentTeam()->id,
]);
$this->database->refresh();
$this->backup->refresh();
$this->s3s = S3Storage::whereTeamId(0)->get();
+
+ $this->uuid = $this->database->uuid;
+ $this->name = $this->database->name;
+ $this->description = $this->database->description;
+ $this->postgres_user = $this->database->postgres_user;
+ $this->postgres_password = $this->database->postgres_password;
+ $this->executions = $this->backup->executions;
+
} catch (\Exception $e) {
return handleError($e, $this);
}
}
- public function backup_now()
- {
- dispatch(new DatabaseBackupJob(
- backup: $this->backup
- ));
- $this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
- }
-
public function submit()
{
+ $this->database->update([
+ 'name' => $this->name,
+ 'description' => $this->description,
+ 'postgres_user' => $this->postgres_user,
+ 'postgres_password' => $this->postgres_password,
+ ]);
$this->dispatch('success', 'Backup updated.');
}
}
diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php
index 3eb8ea646..61f720b3a 100644
--- a/app/Livewire/SettingsEmail.php
+++ b/app/Livewire/SettingsEmail.php
@@ -3,104 +3,85 @@
namespace App\Livewire;
use App\Models\InstanceSettings;
-use App\Notifications\TransactionalEmails\Test;
+use Livewire\Attributes\Validate;
use Livewire\Component;
class SettingsEmail extends Component
{
public InstanceSettings $settings;
- public string $emails;
+ #[Validate(['boolean'])]
+ public bool $smtpEnabled = false;
- protected $rules = [
- 'settings.smtp_enabled' => 'nullable|boolean',
- 'settings.smtp_host' => 'required',
- 'settings.smtp_port' => 'required|numeric',
- 'settings.smtp_encryption' => 'nullable',
- 'settings.smtp_username' => 'nullable',
- 'settings.smtp_password' => 'nullable',
- 'settings.smtp_timeout' => 'nullable',
- 'settings.smtp_from_address' => 'required|email',
- 'settings.smtp_from_name' => 'required',
- 'settings.resend_enabled' => 'nullable|boolean',
- 'settings.resend_api_key' => 'nullable',
+ #[Validate(['nullable', 'string'])]
+ public ?string $smtpHost = null;
- ];
+ #[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
+ public ?int $smtpPort = null;
- protected $validationAttributes = [
- 'settings.smtp_from_address' => 'From Address',
- 'settings.smtp_from_name' => 'From Name',
- 'settings.smtp_recipients' => 'Recipients',
- 'settings.smtp_host' => 'Host',
- 'settings.smtp_port' => 'Port',
- 'settings.smtp_encryption' => 'Encryption',
- 'settings.smtp_username' => 'Username',
- 'settings.smtp_password' => 'Password',
- 'settings.smtp_timeout' => 'Timeout',
- 'settings.resend_api_key' => 'Resend API Key',
- ];
+ #[Validate(['nullable', 'string'])]
+ public ?string $smtpEncryption = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $smtpUsername = null;
+
+ #[Validate(['nullable'])]
+ public ?string $smtpPassword = null;
+
+ #[Validate(['nullable', 'numeric'])]
+ public ?int $smtpTimeout = null;
+
+ #[Validate(['nullable', 'email'])]
+ public ?string $smtpFromAddress = null;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $smtpFromName = null;
+
+ #[Validate(['boolean'])]
+ public bool $resendEnabled = false;
+
+ #[Validate(['nullable', 'string'])]
+ public ?string $resendApiKey = null;
public function mount()
{
- if (isInstanceAdmin()) {
- $this->settings = InstanceSettings::get();
- $this->emails = auth()->user()->email;
- } else {
+ if (isInstanceAdmin() === false) {
return redirect()->route('dashboard');
}
-
+ $this->settings = instanceSettings();
+ $this->syncData();
}
- public function submitFromFields()
+ public function syncData(bool $toModel = false)
{
- try {
- $this->resetErrorBag();
- $this->validate([
- 'settings.smtp_from_address' => 'required|email',
- 'settings.smtp_from_name' => 'required',
- ]);
+ if ($toModel) {
+ $this->validate();
+ $this->settings->smtp_enabled = $this->smtpEnabled;
+ $this->settings->smtp_host = $this->smtpHost;
+ $this->settings->smtp_port = $this->smtpPort;
+ $this->settings->smtp_encryption = $this->smtpEncryption;
+ $this->settings->smtp_username = $this->smtpUsername;
+ $this->settings->smtp_password = $this->smtpPassword;
+ $this->settings->smtp_timeout = $this->smtpTimeout;
+ $this->settings->smtp_from_address = $this->smtpFromAddress;
+ $this->settings->smtp_from_name = $this->smtpFromName;
+
+ $this->settings->resend_enabled = $this->resendEnabled;
+ $this->settings->resend_api_key = $this->resendApiKey;
$this->settings->save();
- $this->dispatch('success', 'Settings saved.');
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
+ } else {
+ $this->smtpEnabled = $this->settings->smtp_enabled;
+ $this->smtpHost = $this->settings->smtp_host;
+ $this->smtpPort = $this->settings->smtp_port;
+ $this->smtpEncryption = $this->settings->smtp_encryption;
+ $this->smtpUsername = $this->settings->smtp_username;
+ $this->smtpPassword = $this->settings->smtp_password;
+ $this->smtpTimeout = $this->settings->smtp_timeout;
+ $this->smtpFromAddress = $this->settings->smtp_from_address;
+ $this->smtpFromName = $this->settings->smtp_from_name;
- public function submitResend()
- {
- try {
- $this->resetErrorBag();
- $this->validate([
- 'settings.smtp_from_address' => 'required|email',
- 'settings.smtp_from_name' => 'required',
- 'settings.resend_api_key' => 'required',
- ]);
- $this->settings->save();
- $this->dispatch('success', 'Settings saved.');
- } catch (\Throwable $e) {
- $this->settings->resend_enabled = false;
-
- return handleError($e, $this);
- }
- }
-
- public function instantSaveResend()
- {
- try {
- $this->settings->smtp_enabled = false;
- $this->submitResend();
- } catch (\Throwable $e) {
- return handleError($e, $this);
- }
- }
-
- public function instantSave()
- {
- try {
- $this->settings->resend_enabled = false;
- $this->submit();
- } catch (\Throwable $e) {
- return handleError($e, $this);
+ $this->resendEnabled = $this->settings->resend_enabled;
+ $this->resendApiKey = $this->settings->resend_api_key;
}
}
@@ -108,26 +89,29 @@ class SettingsEmail extends Component
{
try {
$this->resetErrorBag();
- $this->validate([
- 'settings.smtp_from_address' => 'required|email',
- 'settings.smtp_from_name' => 'required',
- 'settings.smtp_host' => 'required',
- 'settings.smtp_port' => 'required|numeric',
- 'settings.smtp_encryption' => 'nullable',
- 'settings.smtp_username' => 'nullable',
- 'settings.smtp_password' => 'nullable',
- 'settings.smtp_timeout' => 'nullable',
- ]);
- $this->settings->save();
+ $this->syncData(true);
$this->dispatch('success', 'Settings saved.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
- public function sendTestNotification()
+ public function instantSave(string $type)
{
- $this->settings?->notify(new Test($this->emails));
- $this->dispatch('success', 'Test email sent.');
+ try {
+ if ($type === 'SMTP') {
+ $this->resendEnabled = false;
+ } else {
+ $this->smtpEnabled = false;
+ }
+ $this->syncData(true);
+ if ($this->smtpEnabled || $this->resendEnabled) {
+ $this->dispatch('success', "{$type} enabled.");
+ } else {
+ $this->dispatch('success', "{$type} disabled.");
+ }
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
}
}
diff --git a/app/Livewire/SettingsOauth.php b/app/Livewire/SettingsOauth.php
index c3884589f..17b3b89a3 100644
--- a/app/Livewire/SettingsOauth.php
+++ b/app/Livewire/SettingsOauth.php
@@ -24,6 +24,9 @@ class SettingsOauth extends Component
public function mount()
{
+ if (! isInstanceAdmin()) {
+ return redirect()->route('home');
+ }
$this->oauth_settings_map = OauthSetting::all()->sortBy('provider')->reduce(function ($carry, $setting) {
$carry[$setting->provider] = $setting;
diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php
index 75d7fd04a..07cef54f9 100644
--- a/app/Livewire/Source/Github/Change.php
+++ b/app/Livewire/Source/Github/Change.php
@@ -4,7 +4,6 @@ namespace App\Livewire\Source\Github;
use App\Jobs\GithubAppPermissionJob;
use App\Models\GithubApp;
-use Illuminate\Support\Facades\Http;
use Livewire\Component;
class Change extends Component
@@ -93,51 +92,53 @@ class Change extends Component
// }
public function mount()
{
- $github_app_uuid = request()->github_app_uuid;
- $this->github_app = GithubApp::where('uuid', $github_app_uuid)->first();
- if (! $this->github_app) {
- return redirect()->route('source.all');
- }
- $this->applications = $this->github_app->applications;
- $settings = \App\Models\InstanceSettings::get();
- $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
+ try {
+ $github_app_uuid = request()->github_app_uuid;
+ $this->github_app = GithubApp::ownedByCurrentTeam()->whereUuid($github_app_uuid)->firstOrFail();
- $this->name = str($this->github_app->name)->kebab();
- $this->fqdn = $settings->fqdn;
+ $this->applications = $this->github_app->applications;
+ $settings = instanceSettings();
+ $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
- if ($settings->public_ipv4) {
- $this->ipv4 = 'http://'.$settings->public_ipv4.':'.config('app.port');
- }
- if ($settings->public_ipv6) {
- $this->ipv6 = 'http://'.$settings->public_ipv6.':'.config('app.port');
- }
- if ($this->github_app->installation_id && session('from')) {
- $source_id = data_get(session('from'), 'source_id');
- if (! $source_id || $this->github_app->id !== $source_id) {
- session()->forget('from');
- } else {
- $parameters = data_get(session('from'), 'parameters');
- $back = data_get(session('from'), 'back');
- $environment_name = data_get($parameters, 'environment_name');
- $project_uuid = data_get($parameters, 'project_uuid');
- $type = data_get($parameters, 'type');
- $destination = data_get($parameters, 'destination');
- session()->forget('from');
+ $this->name = str($this->github_app->name)->kebab();
+ $this->fqdn = $settings->fqdn;
- return redirect()->route($back, [
- 'environment_name' => $environment_name,
- 'project_uuid' => $project_uuid,
- 'type' => $type,
- 'destination' => $destination,
- ]);
+ if ($settings->public_ipv4) {
+ $this->ipv4 = 'http://'.$settings->public_ipv4.':'.config('app.port');
}
- }
- $this->parameters = get_route_parameters();
- if (isCloud() && ! isDev()) {
- $this->webhook_endpoint = config('app.url');
- } else {
- $this->webhook_endpoint = $this->ipv4;
- $this->is_system_wide = $this->github_app->is_system_wide;
+ if ($settings->public_ipv6) {
+ $this->ipv6 = 'http://'.$settings->public_ipv6.':'.config('app.port');
+ }
+ if ($this->github_app->installation_id && session('from')) {
+ $source_id = data_get(session('from'), 'source_id');
+ if (! $source_id || $this->github_app->id !== $source_id) {
+ session()->forget('from');
+ } else {
+ $parameters = data_get(session('from'), 'parameters');
+ $back = data_get(session('from'), 'back');
+ $environment_name = data_get($parameters, 'environment_name');
+ $project_uuid = data_get($parameters, 'project_uuid');
+ $type = data_get($parameters, 'type');
+ $destination = data_get($parameters, 'destination');
+ session()->forget('from');
+
+ return redirect()->route($back, [
+ 'environment_name' => $environment_name,
+ 'project_uuid' => $project_uuid,
+ 'type' => $type,
+ 'destination' => $destination,
+ ]);
+ }
+ }
+ $this->parameters = get_route_parameters();
+ if (isCloud() && ! isDev()) {
+ $this->webhook_endpoint = config('app.url');
+ } else {
+ $this->webhook_endpoint = $this->ipv4;
+ $this->is_system_wide = $this->github_app->is_system_wide;
+ }
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
}
}
diff --git a/app/Livewire/Source/Github/Create.php b/app/Livewire/Source/Github/Create.php
index f85e8646e..136d3525e 100644
--- a/app/Livewire/Source/Github/Create.php
+++ b/app/Livewire/Source/Github/Create.php
@@ -23,7 +23,7 @@ class Create extends Component
public function mount()
{
- $this->name = generate_random_name();
+ $this->name = substr(generate_random_name(), 0, 30);
}
public function createGitHubApp()
diff --git a/app/Livewire/Storage/Create.php b/app/Livewire/Storage/Create.php
index a05834ecc..c5250e1e3 100644
--- a/app/Livewire/Storage/Create.php
+++ b/app/Livewire/Storage/Create.php
@@ -43,15 +43,17 @@ class Create extends Component
'endpoint' => 'Endpoint',
];
- public function mount()
+ public function updatedEndpoint($value)
{
- if (isDev()) {
- $this->name = 'Local MinIO';
- $this->description = 'Local MinIO';
- $this->key = 'minioadmin';
- $this->secret = 'minioadmin';
- $this->bucket = 'local';
- $this->endpoint = 'http://coolify-minio:9000';
+ if (! str($value)->startsWith('https://') && ! str($value)->startsWith('http://')) {
+ $this->endpoint = 'https://'.$value;
+ $value = $this->endpoint;
+ }
+
+ if (str($value)->contains('your-objectstorage.com') && ! isset($this->bucket)) {
+ $this->bucket = str($value)->after('//')->before('.');
+ } elseif (str($value)->contains('your-objectstorage.com')) {
+ $this->bucket = $this->bucket ?: str($value)->after('//')->before('.');
}
}
diff --git a/app/Livewire/Subscription/Index.php b/app/Livewire/Subscription/Index.php
index c278bf58e..df450cf7e 100644
--- a/app/Livewire/Subscription/Index.php
+++ b/app/Livewire/Subscription/Index.php
@@ -23,7 +23,7 @@ class Index extends Component
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
return redirect()->route('subscription.show');
}
- $this->settings = \App\Models\InstanceSettings::get();
+ $this->settings = instanceSettings();
$this->alreadySubscribed = currentTeam()->subscription()->exists();
}
diff --git a/app/Livewire/Subscription/PricingPlans.php b/app/Livewire/Subscription/PricingPlans.php
index 9bc11d862..6b2d3fb36 100644
--- a/app/Livewire/Subscription/PricingPlans.php
+++ b/app/Livewire/Subscription/PricingPlans.php
@@ -2,55 +2,23 @@
namespace App\Livewire\Subscription;
+use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use Stripe\Checkout\Session;
use Stripe\Stripe;
class PricingPlans extends Component
{
- public bool $isTrial = false;
-
- public function mount()
- {
- $this->isTrial = ! data_get(currentTeam(), 'subscription.stripe_trial_already_ended');
- if (config('constants.limits.trial_period') == 0) {
- $this->isTrial = false;
- }
- }
-
public function subscribeStripe($type)
{
- $team = currentTeam();
Stripe::setApiKey(config('subscription.stripe_api_key'));
- switch ($type) {
- case 'basic-monthly':
- $priceId = config('subscription.stripe_price_id_basic_monthly');
- break;
- case 'basic-yearly':
- $priceId = config('subscription.stripe_price_id_basic_yearly');
- break;
- case 'pro-monthly':
- $priceId = config('subscription.stripe_price_id_pro_monthly');
- break;
- case 'pro-yearly':
- $priceId = config('subscription.stripe_price_id_pro_yearly');
- break;
- case 'ultimate-monthly':
- $priceId = config('subscription.stripe_price_id_ultimate_monthly');
- break;
- case 'ultimate-yearly':
- $priceId = config('subscription.stripe_price_id_ultimate_yearly');
- break;
- case 'dynamic-monthly':
- $priceId = config('subscription.stripe_price_id_dynamic_monthly');
- break;
- case 'dynamic-yearly':
- $priceId = config('subscription.stripe_price_id_dynamic_yearly');
- break;
- default:
- $priceId = config('subscription.stripe_price_id_basic_monthly');
- break;
- }
+
+ $priceId = match ($type) {
+ 'dynamic-monthly' => config('subscription.stripe_price_id_dynamic_monthly'),
+ 'dynamic-yearly' => config('subscription.stripe_price_id_dynamic_yearly'),
+ default => config('subscription.stripe_price_id_dynamic_monthly'),
+ };
+
if (! $priceId) {
$this->dispatch('error', 'Price ID not found! Please contact the administrator.');
@@ -59,10 +27,14 @@ class PricingPlans extends Component
$payload = [
'allow_promotion_codes' => true,
'billing_address_collection' => 'required',
- 'client_reference_id' => auth()->user()->id.':'.currentTeam()->id,
+ 'client_reference_id' => Auth::id().':'.currentTeam()->id,
'line_items' => [[
'price' => $priceId,
- 'quantity' => 1,
+ 'adjustable_quantity' => [
+ 'enabled' => true,
+ 'minimum' => 2,
+ ],
+ 'quantity' => 2,
]],
'tax_id_collection' => [
'enabled' => true,
@@ -70,39 +42,18 @@ class PricingPlans extends Component
'automatic_tax' => [
'enabled' => true,
],
-
+ 'subscription_data' => [
+ 'metadata' => [
+ 'user_id' => Auth::id(),
+ 'team_id' => currentTeam()->id,
+ ],
+ ],
+ 'payment_method_collection' => 'if_required',
'mode' => 'subscription',
'success_url' => route('dashboard', ['success' => true]),
'cancel_url' => route('subscription.index', ['cancelled' => true]),
];
- if (str($type)->contains('ultimate')) {
- $payload['line_items'][0]['adjustable_quantity'] = [
- 'enabled' => true,
- 'minimum' => 10,
- ];
- $payload['line_items'][0]['quantity'] = 10;
- }
- if (str($type)->contains('dynamic')) {
- $payload['line_items'][0]['adjustable_quantity'] = [
- 'enabled' => true,
- 'minimum' => 2,
- ];
- $payload['line_items'][0]['quantity'] = 2;
- }
- if (! data_get($team, 'subscription.stripe_trial_already_ended')) {
- if (config('constants.limits.trial_period') > 0) {
- $payload['subscription_data'] = [
- 'trial_period_days' => config('constants.limits.trial_period'),
- 'trial_settings' => [
- 'end_behavior' => [
- 'missing_payment_method' => 'cancel',
- ],
- ],
- ];
- }
- $payload['payment_method_collection'] = 'if_required';
- }
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
if ($customer) {
$payload['customer'] = $customer;
@@ -110,7 +61,7 @@ class PricingPlans extends Component
'name' => 'auto',
];
} else {
- $payload['customer_email'] = auth()->user()->email;
+ $payload['customer_email'] = Auth::user()->email;
}
$session = Session::create($payload);
diff --git a/app/Livewire/Tags/Deployments.php b/app/Livewire/Tags/Deployments.php
index 270aa176a..e4afa5b60 100644
--- a/app/Livewire/Tags/Deployments.php
+++ b/app/Livewire/Tags/Deployments.php
@@ -7,19 +7,19 @@ use Livewire\Component;
class Deployments extends Component
{
- public $deployments_per_tag_per_server = [];
+ public $deploymentsPerTagPerServer = [];
- public $resource_ids = [];
+ public $resourceIds = [];
public function render()
{
return view('livewire.tags.deployments');
}
- public function get_deployments()
+ public function getDeployments()
{
try {
- $this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('application_id', $this->resource_ids)->get([
+ $this->deploymentsPerTagPerServer = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('application_id', $this->resourceIds)->get([
'id',
'application_id',
'application_name',
@@ -29,7 +29,7 @@ class Deployments extends Component
'server_id',
'status',
])->sortBy('id')->groupBy('server_name')->toArray();
- $this->dispatch('deployments', $this->deployments_per_tag_per_server);
+ $this->dispatch('deployments', $this->deploymentsPerTagPerServer);
} catch (\Exception $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Tags/Index.php b/app/Livewire/Tags/Index.php
deleted file mode 100644
index a01d00a70..000000000
--- a/app/Livewire/Tags/Index.php
+++ /dev/null
@@ -1,79 +0,0 @@
- 'update_deployments'];
-
- public function update_deployments($deployments)
- {
- $this->deployments_per_tag_per_server = $deployments;
- }
-
- public function tag_updated()
- {
- if ($this->tag == '') {
- return;
- }
- $tag = $this->tags->where('name', $this->tag)->first();
- if (! $tag) {
- $this->dispatch('error', "Tag ({$this->tag}) not found.");
- $this->tag = '';
-
- return;
- }
- $this->webhook = generatTagDeployWebhook($tag->name);
- $this->applications = $tag->applications()->get();
- $this->services = $tag->services()->get();
- }
-
- public function redeploy_all()
- {
- try {
- $this->applications->each(function ($resource) {
- $deploy = new DeployController;
- $deploy->deploy_resource($resource);
- });
- $this->services->each(function ($resource) {
- $deploy = new DeployController;
- $deploy->deploy_resource($resource);
- });
- $this->dispatch('success', 'Mass deployment started.');
- } catch (\Exception $e) {
- return handleError($e, $this);
- }
- }
-
- public function mount()
- {
- $this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
- if ($this->tag) {
- $this->tag_updated();
- }
- }
-
- public function render()
- {
- return view('livewire.tags.index');
- }
-}
diff --git a/app/Livewire/Tags/Show.php b/app/Livewire/Tags/Show.php
index 668101edb..fc5b13374 100644
--- a/app/Livewire/Tags/Show.php
+++ b/app/Livewire/Tags/Show.php
@@ -5,41 +5,57 @@ namespace App\Livewire\Tags;
use App\Http\Controllers\Api\DeployController;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Tag;
+use Illuminate\Support\Collection;
+use Livewire\Attributes\Locked;
+use Livewire\Attributes\Title;
use Livewire\Component;
+#[Title('Tags | Coolify')]
class Show extends Component
{
- public $tags;
+ #[Locked]
+ public ?string $tagName = null;
- public Tag $tag;
+ #[Locked]
+ public ?Collection $tags = null;
- public $applications;
+ #[Locked]
+ public ?Tag $tag = null;
- public $services;
+ #[Locked]
+ public ?Collection $applications = null;
- public $webhook = null;
+ #[Locked]
+ public ?Collection $services = null;
- public $deployments_per_tag_per_server = [];
+ #[Locked]
+ public ?string $webhook = null;
+
+ #[Locked]
+ public ?array $deploymentsPerTagPerServer = null;
public function mount()
{
- $this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
- $tag = $this->tags->where('name', request()->tag_name)->first();
- if (! $tag) {
- return redirect()->route('tags.index');
+ try {
+ $this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
+ if (str($this->tagName)->isNotEmpty()) {
+ $tag = $this->tags->where('name', $this->tagName)->first();
+ $this->webhook = generateTagDeployWebhook($tag->name);
+ $this->applications = $tag->applications()->get();
+ $this->services = $tag->services()->get();
+ $this->tag = $tag;
+ $this->getDeployments();
+ }
+ } catch (\Exception $e) {
+ return handleError($e, $this);
}
- $this->webhook = generatTagDeployWebhook($tag->name);
- $this->applications = $tag->applications()->get();
- $this->services = $tag->services()->get();
- $this->tag = $tag;
- $this->get_deployments();
}
- public function get_deployments()
+ public function getDeployments()
{
try {
$resource_ids = $this->applications->pluck('id');
- $this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('application_id', $resource_ids)->get([
+ $this->deploymentsPerTagPerServer = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('application_id', $resource_ids)->get([
'id',
'application_id',
'application_name',
@@ -54,7 +70,7 @@ class Show extends Component
}
}
- public function redeploy_all()
+ public function redeployAll()
{
try {
$message = collect([]);
diff --git a/app/Livewire/Team/AdminView.php b/app/Livewire/Team/AdminView.php
index 97d4fcdbf..cfb47d9d8 100644
--- a/app/Livewire/Team/AdminView.php
+++ b/app/Livewire/Team/AdminView.php
@@ -2,8 +2,11 @@
namespace App\Livewire\Team;
+use App\Models\InstanceSettings;
use App\Models\Team;
use App\Models\User;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class AdminView extends Component
@@ -56,54 +59,54 @@ class AdminView extends Component
foreach ($servers as $server) {
$resources = $server->definedResources();
foreach ($resources as $resource) {
- ray('Deleting resource: '.$resource->name);
$resource->forceDelete();
}
- ray('Deleting server: '.$server->name);
$server->forceDelete();
}
$projects = $team->projects;
foreach ($projects as $project) {
- ray('Deleting project: '.$project->name);
$project->forceDelete();
}
$team->members()->detach($user->id);
- ray('Deleting team: '.$team->name);
$team->delete();
}
- public function delete($id)
+ public function delete($id, $password)
{
+ if (! isInstanceAdmin()) {
+ return redirect()->route('dashboard');
+ }
+ if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
+ if (! Hash::check($password, Auth::user()->password)) {
+ $this->addError('password', 'The provided password is incorrect.');
+
+ return;
+ }
+ }
if (! auth()->user()->isInstanceAdmin()) {
return $this->dispatch('error', 'You are not authorized to delete users');
}
$user = User::find($id);
$teams = $user->teams;
foreach ($teams as $team) {
- ray($team->name);
$user_alone_in_team = $team->members->count() === 1;
if ($team->id === 0) {
if ($user_alone_in_team) {
- ray('user is alone in the root team, do nothing');
-
return $this->dispatch('error', 'User is alone in the root team, cannot delete');
}
}
if ($user_alone_in_team) {
- ray('user is alone in the team');
$this->finalizeDeletion($user, $team);
continue;
}
- ray('user is not alone in the team');
if ($user->isOwner()) {
$found_other_owner_or_admin = $team->members->filter(function ($member) {
return $member->pivot->role === 'owner' || $member->pivot->role === 'admin';
})->where('id', '!=', $user->id)->first();
if ($found_other_owner_or_admin) {
- ray('found other owner or admin');
$team->members()->detach($user->id);
continue;
@@ -112,24 +115,19 @@ class AdminView extends Component
return $member->pivot->role === 'member';
})->first();
if ($found_other_member_who_is_not_owner) {
- ray('found other member who is not owner');
$found_other_member_who_is_not_owner->pivot->role = 'owner';
$found_other_member_who_is_not_owner->pivot->save();
$team->members()->detach($user->id);
} else {
- // This should never happen as if the user is the only member in the team, the team should be deleted already.
- ray('found no other member who is not owner');
$this->finalizeDeletion($user, $team);
}
continue;
}
} else {
- ray('user is not owner');
$team->members()->detach($user->id);
}
}
- ray('Deleting user: '.$user->name);
$user->delete();
$this->getUsers();
}
diff --git a/app/Livewire/Team/Create.php b/app/Livewire/Team/Create.php
index 992833da5..f805d6122 100644
--- a/app/Livewire/Team/Create.php
+++ b/app/Livewire/Team/Create.php
@@ -3,28 +3,21 @@
namespace App\Livewire\Team;
use App\Models\Team;
+use Livewire\Attributes\Validate;
use Livewire\Component;
class Create extends Component
{
+ #[Validate(['required', 'min:3', 'max:255'])]
public string $name = '';
+ #[Validate(['nullable', 'min:3', 'max:255'])]
public ?string $description = null;
- protected $rules = [
- 'name' => 'required|min:3|max:255',
- 'description' => 'nullable|min:3|max:255',
- ];
-
- protected $validationAttributes = [
- 'name' => 'name',
- 'description' => 'description',
- ];
-
public function submit()
{
- $this->validate();
try {
+ $this->validate();
$team = Team::create([
'name' => $this->name,
'description' => $this->description,
diff --git a/app/Livewire/Team/Index.php b/app/Livewire/Team/Index.php
index 45600dbfe..0972e7364 100644
--- a/app/Livewire/Team/Index.php
+++ b/app/Livewire/Team/Index.php
@@ -4,6 +4,7 @@ namespace App\Livewire\Team;
use App\Models\Team;
use App\Models\TeamInvitation;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
@@ -55,7 +56,7 @@ class Index extends Component
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) {
- if ($user->id === auth()->user()->id) {
+ if ($user->id === Auth::id()) {
return;
}
$user->teams()->detach($currentTeam);
diff --git a/app/Livewire/Team/Invitations.php b/app/Livewire/Team/Invitations.php
index 6a32a1d16..93432efc8 100644
--- a/app/Livewire/Team/Invitations.php
+++ b/app/Livewire/Team/Invitations.php
@@ -13,17 +13,18 @@ class Invitations extends Component
public function deleteInvitation(int $invitation_id)
{
- $initiation_found = TeamInvitation::find($invitation_id);
- if (! $initiation_found) {
+ try {
+ $initiation_found = TeamInvitation::ownedByCurrentTeam()->findOrFail($invitation_id);
+ $initiation_found->delete();
+ $this->refreshInvitations();
+ $this->dispatch('success', 'Invitation revoked.');
+ } catch (\Exception) {
return $this->dispatch('error', 'Invitation not found.');
}
- $initiation_found->delete();
- $this->refreshInvitations();
- $this->dispatch('success', 'Invitation revoked.');
}
public function refreshInvitations()
{
- $this->invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
+ $this->invitations = TeamInvitation::ownedByCurrentTeam()->get();
}
}
diff --git a/app/Livewire/Team/InviteLink.php b/app/Livewire/Team/InviteLink.php
index 6c9e405fc..25f8a1ff5 100644
--- a/app/Livewire/Team/InviteLink.php
+++ b/app/Livewire/Team/InviteLink.php
@@ -41,6 +41,9 @@ class InviteLink extends Component
{
try {
$this->validate();
+ if (auth()->user()->role() === 'admin' && $this->role === 'owner') {
+ throw new \Exception('Admins cannot invite owners.');
+ }
$member_emails = currentTeam()->members()->get()->pluck('email');
if ($member_emails->contains($this->email)) {
return handleError(livewire: $this, customErrorMessage: "$this->email is already a member of ".currentTeam()->name.'.');
diff --git a/app/Livewire/Team/Member.php b/app/Livewire/Team/Member.php
index 680cb901b..890d640a0 100644
--- a/app/Livewire/Team/Member.php
+++ b/app/Livewire/Team/Member.php
@@ -2,6 +2,7 @@
namespace App\Livewire\Team;
+use App\Enums\Role;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
@@ -12,29 +13,66 @@ class Member extends Component
public function makeAdmin()
{
- $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'admin']);
- $this->dispatch('reloadWindow');
+ try {
+ if (Role::from(auth()->user()->role())->lt(Role::ADMIN)
+ || Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
+ throw new \Exception('You are not authorized to perform this action.');
+ }
+ $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => Role::ADMIN->value]);
+ $this->dispatch('reloadWindow');
+ } catch (\Exception $e) {
+ $this->dispatch('error', $e->getMessage());
+ }
}
public function makeOwner()
{
- $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'owner']);
- $this->dispatch('reloadWindow');
+ try {
+ if (Role::from(auth()->user()->role())->lt(Role::OWNER)
+ || Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
+ throw new \Exception('You are not authorized to perform this action.');
+ }
+ $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => Role::OWNER->value]);
+ $this->dispatch('reloadWindow');
+ } catch (\Exception $e) {
+ $this->dispatch('error', $e->getMessage());
+ }
}
public function makeReadonly()
{
- $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => 'member']);
- $this->dispatch('reloadWindow');
+ try {
+ if (Role::from(auth()->user()->role())->lt(Role::ADMIN)
+ || Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
+ throw new \Exception('You are not authorized to perform this action.');
+ }
+ $this->member->teams()->updateExistingPivot(currentTeam()->id, ['role' => Role::MEMBER->value]);
+ $this->dispatch('reloadWindow');
+ } catch (\Exception $e) {
+ $this->dispatch('error', $e->getMessage());
+ }
}
public function remove()
{
- $this->member->teams()->detach(currentTeam());
- Cache::forget("team:{$this->member->id}");
- Cache::remember('team:'.$this->member->id, 3600, function () {
- return $this->member->teams()->first();
- });
- $this->dispatch('reloadWindow');
+ try {
+ if (Role::from(auth()->user()->role())->lt(Role::ADMIN)
+ || Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
+ throw new \Exception('You are not authorized to perform this action.');
+ }
+ $this->member->teams()->detach(currentTeam());
+ Cache::forget("team:{$this->member->id}");
+ Cache::remember('team:'.$this->member->id, 3600, function () {
+ return $this->member->teams()->first();
+ });
+ $this->dispatch('reloadWindow');
+ } catch (\Exception $e) {
+ $this->dispatch('error', $e->getMessage());
+ }
+ }
+
+ private function getMemberRole()
+ {
+ return $this->member->teams()->where('teams.id', currentTeam()->id)->first()?->pivot?->role;
}
}
diff --git a/app/Livewire/Terminal/Index.php b/app/Livewire/Terminal/Index.php
index 945b25714..a24a237c5 100644
--- a/app/Livewire/Terminal/Index.php
+++ b/app/Livewire/Terminal/Index.php
@@ -14,13 +14,25 @@ class Index extends Component
public $containers = [];
+ public bool $isLoadingContainers = true;
+
public function mount()
{
if (! auth()->user()->isAdmin()) {
abort(403);
}
$this->servers = Server::isReachable()->get();
- $this->containers = $this->getAllActiveContainers();
+ }
+
+ public function loadContainers()
+ {
+ try {
+ $this->containers = $this->getAllActiveContainers();
+ } catch (\Exception $e) {
+ return handleError($e, $this);
+ } finally {
+ $this->isLoadingContainers = false;
+ }
}
private function getAllActiveContainers()
diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php
index dfbd945f5..e50085c64 100644
--- a/app/Livewire/Upgrade.php
+++ b/app/Livewire/Upgrade.php
@@ -23,11 +23,12 @@ class Upgrade extends Component
try {
$this->latestVersion = get_latest_version_of_coolify();
$this->isUpgradeAvailable = data_get(InstanceSettings::get(), 'new_version_available', false);
-
+ if (isDev()) {
+ $this->isUpgradeAvailable = true;
+ }
} catch (\Throwable $e) {
return handleError($e, $this);
}
-
}
public function upgrade()
diff --git a/app/Livewire/VerifyEmail.php b/app/Livewire/VerifyEmail.php
index d1f79c835..fab3265b6 100644
--- a/app/Livewire/VerifyEmail.php
+++ b/app/Livewire/VerifyEmail.php
@@ -15,10 +15,7 @@ class VerifyEmail extends Component
$this->rateLimit(1, 300);
auth()->user()->sendVerificationEmail();
$this->dispatch('success', 'Email verification link sent!');
-
} catch (\Exception $e) {
- ray($e);
-
return handleError($e, $this);
}
}
diff --git a/app/Livewire/Waitlist/Index.php b/app/Livewire/Waitlist/Index.php
index 422415449..0524b495c 100644
--- a/app/Livewire/Waitlist/Index.php
+++ b/app/Livewire/Waitlist/Index.php
@@ -27,7 +27,7 @@ class Index extends Component
public function mount()
{
- if (config('coolify.waitlist') == false) {
+ if (config('constants.waitlist.enabled') == false) {
return redirect()->route('register');
}
$this->waitingInLine = Waitlist::whereVerified(true)->count();
diff --git a/app/Models/Application.php b/app/Models/Application.php
index d0cc34a06..c284528f1 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -6,7 +6,10 @@ use App\Enums\ApplicationDeploymentStatus;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Process\InvokedProcess;
use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Process;
+use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use OpenApi\Attributes as OA;
use RuntimeException;
@@ -95,6 +98,7 @@ use Visus\Cuid2\Cuid2;
'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was last updated.'],
'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'],
'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'],
+ 'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'],
]
)]
@@ -102,7 +106,7 @@ class Application extends BaseModel
{
use SoftDeletes;
- private static $parserVersion = '3';
+ private static $parserVersion = '4';
protected $guarded = [];
@@ -111,17 +115,39 @@ class Application extends BaseModel
protected static function booted()
{
static::saving(function ($application) {
- if ($application->fqdn == '') {
- $application->fqdn = null;
+ $payload = [];
+ if ($application->isDirty('fqdn')) {
+ if ($application->fqdn === '') {
+ $application->fqdn = null;
+ }
+ $payload['fqdn'] = $application->fqdn;
+ }
+ if ($application->isDirty('install_command')) {
+ $payload['install_command'] = str($application->install_command)->trim();
+ }
+ if ($application->isDirty('build_command')) {
+ $payload['build_command'] = str($application->build_command)->trim();
+ }
+ if ($application->isDirty('start_command')) {
+ $payload['start_command'] = str($application->start_command)->trim();
+ }
+ if ($application->isDirty('base_directory')) {
+ $payload['base_directory'] = str($application->base_directory)->trim();
+ }
+ if ($application->isDirty('publish_directory')) {
+ $payload['publish_directory'] = str($application->publish_directory)->trim();
+ }
+ if ($application->isDirty('status')) {
+ $payload['last_online_at'] = now();
+ }
+ if ($application->isDirty('custom_nginx_configuration')) {
+ if ($application->custom_nginx_configuration === '') {
+ $payload['custom_nginx_configuration'] = null;
+ }
+ }
+ if (count($payload) > 0) {
+ $application->forceFill($payload);
}
- $application->forceFill([
- 'fqdn' => $application->fqdn,
- 'install_command' => str($application->install_command)->trim(),
- 'build_command' => str($application->build_command)->trim(),
- 'start_command' => str($application->start_command)->trim(),
- 'base_directory' => str($application->base_directory)->trim(),
- 'publish_directory' => str($application->publish_directory)->trim(),
- ]);
});
static::created(function ($application) {
ApplicationSetting::create([
@@ -141,6 +167,9 @@ class Application extends BaseModel
}
$application->tags()->detach();
$application->previews()->delete();
+ foreach ($application->deployment_queue as $deployment) {
+ $deployment->delete();
+ }
});
}
@@ -149,12 +178,69 @@ class Application extends BaseModel
return Application::whereRelation('environment.project.team', 'id', $teamId)->orderBy('name');
}
+ public static function ownedByCurrentTeam()
+ {
+ return Application::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
+ }
+
+ public function getContainersToStop(bool $previewDeployments = false): array
+ {
+ $containers = $previewDeployments
+ ? getCurrentApplicationContainerStatus($this->destination->server, $this->id, includePullrequests: true)
+ : getCurrentApplicationContainerStatus($this->destination->server, $this->id, 0);
+
+ return $containers->pluck('Names')->toArray();
+ }
+
+ public function stopContainers(array $containerNames, $server, int $timeout = 600)
+ {
+ $processes = [];
+ foreach ($containerNames as $containerName) {
+ $processes[$containerName] = $this->stopContainer($containerName, $server, $timeout);
+ }
+
+ $startTime = time();
+ while (count($processes) > 0) {
+ $finishedProcesses = array_filter($processes, function ($process) {
+ return ! $process->running();
+ });
+ foreach ($finishedProcesses as $containerName => $process) {
+ unset($processes[$containerName]);
+ $this->removeContainer($containerName, $server);
+ }
+
+ if (time() - $startTime >= $timeout) {
+ $this->forceStopRemainingContainers(array_keys($processes), $server);
+ break;
+ }
+
+ usleep(100000);
+ }
+ }
+
+ public function stopContainer(string $containerName, $server, int $timeout): InvokedProcess
+ {
+ return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
+ }
+
+ public function removeContainer(string $containerName, $server)
+ {
+ instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
+ }
+
+ public function forceStopRemainingContainers(array $containerNames, $server)
+ {
+ foreach ($containerNames as $containerName) {
+ instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
+ $this->removeContainer($containerName, $server);
+ }
+ }
+
public function delete_configurations()
{
$server = data_get($this, 'destination.server');
$workdir = $this->workdir();
if (str($workdir)->endsWith($this->uuid)) {
- ray('Deleting workdir');
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
}
}
@@ -163,7 +249,6 @@ class Application extends BaseModel
{
if ($this->build_pack === 'dockercompose') {
$server = data_get($this, 'destination.server');
- ray('Deleting volumes');
instant_remote_process(["cd {$this->dirOnServer()} && docker compose down -v"], $server, false);
} else {
if ($persistentStorages->count() === 0) {
@@ -176,6 +261,13 @@ class Application extends BaseModel
}
}
+ public function delete_connected_networks($uuid)
+ {
+ $server = data_get($this, 'destination.server');
+ instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
+ instant_remote_process(["docker network rm {$uuid}"], $server, false);
+ }
+
public function additional_servers()
{
return $this->belongsToMany(Server::class, 'additional_destinations')
@@ -243,7 +335,7 @@ class Application extends BaseModel
'application_uuid' => data_get($this, 'uuid'),
'task_uuid' => $task_uuid,
]);
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
if (data_get($settings, 'fqdn')) {
$url = Url::fromString($route);
$url = $url->withPort(null);
@@ -546,6 +638,14 @@ class Application extends BaseModel
);
}
+ public function customNginxConfiguration(): Attribute
+ {
+ return Attribute::make(
+ set: fn ($value) => base64_encode($value),
+ get: fn ($value) => base64_decode($value),
+ );
+ }
+
public function portsExposesArray(): Attribute
{
return Attribute::make(
@@ -649,6 +749,11 @@ class Application extends BaseModel
return $this->hasMany(ApplicationPreview::class);
}
+ public function deployment_queue()
+ {
+ return $this->hasMany(ApplicationDeploymentQueue::class);
+ }
+
public function destination()
{
return $this->morphTo();
@@ -771,7 +876,7 @@ class Application extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
- $newConfigHash = $this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect;
+ $newConfigHash = base64_encode($this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect.$this->custom_nginx_configuration);
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
} else {
@@ -801,21 +906,7 @@ class Application extends BaseModel
public function customRepository()
{
- preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches);
- $port = 22;
- if (count($matches) === 1) {
- $port = $matches[0];
- $gitHost = str($this->git_repository)->before(':');
- $gitRepo = str($this->git_repository)->after('/');
- $repository = "$gitHost:$gitRepo";
- } else {
- $repository = $this->git_repository;
- }
-
- return [
- 'repository' => $repository,
- 'port' => $port,
- ];
+ return convertGitUrl($this->git_repository, $this->deploymentType(), $this->source);
}
public function generateBaseDir(string $uuid)
@@ -848,6 +939,122 @@ class Application extends BaseModel
return $git_clone_command;
}
+ public function getGitRemoteStatus(string $deployment_uuid)
+ {
+ try {
+ ['commands' => $lsRemoteCommand] = $this->generateGitLsRemoteCommands(deployment_uuid: $deployment_uuid, exec_in_docker: false);
+ instant_remote_process([$lsRemoteCommand], $this->destination->server, true);
+
+ return [
+ 'is_accessible' => true,
+ 'error' => null,
+ ];
+ } catch (\RuntimeException $ex) {
+ return [
+ 'is_accessible' => false,
+ 'error' => $ex->getMessage(),
+ ];
+ }
+ }
+
+ public function generateGitLsRemoteCommands(string $deployment_uuid, bool $exec_in_docker = true)
+ {
+ $branch = $this->git_branch;
+ ['repository' => $customRepository, 'port' => $customPort] = $this->customRepository();
+ $commands = collect([]);
+ $base_command = 'git ls-remote';
+
+ if ($this->deploymentType() === 'source') {
+ $source_html_url = data_get($this, 'source.html_url');
+ $url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL));
+ $source_html_url_host = $url['host'];
+ $source_html_url_scheme = $url['scheme'];
+
+ if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
+ if ($this->source->is_public) {
+ $fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
+ $base_command = "{$base_command} {$this->source->html_url}/{$customRepository}";
+ } else {
+ $github_access_token = generate_github_installation_token($this->source);
+
+ if ($exec_in_docker) {
+ $base_command = "{$base_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git";
+ $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git";
+ } else {
+ $base_command = "{$base_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
+ $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
+ }
+ }
+
+ if ($exec_in_docker) {
+ $commands->push(executeInDocker($deployment_uuid, $base_command));
+ } else {
+ $commands->push($base_command);
+ }
+
+ return [
+ 'commands' => $commands->implode(' && '),
+ 'branch' => $branch,
+ 'fullRepoUrl' => $fullRepoUrl,
+ ];
+ }
+ }
+
+ if ($this->deploymentType() === 'deploy_key') {
+ $fullRepoUrl = $customRepository;
+ $private_key = data_get($this, 'private_key.private_key');
+ if (is_null($private_key)) {
+ throw new RuntimeException('Private key not found. Please add a private key to the application and try again.');
+ }
+ $private_key = base64_encode($private_key);
+ $base_comamnd = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$base_command} {$customRepository}";
+
+ if ($exec_in_docker) {
+ $commands = collect([
+ executeInDocker($deployment_uuid, 'mkdir -p /root/.ssh'),
+ executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"),
+ executeInDocker($deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'),
+ ]);
+ } else {
+ $commands = collect([
+ 'mkdir -p /root/.ssh',
+ "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null",
+ 'chmod 600 /root/.ssh/id_rsa',
+ ]);
+ }
+
+ if ($exec_in_docker) {
+ $commands->push(executeInDocker($deployment_uuid, $base_comamnd));
+ } else {
+ $commands->push($base_comamnd);
+ }
+
+ return [
+ 'commands' => $commands->implode(' && '),
+ 'branch' => $branch,
+ 'fullRepoUrl' => $fullRepoUrl,
+ ];
+ }
+
+ if ($this->deploymentType() === 'other') {
+ $fullRepoUrl = $customRepository;
+ $base_command = "{$base_command} {$customRepository}";
+ $base_command = $this->setGitImportSettings($deployment_uuid, $base_command, public: true);
+
+ if ($exec_in_docker) {
+ $commands->push(executeInDocker($deployment_uuid, $base_command));
+ } else {
+ $commands->push($base_command);
+ }
+
+ return [
+ 'commands' => $commands->implode(' && '),
+ 'branch' => $branch,
+ 'fullRepoUrl' => $fullRepoUrl,
+ ];
+ }
+ }
+
public function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null)
{
$branch = $this->git_branch;
@@ -867,7 +1074,7 @@ class Application extends BaseModel
$source_html_url_host = $url['host'];
$source_html_url_scheme = $url['scheme'];
- if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
+ if ($this->source->getMorphClass() === \App\Models\GithubApp::class) {
if ($this->source->is_public) {
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$customRepository} {$baseDir}";
@@ -1034,6 +1241,7 @@ class Application extends BaseModel
throw new \Exception($e->getMessage());
}
$services = data_get($yaml, 'services');
+
$commands = collect([]);
$services = collect($services)->map(function ($service) use ($commands) {
$serviceVolumes = collect(data_get($service, 'volumes', []));
@@ -1088,7 +1296,7 @@ class Application extends BaseModel
public function parse(int $pull_request_id = 0, ?int $preview_id = null)
{
- if ($this->compose_parsing_version === '3') {
+ if ((int) $this->compose_parsing_version >= 3) {
return newParser($this, $pull_request_id, $preview_id);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
@@ -1108,6 +1316,11 @@ class Application extends BaseModel
$workdir = rtrim($this->base_directory, '/');
$composeFile = $this->docker_compose_location;
$fileList = collect([".$workdir$composeFile"]);
+ $gitRemoteStatus = $this->getGitRemoteStatus(deployment_uuid: $uuid);
+ if (! $gitRemoteStatus['is_accessible']) {
+ throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}");
+ }
+
$commands = collect([
"rm -rf /tmp/{$uuid}",
"mkdir -p /tmp/{$uuid}",
@@ -1166,7 +1379,6 @@ class Application extends BaseModel
} else {
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile
Check if you used the right extension (.yaml or .yml) in the compose file name.");
}
-
}
public function parseContainerLabels(?ApplicationPreview $preview = null)
@@ -1176,13 +1388,11 @@ class Application extends BaseModel
return;
}
if (base64_encode(base64_decode($customLabels, true)) !== $customLabels) {
- ray('custom_labels is not base64 encoded');
$this->custom_labels = str($customLabels)->replace(',', "\n");
$this->custom_labels = base64_encode($customLabels);
}
$customLabels = base64_decode($this->custom_labels);
if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
- ray('custom_labels contains non-ascii characters');
$customLabels = str(implode('|coolify|', generateLabelsApplication($this, $preview)))->replace('|coolify|', "\n");
}
$this->custom_labels = base64_encode($customLabels);
@@ -1330,32 +1540,114 @@ class Application extends BaseModel
return [];
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
+ if ($error === 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
});
return $parsedCollection->toArray();
}
}
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+ }
+
+ public function generateConfig($is_json = false)
+ {
+ $config = collect([]);
+ if ($this->build_pack = 'nixpacks') {
+ $config = collect([
+ 'build_pack' => 'nixpacks',
+ 'docker_registry_image_name' => $this->docker_registry_image_name,
+ 'docker_registry_image_tag' => $this->docker_registry_image_tag,
+ 'install_command' => $this->install_command,
+ 'build_command' => $this->build_command,
+ 'start_command' => $this->start_command,
+ 'base_directory' => $this->base_directory,
+ 'publish_directory' => $this->publish_directory,
+ 'custom_docker_run_options' => $this->custom_docker_run_options,
+ 'ports_exposes' => $this->ports_exposes,
+ 'ports_mappings' => $this->ports_mapping,
+ 'settings' => collect([
+ 'is_static' => $this->settings->is_static,
+ ]),
+ ]);
+ }
+ $config = $config->filter(function ($value) {
+ return str($value)->isNotEmpty();
+ });
+ if ($is_json) {
+ return json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ }
+
+ return $config;
+ }
+
+ public function setConfig($config)
+ {
+ $validator = Validator::make(['config' => $config], [
+ 'config' => 'required|json',
+ ]);
+ if ($validator->fails()) {
+ throw new \Exception('Invalid JSON format');
+ }
+ $config = json_decode($config, true);
+
+ $deepValidator = Validator::make(['config' => $config], [
+ 'config.build_pack' => 'required|string',
+ 'config.base_directory' => 'required|string',
+ 'config.publish_directory' => 'required|string',
+ 'config.ports_exposes' => 'required|string',
+ 'config.settings.is_static' => 'required|boolean',
+ ]);
+ if ($deepValidator->fails()) {
+ throw new \Exception('Invalid data');
+ }
+ $config = $deepValidator->validated()['config'];
+
+ try {
+ $settings = data_get($config, 'settings', []);
+ data_forget($config, 'settings');
+ $this->update($config);
+ $this->settings()->update($settings);
+ } catch (\Exception $e) {
+ throw new \Exception('Failed to update application settings');
+ }
+ }
}
diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php
index 90d7608cc..c261c30c6 100644
--- a/app/Models/ApplicationDeploymentQueue.php
+++ b/app/Models/ApplicationDeploymentQueue.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use OpenApi\Attributes as OA;
@@ -39,6 +40,20 @@ class ApplicationDeploymentQueue extends Model
{
protected $guarded = [];
+ public function application(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => Application::find($this->application_id),
+ );
+ }
+
+ public function server(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => Server::find($this->server_id),
+ );
+ }
+
public function setStatus(string $status)
{
$this->update([
diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php
index 04a0ab27e..bf2bf05bf 100644
--- a/app/Models/ApplicationPreview.php
+++ b/app/Models/ApplicationPreview.php
@@ -28,6 +28,11 @@ class ApplicationPreview extends BaseModel
});
}
});
+ static::saving(function ($preview) {
+ if ($preview->isDirty('status')) {
+ $preview->forceFill(['last_online_at' => now()]);
+ }
+ });
}
public static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id)
diff --git a/app/Models/Environment.php b/app/Models/Environment.php
index c892d7ba1..71e8bbd21 100644
--- a/app/Models/Environment.php
+++ b/app/Models/Environment.php
@@ -27,10 +27,8 @@ class Environment extends Model
static::deleting(function ($environment) {
$shared_variables = $environment->environment_variables();
foreach ($shared_variables as $shared_variable) {
- ray('Deleting environment shared variable: '.$shared_variable->name);
$shared_variable->delete();
}
-
});
}
diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php
index 138775aba..08f23d7ab 100644
--- a/app/Models/EnvironmentVariable.php
+++ b/app/Models/EnvironmentVariable.php
@@ -44,7 +44,7 @@ class EnvironmentVariable extends Model
'version' => 'string',
];
- protected $appends = ['real_value', 'is_shared'];
+ protected $appends = ['real_value', 'is_shared', 'is_really_required'];
protected static function booted()
{
@@ -74,6 +74,9 @@ class EnvironmentVariable extends Model
'version' => config('version'),
]);
});
+ static::saving(function (EnvironmentVariable $environmentVariable) {
+ $environmentVariable->updateIsShared();
+ });
}
public function service()
@@ -126,15 +129,17 @@ class EnvironmentVariable extends Model
$env = $this->get_real_environment_variables($this->value, $resource);
return data_get($env, 'value', $env);
- if (is_string($env)) {
- return $env;
- }
-
- return $env->value;
}
);
}
+ protected function isReallyRequired(): Attribute
+ {
+ return Attribute::make(
+ get: fn () => $this->is_required && str($this->real_value)->isEmpty(),
+ );
+ }
+
protected function isShared(): Attribute
{
return Attribute::make(
@@ -151,13 +156,12 @@ class EnvironmentVariable extends Model
private function get_real_environment_variables(?string $environment_variable = null, $resource = null)
{
- if ((is_null($environment_variable) && $environment_variable == '') || is_null($resource)) {
+ if ((is_null($environment_variable) && $environment_variable === '') || is_null($resource)) {
return null;
}
$environment_variable = trim($environment_variable);
$sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/');
if ($sharedEnvsFound->isEmpty()) {
-
return $environment_variable;
}
@@ -197,7 +201,7 @@ class EnvironmentVariable extends Model
private function set_environment_variables(?string $environment_variable = null): ?string
{
- if (is_null($environment_variable) && $environment_variable == '') {
+ if (is_null($environment_variable) && $environment_variable === '') {
return null;
}
$environment_variable = trim($environment_variable);
@@ -215,4 +219,11 @@ class EnvironmentVariable extends Model
set: fn (string $value) => str($value)->trim()->replace(' ', '_')->value,
);
}
+
+ protected function updateIsShared(): void
+ {
+ $type = str($this->value)->after('{{')->before('.')->value;
+ $isShared = str($this->value)->startsWith('{{'.$type) && str($this->value)->endsWith('}}');
+ $this->is_shared = $isShared;
+ }
}
diff --git a/app/Models/GithubApp.php b/app/Models/GithubApp.php
index 66ecdd967..0b0e93b12 100644
--- a/app/Models/GithubApp.php
+++ b/app/Models/GithubApp.php
@@ -31,6 +31,11 @@ class GithubApp extends BaseModel
});
}
+ public static function ownedByCurrentTeam()
+ {
+ return GithubApp::whereTeamId(currentTeam()->id);
+ }
+
public static function public()
{
return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get();
@@ -60,7 +65,7 @@ class GithubApp extends BaseModel
{
return Attribute::make(
get: function () {
- if ($this->getMorphClass() === 'App\Models\GithubApp') {
+ if ($this->getMorphClass() === \App\Models\GithubApp::class) {
return 'github';
}
},
diff --git a/app/Models/GitlabApp.php b/app/Models/GitlabApp.php
index a789a7e65..2112a4a66 100644
--- a/app/Models/GitlabApp.php
+++ b/app/Models/GitlabApp.php
@@ -9,6 +9,11 @@ class GitlabApp extends BaseModel
'app_secret',
];
+ public static function ownedByCurrentTeam()
+ {
+ return GitlabApp::whereTeamId(currentTeam()->id);
+ }
+
public function applications()
{
return $this->morphMany(Application::class, 'source');
diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php
index 27a181ee4..eeb803925 100644
--- a/app/Models/InstanceSettings.php
+++ b/app/Models/InstanceSettings.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use App\Jobs\PullHelperImageJob;
use App\Notifications\Channels\SendsEmail;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -21,8 +22,22 @@ class InstanceSettings extends Model implements SendsEmail
'is_auto_update_enabled' => 'boolean',
'auto_update_frequency' => 'string',
'update_check_frequency' => 'string',
+ 'sentinel_token' => 'encrypted',
];
+ protected static function booted(): void
+ {
+ static::updated(function ($settings) {
+ if ($settings->isDirty('helper_version')) {
+ Server::chunkById(100, function ($servers) {
+ foreach ($servers as $server) {
+ PullHelperImageJob::dispatch($server);
+ }
+ });
+ }
+ });
+ }
+
public function fqdn(): Attribute
{
return Attribute::make(
@@ -85,4 +100,17 @@ class InstanceSettings extends Model implements SendsEmail
return "[{$instanceName}]";
}
+
+ // public function helperVersion(): Attribute
+ // {
+ // return Attribute::make(
+ // get: function ($value) {
+ // if (isDev()) {
+ // return 'latest';
+ // }
+
+ // return $value;
+ // }
+ // );
+ // }
}
diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php
index d528099ff..2c223be77 100644
--- a/app/Models/LocalFileVolume.php
+++ b/app/Models/LocalFileVolume.php
@@ -72,7 +72,6 @@ class LocalFileVolume extends BaseModel
if ($path && $path != '/' && $path != '.' && $path != '..') {
if ($isFile === 'OK') {
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
-
} elseif ($isDir === 'OK') {
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
$commands->push("rmdir $path > /dev/null 2>&1 || true");
@@ -113,15 +112,15 @@ class LocalFileVolume extends BaseModel
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
- if ($isFile == 'OK' && $this->is_directory) {
+ if ($isFile === 'OK' && $this->is_directory) {
$content = instant_remote_process(["cat $path"], $server, false);
$this->is_directory = false;
$this->content = $content;
$this->save();
FileStorageChanged::dispatch(data_get($server, 'team_id'));
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
- } elseif ($isDir == 'OK' && ! $this->is_directory) {
- if ($path == '/' || $path == '.' || $path == '..' || $path == '' || str($path)->isEmpty() || is_null($path)) {
+ } elseif ($isDir === 'OK' && ! $this->is_directory) {
+ if ($path === '/' || $path === '.' || $path === '..' || $path === '' || str($path)->isEmpty() || is_null($path)) {
$this->is_directory = true;
$this->save();
throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file.
Please delete the directory on the server or mark it as directory.');
@@ -132,7 +131,7 @@ class LocalFileVolume extends BaseModel
], $server, false);
FileStorageChanged::dispatch(data_get($server, 'team_id'));
}
- if ($isDir == 'NOK' && ! $this->is_directory) {
+ if ($isDir === 'NOK' && ! $this->is_directory) {
$chmod = data_get($this, 'chmod');
$chown = data_get($this, 'chown');
if ($content) {
@@ -148,7 +147,7 @@ class LocalFileVolume extends BaseModel
if ($chmod) {
$commands->push("chmod $chmod $path");
}
- } elseif ($isDir == 'NOK' && $this->is_directory) {
+ } elseif ($isDir === 'NOK' && $this->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
}
diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php
index 45bc6bc84..80015d87f 100644
--- a/app/Models/PrivateKey.php
+++ b/app/Models/PrivateKey.php
@@ -2,6 +2,9 @@
namespace App\Models;
+use DanHarrin\LivewireRateLimiting\WithRateLimiting;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Validation\ValidationException;
use OpenApi\Attributes as OA;
use phpseclib3\Crypt\PublicKeyLoader;
@@ -22,48 +25,144 @@ use phpseclib3\Crypt\PublicKeyLoader;
)]
class PrivateKey extends BaseModel
{
+ use WithRateLimiting;
+
protected $fillable = [
'name',
'description',
'private_key',
'is_git_related',
'team_id',
+ 'fingerprint',
+ ];
+
+ protected $casts = [
+ 'private_key' => 'encrypted',
];
protected static function booted()
{
static::saving(function ($key) {
- $privateKey = data_get($key, 'private_key');
- if (substr($privateKey, -1) !== "\n") {
- $key->private_key = $privateKey."\n";
+ $key->private_key = formatPrivateKey($key->private_key);
+
+ if (! self::validatePrivateKey($key->private_key)) {
+ throw ValidationException::withMessages([
+ 'private_key' => ['The private key is invalid.'],
+ ]);
+ }
+
+ $key->fingerprint = self::generateFingerprint($key->private_key);
+ if (self::fingerprintExists($key->fingerprint, $key->id)) {
+ throw ValidationException::withMessages([
+ 'private_key' => ['This private key already exists.'],
+ ]);
}
});
+ static::deleted(function ($key) {
+ self::deleteFromStorage($key);
+ });
+ }
+
+ public function getPublicKey()
+ {
+ return self::extractPublicKeyFromPrivate($this->private_key) ?? 'Error loading private key';
}
public static function ownedByCurrentTeam(array $select = ['*'])
{
$selectArray = collect($select)->concat(['id']);
- return PrivateKey::whereTeamId(currentTeam()->id)->select($selectArray->all());
+ return self::whereTeamId(currentTeam()->id)->select($selectArray->all());
}
- public function publicKey()
+ public static function validatePrivateKey($privateKey)
{
try {
- return PublicKeyLoader::load($this->private_key)->getPublicKey()->toString('OpenSSH', ['comment' => '']);
+ PublicKeyLoader::load($privateKey);
+
+ return true;
} catch (\Throwable $e) {
- return 'Error loading private key';
+ return false;
}
}
- public function isEmpty()
+ public static function createAndStore(array $data)
{
- if ($this->servers()->count() === 0 && $this->applications()->count() === 0 && $this->githubApps()->count() === 0 && $this->gitlabApps()->count() === 0) {
- return true;
- }
+ $privateKey = new self($data);
+ $privateKey->save();
+ $privateKey->storeInFileSystem();
- return false;
+ return $privateKey;
+ }
+
+ public static function generateNewKeyPair($type = 'rsa')
+ {
+ try {
+ $instance = new self;
+ $instance->rateLimit(10);
+ $name = generate_random_name();
+ $description = 'Created by Coolify';
+ $keyPair = generateSSHKey($type === 'ed25519' ? 'ed25519' : 'rsa');
+
+ return [
+ 'name' => $name,
+ 'description' => $description,
+ 'private_key' => $keyPair['private'],
+ 'public_key' => $keyPair['public'],
+ ];
+ } catch (\Throwable $e) {
+ throw new \Exception("Failed to generate new {$type} key: ".$e->getMessage());
+ }
+ }
+
+ public static function extractPublicKeyFromPrivate($privateKey)
+ {
+ try {
+ $key = PublicKeyLoader::load($privateKey);
+
+ return $key->getPublicKey()->toString('OpenSSH', ['comment' => '']);
+ } catch (\Throwable $e) {
+ return null;
+ }
+ }
+
+ public static function validateAndExtractPublicKey($privateKey)
+ {
+ $isValid = self::validatePrivateKey($privateKey);
+ $publicKey = $isValid ? self::extractPublicKeyFromPrivate($privateKey) : '';
+
+ return [
+ 'isValid' => $isValid,
+ 'publicKey' => $publicKey,
+ ];
+ }
+
+ public function storeInFileSystem()
+ {
+ $filename = "ssh_key@{$this->uuid}";
+ Storage::disk('ssh-keys')->put($filename, $this->private_key);
+
+ return "/var/www/html/storage/app/ssh/keys/{$filename}";
+ }
+
+ public static function deleteFromStorage(self $privateKey)
+ {
+ $filename = "ssh_key@{$privateKey->uuid}";
+ Storage::disk('ssh-keys')->delete($filename);
+ }
+
+ public function getKeyLocation()
+ {
+ return "/var/www/html/storage/app/ssh/keys/ssh_key@{$this->uuid}";
+ }
+
+ public function updatePrivateKey(array $data)
+ {
+ $this->update($data);
+ $this->storeInFileSystem();
+
+ return $this;
}
public function servers()
@@ -85,4 +184,55 @@ class PrivateKey extends BaseModel
{
return $this->hasMany(GitlabApp::class);
}
+
+ public function isInUse()
+ {
+ return $this->servers()->exists()
+ || $this->applications()->exists()
+ || $this->githubApps()->exists()
+ || $this->gitlabApps()->exists();
+ }
+
+ public function safeDelete()
+ {
+ if (! $this->isInUse()) {
+ $this->delete();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public static function generateFingerprint($privateKey)
+ {
+ try {
+ $key = PublicKeyLoader::load($privateKey);
+ $publicKey = $key->getPublicKey();
+
+ return $publicKey->getFingerprint('sha256');
+ } catch (\Throwable $e) {
+ return null;
+ }
+ }
+
+ private static function fingerprintExists($fingerprint, $excludeId = null)
+ {
+ $query = self::query()
+ ->where('fingerprint', $fingerprint)
+ ->where('id', '!=', $excludeId);
+
+ if (currentTeam()) {
+ $query->where('team_id', currentTeam()->id);
+ }
+
+ return $query->exists();
+ }
+
+ public static function cleanupUnusedKeys()
+ {
+ self::ownedByCurrentTeam()->each(function ($privateKey) {
+ $privateKey->safeDelete();
+ });
+ }
}
diff --git a/app/Models/Project.php b/app/Models/Project.php
index 18481751c..f27e6c208 100644
--- a/app/Models/Project.php
+++ b/app/Models/Project.php
@@ -24,9 +24,11 @@ class Project extends BaseModel
{
protected $guarded = [];
+ protected $appends = ['default_environment'];
+
public static function ownedByCurrentTeam()
{
- return Project::whereTeamId(currentTeam()->id)->orderBy('name');
+ return Project::whereTeamId(currentTeam()->id)->orderByRaw('LOWER(name)');
}
protected static function booted()
@@ -45,7 +47,6 @@ class Project extends BaseModel
$project->settings()->delete();
$shared_variables = $project->environment_variables();
foreach ($shared_variables as $shared_variable) {
- ray('Deleting project shared variable: '.$shared_variable->name);
$shared_variable->delete();
}
});
@@ -121,9 +122,18 @@ class Project extends BaseModel
return $this->hasManyThrough(StandaloneMariadb::class, Environment::class);
}
- public function resource_count()
+ public function isEmpty()
{
- return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count() + $this->keydbs()->count() + $this->dragonflies()->count() + $this->clickhouses()->count() + $this->services()->count();
+ return $this->applications()->count() == 0 &&
+ $this->redis()->count() == 0 &&
+ $this->postgresqls()->count() == 0 &&
+ $this->mysqls()->count() == 0 &&
+ $this->keydbs()->count() == 0 &&
+ $this->dragonflies()->count() == 0 &&
+ $this->clickhouses()->count() == 0 &&
+ $this->mariadbs()->count() == 0 &&
+ $this->mongodbs()->count() == 0 &&
+ $this->services()->count() == 0;
}
public function databases()
@@ -131,7 +141,7 @@ class Project extends BaseModel
return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get());
}
- public function default_environment()
+ public function getDefaultEnvironmentAttribute()
{
$default = $this->environments()->where('name', 'production')->first();
if ($default) {
diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php
index 4c7faaa6f..a432a6e9c 100644
--- a/app/Models/S3Storage.php
+++ b/app/Models/S3Storage.php
@@ -40,6 +40,16 @@ class S3Storage extends BaseModel
return "{$this->endpoint}/{$this->bucket}";
}
+ public function isHetzner()
+ {
+ return str($this->endpoint)->contains('your-objectstorage.com');
+ }
+
+ public function isDigitalOcean()
+ {
+ return str($this->endpoint)->contains('digitaloceanspaces.com');
+ }
+
public function testConnection(bool $shouldSave = false)
{
try {
diff --git a/app/Models/ScheduledDatabaseBackup.php b/app/Models/ScheduledDatabaseBackup.php
index 50a0c8173..473fc7b4b 100644
--- a/app/Models/ScheduledDatabaseBackup.php
+++ b/app/Models/ScheduledDatabaseBackup.php
@@ -35,14 +35,22 @@ class ScheduledDatabaseBackup extends BaseModel
{
return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get();
}
+
public function server()
{
if ($this->database) {
- if ($this->database->destination && $this->database->destination->server) {
- $server = $this->database->destination->server;
+ if ($this->database instanceof ServiceDatabase) {
+ $destination = data_get($this->database->service, 'destination');
+ $server = data_get($destination, 'server');
+ } else {
+ $destination = data_get($this->database, 'destination');
+ $server = data_get($destination, 'server');
+ }
+ if ($server) {
return $server;
}
}
+
return null;
}
}
diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php
index 82f0036a5..264a04d1f 100644
--- a/app/Models/ScheduledTask.php
+++ b/app/Models/ScheduledTask.php
@@ -4,8 +4,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
-use App\Models\Service;
-use App\Models\Application;
class ScheduledTask extends BaseModel
{
@@ -36,20 +34,18 @@ class ScheduledTask extends BaseModel
{
if ($this->application) {
if ($this->application->destination && $this->application->destination->server) {
- $server = $this->application->destination->server;
- return $server;
+ return $this->application->destination->server;
}
} elseif ($this->service) {
if ($this->service->destination && $this->service->destination->server) {
- $server = $this->service->destination->server;
- return $server;
+ return $this->service->destination->server;
}
} elseif ($this->database) {
if ($this->database->destination && $this->database->destination->server) {
- $server = $this->database->destination->server;
- return $server;
+ return $this->database->destination->server;
}
}
+
return null;
}
}
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 90e6ade14..e6e2ffbe1 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -2,15 +2,19 @@
namespace App\Models;
+use App\Actions\Proxy\StartProxy;
use App\Actions\Server\InstallDocker;
+use App\Actions\Server\StartSentinel;
use App\Enums\ProxyTypes;
-use App\Jobs\PullSentinelImageJob;
-use App\Notifications\Server\Revived;
+use App\Jobs\CheckAndStartSentinelJob;
+use App\Notifications\Server\Reachable;
+use App\Notifications\Server\Unreachable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
-use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Stringable;
use OpenApi\Attributes as OA;
@@ -23,26 +27,29 @@ use Symfony\Component\Yaml\Yaml;
description: 'Server model',
type: 'object',
properties: [
- 'id' => ['type' => 'integer'],
- 'uuid' => ['type' => 'string'],
- 'name' => ['type' => 'string'],
- 'description' => ['type' => 'string'],
- 'ip' => ['type' => 'string'],
- 'user' => ['type' => 'string'],
- 'port' => ['type' => 'integer'],
- 'proxy' => ['type' => 'object'],
- 'high_disk_usage_notification_sent' => ['type' => 'boolean'],
- 'unreachable_notification_sent' => ['type' => 'boolean'],
- 'unreachable_count' => ['type' => 'integer'],
- 'validation_logs' => ['type' => 'string'],
- 'log_drain_notification_sent' => ['type' => 'boolean'],
- 'swarm_cluster' => ['type' => 'string'],
+ 'id' => ['type' => 'integer', 'description' => 'The server ID.'],
+ 'uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
+ 'name' => ['type' => 'string', 'description' => 'The server name.'],
+ 'description' => ['type' => 'string', 'description' => 'The server description.'],
+ 'ip' => ['type' => 'string', 'description' => 'The IP address.'],
+ 'user' => ['type' => 'string', 'description' => 'The user.'],
+ 'port' => ['type' => 'integer', 'description' => 'The port number.'],
+ 'proxy' => ['type' => 'object', 'description' => 'The proxy configuration.'],
+ 'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'description' => 'The proxy type.'],
+ 'high_disk_usage_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the high disk usage notification has been sent.'],
+ 'unreachable_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unreachable notification has been sent.'],
+ 'unreachable_count' => ['type' => 'integer', 'description' => 'The unreachable count for your server.'],
+ 'validation_logs' => ['type' => 'string', 'description' => 'The validation logs.'],
+ 'log_drain_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the log drain notification has been sent.'],
+ 'swarm_cluster' => ['type' => 'string', 'description' => 'The swarm cluster configuration.'],
+ 'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'],
+ 'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'],
]
)]
class Server extends BaseModel
{
- use SchemalessAttributesTrait;
+ use SchemalessAttributesTrait, SoftDeletes;
public static $batch_counter = 0;
@@ -58,6 +65,11 @@ class Server extends BaseModel
}
$server->forceFill($payload);
});
+ static::saved(function ($server) {
+ if ($server->privateKey?->isDirty()) {
+ refresh_server_connection($server->privateKey);
+ }
+ });
static::created(function ($server) {
ServerSetting::create([
'server_id' => $server->id,
@@ -94,7 +106,8 @@ class Server extends BaseModel
}
}
});
- static::deleting(function ($server) {
+
+ static::forceDeleting(function ($server) {
$server->destinations()->each(function ($destination) {
$destination->delete();
});
@@ -102,10 +115,15 @@ class Server extends BaseModel
});
}
- public $casts = [
+ protected $casts = [
'proxy' => SchemalessAttributes::class,
'logdrain_axiom_api_key' => 'encrypted',
'logdrain_newrelic_license_key' => 'encrypted',
+ 'delete_unused_volumes' => 'boolean',
+ 'delete_unused_networks' => 'boolean',
+ 'unreachable_notification_sent' => 'boolean',
+ 'is_build_server' => 'boolean',
+ 'force_disabled' => 'boolean',
];
protected $schemalessAttributes = [
@@ -124,6 +142,11 @@ class Server extends BaseModel
protected $guarded = [];
+ public function type()
+ {
+ return 'server';
+ }
+
public static function isReachable()
{
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true);
@@ -156,6 +179,11 @@ class Server extends BaseModel
return $this->hasOne(ServerSetting::class);
}
+ public function proxySet()
+ {
+ return $this->proxyType() && $this->proxyType() !== 'NONE' && $this->isFunctional() && ! $this->isSwarmWorker() && ! $this->settings->is_build_server;
+ }
+
public function setupDefault404Redirect()
{
$dynamic_conf_path = $this->proxyPath().'/dynamic';
@@ -163,11 +191,11 @@ class Server extends BaseModel
$redirect_url = $this->proxy->redirect_url;
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml";
- } elseif ($proxy_type === 'CADDY') {
+ } elseif ($proxy_type === ProxyTypes::CADDY->value) {
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy";
}
if (empty($redirect_url)) {
- if ($proxy_type === 'CADDY') {
+ if ($proxy_type === ProxyTypes::CADDY->value) {
$conf = ':80, :443 {
respond 404
}';
@@ -201,10 +229,13 @@ respond 404
1 => 'https',
],
'service' => 'noop',
- 'rule' => 'HostRegexp(`{catchall:.*}`)',
+ 'rule' => 'HostRegexp(`.+`)',
+ 'tls' => [
+ 'certResolver' => 'letsencrypt',
+ ],
'priority' => 1,
'middlewares' => [
- 0 => 'redirect-regexp@file',
+ 0 => 'redirect-regexp',
],
],
],
@@ -237,7 +268,7 @@ respond 404
$conf;
$base64 = base64_encode($conf);
- } elseif ($proxy_type === 'CADDY') {
+ } elseif ($proxy_type === ProxyTypes::CADDY->value) {
$conf = ":80, :443 {
redir $redirect_url
}";
@@ -253,9 +284,6 @@ respond 404
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
], $this);
- if (config('app.env') == 'local') {
- ray($conf);
- }
if ($proxy_type === 'CADDY') {
$this->reloadCaddy();
}
@@ -263,7 +291,7 @@ respond 404
public function setupDynamicProxyConfiguration()
{
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
$dynamic_config_path = $this->proxyPath().'/dynamic';
if ($this->proxyType() === ProxyTypes::TRAEFIK->value) {
$file = "$dynamic_config_path/coolify.yaml";
@@ -393,7 +421,7 @@ respond 404
"echo '$base64' | base64 -d | tee $file > /dev/null",
], $this);
- if (config('app.env') == 'local') {
+ if (config('app.env') === 'local') {
// ray($yaml);
}
}
@@ -436,18 +464,26 @@ $schema://$host {
public function proxyPath()
{
- $base_path = config('coolify.base_config_path');
+ $base_path = config('constants.coolify.base_config_path');
$proxyType = $this->proxyType();
$proxy_path = "$base_path/proxy";
// TODO: should use /traefik for already exisiting configurations?
// Should move everything except /caddy and /nginx to /traefik
// The code needs to be modified as well, so maybe it does not worth it
if ($proxyType === ProxyTypes::TRAEFIK->value) {
- $proxy_path = $proxy_path;
+ // Do nothing
} elseif ($proxyType === ProxyTypes::CADDY->value) {
- $proxy_path = $proxy_path.'/caddy';
+ if (isDev()) {
+ $proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/caddy';
+ } else {
+ $proxy_path = $proxy_path.'/caddy';
+ }
} elseif ($proxyType === ProxyTypes::NGINX->value) {
- $proxy_path = $proxy_path.'/nginx';
+ if (isDev()) {
+ $proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/nginx';
+ } else {
+ $proxy_path = $proxy_path.'/nginx';
+ }
}
return $proxy_path;
@@ -455,15 +491,6 @@ $schema://$host {
public function proxyType()
{
- // $proxyType = $this->proxy->get('type');
- // if ($proxyType === ProxyTypes::NONE->value) {
- // return $proxyType;
- // }
- // if (is_null($proxyType)) {
- // $this->proxy->type = ProxyTypes::TRAEFIK->value;
- // $this->proxy->status = ProxyStatus::EXITED->value;
- // $this->save();
- // }
return data_get($this->proxy, 'type');
}
@@ -482,20 +509,6 @@ $schema://$host {
return Server::whereTeamId($teamId)->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_build_server', true);
}
- public function skipServer()
- {
- if ($this->ip === '1.2.3.4') {
- // ray('skipping 1.2.3.4');
- return true;
- }
- if ($this->settings->force_disabled === true) {
- // ray('force_disabled');
- return true;
- }
-
- return false;
- }
-
public function isForceDisabled()
{
return $this->settings->force_disabled;
@@ -503,24 +516,48 @@ $schema://$host {
public function forceEnableServer()
{
- $this->settings->update([
- 'force_disabled' => false,
- ]);
+ $this->settings->force_disabled = false;
+ $this->settings->save();
}
public function forceDisableServer()
{
- $this->settings->update([
- 'force_disabled' => true,
- ]);
+ $this->settings->force_disabled = true;
+ $this->settings->save();
$sshKeyFileLocation = "id.root@{$this->uuid}";
Storage::disk('ssh-keys')->delete($sshKeyFileLocation);
Storage::disk('ssh-mux')->delete($this->muxFilename());
}
+ public function sentinelHeartbeat(bool $isReset = false)
+ {
+ $this->sentinel_updated_at = $isReset ? now()->subMinutes(6000) : now();
+ $this->save();
+ }
+
+ /**
+ * Get the wait time for Sentinel to push before performing an SSH check.
+ *
+ * @return int The wait time in seconds.
+ */
+ public function waitBeforeDoingSshCheck(): int
+ {
+ $wait = $this->settings->sentinel_push_interval_seconds * 3;
+ if ($wait < 120) {
+ $wait = 120;
+ }
+
+ return $wait;
+ }
+
+ public function isSentinelLive()
+ {
+ return Carbon::parse($this->sentinel_updated_at)->isAfter(now()->subSeconds($this->waitBeforeDoingSshCheck()));
+ }
+
public function isSentinelEnabled()
{
- return $this->isMetricsEnabled() || $this->isServerApiEnabled();
+ return ($this->isMetricsEnabled() || $this->isServerApiEnabled()) && ! $this->isBuildServer();
}
public function isMetricsEnabled()
@@ -530,68 +567,32 @@ $schema://$host {
public function isServerApiEnabled()
{
- return $this->settings->is_server_api_enabled;
- }
-
- public function checkServerApi()
- {
- if ($this->isServerApiEnabled()) {
- $server_ip = $this->ip;
- if (isDev()) {
- if ($this->id === 0) {
- $server_ip = 'localhost';
- }
- }
- $command = "curl -s http://{$server_ip}:12172/api/health";
- $process = Process::timeout(5)->run($command);
- if ($process->failed()) {
- ray($process->exitCode(), $process->output(), $process->errorOutput());
- throw new \Exception("Server API is not reachable on http://{$server_ip}:12172");
- }
-
- }
+ return $this->settings->is_sentinel_enabled;
}
public function checkSentinel()
{
- // ray("Checking sentinel on server: {$this->name}");
- if ($this->isSentinelEnabled()) {
- $sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false);
- $sentinel_found = json_decode($sentinel_found, true);
- $status = data_get($sentinel_found, '0.State.Status', 'exited');
- if ($status !== 'running') {
- // ray('Sentinel is not running, starting it...');
- PullSentinelImageJob::dispatch($this);
- } else {
- // ray('Sentinel is running');
- }
- }
+ CheckAndStartSentinelJob::dispatch($this);
}
public function getCpuMetrics(int $mins = 5)
{
if ($this->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
+ $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
if (str($cpu)->contains('error')) {
$error = json_decode($cpu, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
+ if ($error === 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
- $cpu = str($cpu)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($cpu)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 0);
+ $cpu = json_decode($cpu, true);
- return [(int) $time, (float) $cpu_usage_percent];
- });
+ return collect($cpu)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
});
-
- return $parsedCollection->toArray();
}
}
@@ -599,98 +600,28 @@ $schema://$host {
{
if ($this->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
- $memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
+ $memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
if (str($memory)->contains('error')) {
$error = json_decode($memory, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
+ if ($error === 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
- $memory = str($memory)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($memory)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $used, $free, $usedPercent] = explode(',', trim($line));
- $usedPercent = number_format($usedPercent, 0);
-
- return [(int) $time, (float) $usedPercent];
- });
+ $memory = json_decode($memory, true);
+ $parsedCollection = collect($memory)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['usedPercent']];
});
return $parsedCollection->toArray();
}
}
- public function isServerReady(int $tries = 3)
- {
- if ($this->skipServer()) {
- return false;
- }
- $serverUptimeCheckNumber = $this->unreachable_count;
- if ($this->unreachable_count < $tries) {
- $serverUptimeCheckNumber = $this->unreachable_count + 1;
- }
- if ($this->unreachable_count > $tries) {
- $serverUptimeCheckNumber = $tries;
- }
-
- $serverUptimeCheckNumberMax = $tries;
-
- // ray('server: ' . $this->name);
- // ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
- // ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
-
- ['uptime' => $uptime] = $this->validateConnection();
- if ($uptime) {
- if ($this->unreachable_notification_sent === true) {
- $this->update(['unreachable_notification_sent' => false]);
- }
-
- return true;
- } else {
- if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
- // Reached max number of retries
- if ($this->unreachable_notification_sent === false) {
- ray('Server unreachable, sending notification...');
- // $this->team?->notify(new Unreachable($this));
- $this->update(['unreachable_notification_sent' => true]);
- }
- if ($this->settings->is_reachable === true) {
- $this->settings()->update([
- 'is_reachable' => false,
- ]);
- }
-
- foreach ($this->applications() as $application) {
- $application->update(['status' => 'exited']);
- }
- foreach ($this->databases() as $database) {
- $database->update(['status' => 'exited']);
- }
- foreach ($this->services()->get() as $service) {
- $apps = $service->applications()->get();
- $dbs = $service->databases()->get();
- foreach ($apps as $app) {
- $app->update(['status' => 'exited']);
- }
- foreach ($dbs as $db) {
- $db->update(['status' => 'exited']);
- }
- }
- } else {
- $this->update([
- 'unreachable_count' => $this->unreachable_count + 1,
- ]);
- }
-
- return false;
- }
- }
-
public function getDiskUsage(): ?string
{
- return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
+ return instant_remote_process(['df / --output=pcent | tr -cd 0-9'], $this, false);
+ // return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
}
public function definedResources()
@@ -748,7 +679,7 @@ $schema://$host {
}
}
} else {
- $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this, false);
+ $containers = instant_remote_process(["docker container inspect $(docker container ls -aq) --format '{{json .}}'"], $this, false);
$containers = format_docker_command_output_to_json($containers);
$containerReplicates = collect([]);
}
@@ -833,9 +764,9 @@ $schema://$host {
$clickhouses = data_get($standaloneDocker, 'clickhouses', collect([]));
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs)->concat($keydbs)->concat($dragonflies)->concat($clickhouses);
- })->filter(function ($item) {
+ })->flatten()->filter(function ($item) {
return data_get($item, 'name') !== 'coolify-db';
- })->flatten();
+ });
}
public function applications()
@@ -879,6 +810,33 @@ $schema://$host {
return $this->hasMany(Service::class);
}
+ public function port(): Attribute
+ {
+ return Attribute::make(
+ get: function ($value) {
+ return preg_replace('/[^0-9]/', '', $value);
+ }
+ );
+ }
+
+ public function user(): Attribute
+ {
+ return Attribute::make(
+ get: function ($value) {
+ return preg_replace('/[^A-Za-z0-9\-_]/', '', $value);
+ }
+ );
+ }
+
+ public function ip(): Attribute
+ {
+ return Attribute::make(
+ get: function ($value) {
+ return preg_replace('/[^0-9a-zA-Z.:%-]/', '', $value);
+ }
+ );
+ }
+
public function getIp(): Attribute
{
return Attribute::make(
@@ -909,8 +867,6 @@ $schema://$host {
$standalone_docker = $this->hasMany(StandaloneDocker::class)->get();
$swarm_docker = $this->hasMany(SwarmDocker::class)->get();
- // $additional_dockers = $this->belongsToMany(StandaloneDocker::class, 'additional_destinations')->withPivot('server_id')->get();
- // return $standalone_docker->concat($swarm_docker)->concat($additional_dockers);
return $standalone_docker->concat($swarm_docker);
}
@@ -941,20 +897,32 @@ $schema://$host {
public function isProxyShouldRun()
{
- if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) {
+ // TODO: Do we need "|| $this->proxy->force_stop" here?
+ if ($this->proxyType() === ProxyTypes::NONE->value || $this->isBuildServer()) {
return false;
}
return true;
}
+ public function skipServer()
+ {
+ if ($this->ip === '1.2.3.4') {
+ return true;
+ }
+ if ($this->settings->force_disabled === true) {
+ return true;
+ }
+
+ return false;
+ }
+
public function isFunctional()
{
- $isFunctional = $this->settings->is_reachable && $this->settings->is_usable && ! $this->settings->force_disabled;
- ['private_key_filename' => $private_key_filename, 'mux_filename' => $mux_filename] = server_ssh_configuration($this);
- if (! $isFunctional) {
- Storage::disk('ssh-keys')->delete($private_key_filename);
- Storage::disk('ssh-mux')->delete($mux_filename);
+ $isFunctional = $this->settings->is_reachable && $this->settings->is_usable && $this->settings->force_disabled === false && $this->ip !== '1.2.3.4';
+
+ if ($isFunctional === false) {
+ Storage::disk('ssh-mux')->delete($this->muxFilename());
}
return $isFunctional;
@@ -1006,36 +974,106 @@ $schema://$host {
return data_get($this, 'settings.is_swarm_worker');
}
- public function validateConnection()
+ public function serverStatus(): bool
+ {
+ if ($this->status() === false) {
+ return false;
+ }
+ if ($this->isFunctional() === false) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function status(): bool
+ {
+ ['uptime' => $uptime] = $this->validateConnection();
+ if ($uptime === false) {
+ foreach ($this->applications() as $application) {
+ $application->status = 'exited';
+ $application->save();
+ }
+ foreach ($this->databases() as $database) {
+ $database->status = 'exited';
+ $database->save();
+ }
+ foreach ($this->services() as $service) {
+ $apps = $service->applications()->get();
+ $dbs = $service->databases()->get();
+ foreach ($apps as $app) {
+ $app->status = 'exited';
+ $app->save();
+ }
+ foreach ($dbs as $db) {
+ $db->status = 'exited';
+ $db->save();
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public function isReachableChanged()
+ {
+ $this->refresh();
+ $unreachableNotificationSent = (bool) $this->unreachable_notification_sent;
+ $isReachable = (bool) $this->settings->is_reachable;
+ // If the server is reachable, send the reachable notification if it was sent before
+ if ($isReachable === true) {
+ if ($unreachableNotificationSent === true) {
+ $this->sendReachableNotification();
+ }
+ } else {
+ // If the server is unreachable, send the unreachable notification if it was not sent before
+ if ($unreachableNotificationSent === false) {
+ $this->sendUnreachableNotification();
+ }
+ }
+ }
+
+ public function sendReachableNotification()
+ {
+ $this->unreachable_notification_sent = false;
+ $this->save();
+ $this->refresh();
+ $this->team->notify(new Reachable($this));
+ }
+
+ public function sendUnreachableNotification()
+ {
+ $this->unreachable_notification_sent = true;
+ $this->save();
+ $this->refresh();
+ $this->team->notify(new Unreachable($this));
+ }
+
+ public function validateConnection(bool $justCheckingNewKey = false)
{
config()->set('constants.ssh.mux_enabled', false);
- $server = Server::find($this->id);
- if (! $server) {
- return ['uptime' => false, 'error' => 'Server not found.'];
- }
- if ($server->skipServer()) {
+ if ($this->skipServer()) {
return ['uptime' => false, 'error' => 'Server skipped.'];
}
try {
- // EC2 does not have `uptime` command, lol
- instant_remote_process(['ls /'], $server);
- $server->settings()->update([
- 'is_reachable' => true,
- ]);
- $server->update([
- 'unreachable_count' => 0,
- ]);
- if (data_get($server, 'unreachable_notification_sent') === true) {
- // $server->team?->notify(new Revived($server));
- $server->update(['unreachable_notification_sent' => false]);
+ instant_remote_process(['ls /'], $this);
+ if ($this->settings->is_reachable === false) {
+ $this->settings->is_reachable = true;
+ $this->settings->save();
}
return ['uptime' => true, 'error' => null];
} catch (\Throwable $e) {
- $server->settings()->update([
- 'is_reachable' => false,
- ]);
+ if ($justCheckingNewKey) {
+ return ['uptime' => false, 'error' => 'This key is not valid for this server.'];
+ }
+ if ($this->settings->is_reachable === true) {
+ $this->settings->is_reachable = false;
+ $this->settings->save();
+ }
return ['uptime' => false, 'error' => $e->getMessage()];
}
@@ -1043,9 +1081,7 @@ $schema://$host {
public function installDocker()
{
- $activity = InstallDocker::run($this);
-
- return $activity;
+ return InstallDocker::run($this);
}
public function validateDockerEngine($throwError = false)
@@ -1156,4 +1192,82 @@ $schema://$host {
{
return $this->settings->is_build_server;
}
+
+ public static function createWithPrivateKey(array $data, PrivateKey $privateKey)
+ {
+ $server = new self($data);
+ $server->privateKey()->associate($privateKey);
+ $server->save();
+
+ return $server;
+ }
+
+ public function updateWithPrivateKey(array $data, ?PrivateKey $privateKey = null)
+ {
+ $this->update($data);
+ if ($privateKey) {
+ $this->privateKey()->associate($privateKey);
+ $this->save();
+ }
+
+ return $this;
+ }
+
+ public function storageCheck(): ?string
+ {
+ $commands = [
+ 'df / --output=pcent | tr -cd 0-9',
+ ];
+
+ return instant_remote_process($commands, $this, false);
+ }
+
+ public function isIpv6(): bool
+ {
+ return str($this->ip)->contains(':');
+ }
+
+ public function restartSentinel(bool $async = true)
+ {
+ try {
+ if ($async) {
+ StartSentinel::dispatch($this, true);
+ } else {
+ StartSentinel::run($this, true);
+ }
+ } catch (\Throwable $e) {
+ return handleError($e);
+ }
+ }
+
+ public function url()
+ {
+ return base_url().'/server/'.$this->uuid;
+ }
+
+ public function restartContainer(string $containerName)
+ {
+ return instant_remote_process(['docker restart '.$containerName], $this, false);
+ }
+
+ public function changeProxy(string $proxyType, bool $async = true)
+ {
+ $validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) {
+ return str($proxyType->value)->lower();
+ });
+ if ($validProxyTypes->contains(str($proxyType)->lower())) {
+ $this->proxy->set('type', str($proxyType)->upper());
+ $this->proxy->set('status', 'exited');
+ $this->save();
+ if ($this->proxySet()) {
+ if ($async) {
+ StartProxy::dispatch($this);
+ } else {
+ StartProxy::run($this);
+ }
+ }
+ } else {
+ throw new \Exception('Invalid proxy type.');
+ }
+ }
}
diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php
index c44a393b4..fc2c5a0f4 100644
--- a/app/Models/ServerSetting.php
+++ b/app/Models/ServerSetting.php
@@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Log;
use OpenApi\Attributes as OA;
#[OA\Schema(
@@ -24,7 +25,7 @@ use OpenApi\Attributes as OA;
'is_logdrain_newrelic_enabled' => ['type' => 'boolean'],
'is_metrics_enabled' => ['type' => 'boolean'],
'is_reachable' => ['type' => 'boolean'],
- 'is_server_api_enabled' => ['type' => 'boolean'],
+ 'is_sentinel_enabled' => ['type' => 'boolean'],
'is_swarm_manager' => ['type' => 'boolean'],
'is_swarm_worker' => ['type' => 'boolean'],
'is_usable' => ['type' => 'boolean'],
@@ -35,9 +36,9 @@ use OpenApi\Attributes as OA;
'logdrain_highlight_project_id' => ['type' => 'string'],
'logdrain_newrelic_base_uri' => ['type' => 'string'],
'logdrain_newrelic_license_key' => ['type' => 'string'],
- 'metrics_history_days' => ['type' => 'integer'],
- 'metrics_refresh_rate_seconds' => ['type' => 'integer'],
- 'metrics_token' => ['type' => 'string'],
+ 'sentinel_metrics_history_days' => ['type' => 'integer'],
+ 'sentinel_metrics_refresh_rate_seconds' => ['type' => 'integer'],
+ 'sentinel_token' => ['type' => 'string'],
'docker_cleanup_frequency' => ['type' => 'string'],
'docker_cleanup_threshold' => ['type' => 'integer'],
'server_id' => ['type' => 'integer'],
@@ -53,8 +54,85 @@ class ServerSetting extends Model
protected $casts = [
'force_docker_cleanup' => 'boolean',
'docker_cleanup_threshold' => 'integer',
+ 'sentinel_token' => 'encrypted',
+ 'is_reachable' => 'boolean',
+ 'is_usable' => 'boolean',
];
+ protected static function booted()
+ {
+ static::creating(function ($setting) {
+ try {
+ if (str($setting->sentinel_token)->isEmpty()) {
+ $setting->generateSentinelToken(save: false, ignoreEvent: true);
+ }
+ if (str($setting->sentinel_custom_url)->isEmpty()) {
+ $setting->generateSentinelUrl(save: false, ignoreEvent: true);
+ }
+ } catch (\Throwable $e) {
+ Log::error('Error creating server setting: '.$e->getMessage());
+ }
+ });
+ static::updated(function ($settings) {
+ if (
+ $settings->isDirty('sentinel_token') ||
+ $settings->isDirty('sentinel_custom_url') ||
+ $settings->isDirty('sentinel_metrics_refresh_rate_seconds') ||
+ $settings->isDirty('sentinel_metrics_history_days') ||
+ $settings->isDirty('sentinel_push_interval_seconds')
+ ) {
+ $settings->server->restartSentinel();
+ }
+ if ($settings->isDirty('is_reachable')) {
+ $settings->server->isReachableChanged();
+ }
+ });
+ }
+
+ public function generateSentinelToken(bool $save = true, bool $ignoreEvent = false)
+ {
+ $data = [
+ 'server_uuid' => $this->server->uuid,
+ ];
+ $token = json_encode($data);
+ $encrypted = encrypt($token);
+ $this->sentinel_token = $encrypted;
+ if ($save) {
+ if ($ignoreEvent) {
+ $this->saveQuietly();
+ } else {
+ $this->save();
+ }
+ }
+
+ return $token;
+ }
+
+ public function generateSentinelUrl(bool $save = true, bool $ignoreEvent = false)
+ {
+ $domain = null;
+ $settings = InstanceSettings::get();
+ if ($this->server->isLocalhost()) {
+ $domain = 'http://host.docker.internal:8000';
+ } elseif ($settings->fqdn) {
+ $domain = $settings->fqdn;
+ } elseif ($settings->public_ipv4) {
+ $domain = 'http://'.$settings->public_ipv4.':8000';
+ } elseif ($settings->public_ipv6) {
+ $domain = 'http://'.$settings->public_ipv6.':8000';
+ }
+ $this->sentinel_custom_url = $domain;
+ if ($save) {
+ if ($ignoreEvent) {
+ $this->saveQuietly();
+ } else {
+ $this->save();
+ }
+ }
+
+ return $domain;
+ }
+
public function server()
{
return $this->belongsTo(Server::class);
diff --git a/app/Models/Service.php b/app/Models/Service.php
index d8def6663..6d3d2024b 100644
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -6,7 +6,9 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
+use Illuminate\Process\InvokedProcess;
use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage;
use OpenApi\Attributes as OA;
use Spatie\Url\Url;
@@ -40,7 +42,7 @@ class Service extends BaseModel
{
use HasFactory, SoftDeletes;
- private static $parserVersion = '3';
+ private static $parserVersion = '4';
protected $guarded = [];
@@ -131,15 +133,86 @@ class Service extends BaseModel
return $this->morphToMany(Tag::class, 'taggable');
}
+ public static function ownedByCurrentTeam()
+ {
+ return Service::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
+ }
+
+ public function getContainersToStop(): array
+ {
+ $containersToStop = [];
+ $applications = $this->applications()->get();
+ foreach ($applications as $application) {
+ $containersToStop[] = "{$application->name}-{$this->uuid}";
+ }
+ $dbs = $this->databases()->get();
+ foreach ($dbs as $db) {
+ $containersToStop[] = "{$db->name}-{$this->uuid}";
+ }
+
+ return $containersToStop;
+ }
+
+ public function stopContainers(array $containerNames, $server, int $timeout = 300)
+ {
+ $processes = [];
+ foreach ($containerNames as $containerName) {
+ $processes[$containerName] = $this->stopContainer($containerName, $timeout);
+ }
+
+ $startTime = time();
+ while (count($processes) > 0) {
+ $finishedProcesses = array_filter($processes, function ($process) {
+ return ! $process->running();
+ });
+ foreach (array_keys($finishedProcesses) as $containerName) {
+ unset($processes[$containerName]);
+ $this->removeContainer($containerName, $server);
+ }
+
+ if (time() - $startTime >= $timeout) {
+ $this->forceStopRemainingContainers(array_keys($processes), $server);
+ break;
+ }
+
+ usleep(100000);
+ }
+ }
+
+ public function stopContainer(string $containerName, int $timeout): InvokedProcess
+ {
+ return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
+ }
+
+ public function removeContainer(string $containerName, $server)
+ {
+ instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
+ }
+
+ public function forceStopRemainingContainers(array $containerNames, $server)
+ {
+ foreach ($containerNames as $containerName) {
+ instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
+ $this->removeContainer($containerName, $server);
+ }
+ }
+
public function delete_configurations()
{
- $server = data_get($this, 'server');
+ $server = data_get($this, 'destination.server');
$workdir = $this->workdir();
if (str($workdir)->endsWith($this->uuid)) {
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
}
}
+ public function delete_connected_networks($uuid)
+ {
+ $server = data_get($this, 'destination.server');
+ instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
+ instant_remote_process(["docker network rm {$uuid}"], $server, false);
+ }
+
public function status()
{
$applications = $this->applications;
@@ -215,9 +288,161 @@ class Service extends BaseModel
$fields = collect([]);
$applications = $this->applications()->get();
foreach ($applications as $application) {
- $image = str($application->image)->before(':')->value();
+ $image = str($application->image)->before(':');
+ if ($image->isEmpty()) {
+ continue;
+ }
switch ($image) {
- case str($image)?->contains('rabbitmq'):
+ case $image->contains('castopod'):
+ $data = collect([]);
+ $disable_https = $this->environment_variables()->where('key', 'CP_DISABLE_HTTPS')->first();
+ if ($disable_https) {
+ $data = $data->merge([
+ 'Disable HTTPS' => [
+ 'key' => 'CP_DISABLE_HTTPS',
+ 'value' => data_get($disable_https, 'value'),
+ 'rules' => 'required',
+ 'customHelper' => 'If you want to use https, set this to 0. Variable name: CP_DISABLE_HTTPS',
+ ],
+ ]);
+ }
+ $fields->put('Castopod', $data->toArray());
+ break;
+ case $image->contains('label-studio'):
+ $data = collect([]);
+ $username = $this->environment_variables()->where('key', 'LABEL_STUDIO_USERNAME')->first();
+ $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_LABELSTUDIO')->first();
+ if ($username) {
+ $data = $data->merge([
+ 'Username' => [
+ 'key' => 'LABEL_STUDIO_USERNAME',
+ 'value' => data_get($username, 'value'),
+ 'rules' => 'required',
+ ],
+ ]);
+ }
+ if ($password) {
+ $data = $data->merge([
+ 'Password' => [
+ 'key' => data_get($password, 'key'),
+ 'value' => data_get($password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ }
+ $fields->put('Label Studio', $data->toArray());
+ break;
+ case $image->contains('litellm'):
+ $data = collect([]);
+ $username = $this->environment_variables()->where('key', 'SERVICE_USER_UI')->first();
+ $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_UI')->first();
+ if ($username) {
+ $data = $data->merge([
+ 'Username' => [
+ 'key' => data_get($username, 'key'),
+ 'value' => data_get($username, 'value'),
+ 'rules' => 'required',
+ ],
+ ]);
+ }
+ if ($password) {
+ $data = $data->merge([
+ 'Password' => [
+ 'key' => data_get($password, 'key'),
+ 'value' => data_get($password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ }
+ $fields->put('Litellm', $data->toArray());
+ break;
+ case $image->contains('langfuse'):
+ $data = collect([]);
+ $email = $this->environment_variables()->where('key', 'LANGFUSE_INIT_USER_EMAIL')->first();
+ if ($email) {
+ $data = $data->merge([
+ 'Admin Email' => [
+ 'key' => data_get($email, 'key'),
+ 'value' => data_get($email, 'value'),
+ 'rules' => 'required|email',
+ ],
+ ]);
+ }
+ $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_LANGFUSE')->first();
+ if ($password) {
+ $data = $data->merge([
+ 'Admin Password' => [
+ 'key' => data_get($password, 'key'),
+ 'value' => data_get($password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ }
+ $fields->put('Langfuse', $data->toArray());
+ break;
+ case $image->contains('invoiceninja'):
+ $data = collect([]);
+ $email = $this->environment_variables()->where('key', 'IN_USER_EMAIL')->first();
+ $data = $data->merge([
+ 'Email' => [
+ 'key' => data_get($email, 'key'),
+ 'value' => data_get($email, 'value'),
+ 'rules' => 'required|email',
+ ],
+ ]);
+ $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_INVOICENINJAUSER')->first();
+ $data = $data->merge([
+ 'Password' => [
+ 'key' => data_get($password, 'key'),
+ 'value' => data_get($password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ $fields->put('Invoice Ninja', $data->toArray());
+ break;
+ case $image->contains('argilla'):
+ $data = collect([]);
+ $api_key = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_APIKEY')->first();
+ $data = $data->merge([
+ 'API Key' => [
+ 'key' => data_get($api_key, 'key'),
+ 'value' => data_get($api_key, 'value'),
+ 'isPassword' => true,
+ 'rules' => 'required',
+ ],
+ ]);
+ $data = $data->merge([
+ 'API Key' => [
+ 'key' => data_get($api_key, 'key'),
+ 'value' => data_get($api_key, 'value'),
+ 'isPassword' => true,
+ 'rules' => 'required',
+ ],
+ ]);
+ $username = $this->environment_variables()->where('key', 'ARGILLA_USERNAME')->first();
+ $data = $data->merge([
+ 'Username' => [
+ 'key' => data_get($username, 'key'),
+ 'value' => data_get($username, 'value'),
+ 'rules' => 'required',
+ ],
+ ]);
+ $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ARGILLA')->first();
+ $data = $data->merge([
+ 'Password' => [
+ 'key' => data_get($password, 'key'),
+ 'value' => data_get($password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ $fields->put('Argilla', $data->toArray());
+ break;
+ case $image->contains('rabbitmq'):
$data = collect([]);
$host_port = $this->environment_variables()->where('key', 'PORT')->first();
$username = $this->environment_variables()->where('key', 'SERVICE_USER_RABBITMQ')->first();
@@ -252,7 +477,7 @@ class Service extends BaseModel
}
$fields->put('RabbitMQ', $data->toArray());
break;
- case str($image)?->contains('tolgee'):
+ case $image->contains('tolgee'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
$data = $data->merge([
@@ -266,7 +491,7 @@ class Service extends BaseModel
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
- 'key' => 'SERVICE_PASSWORD_TOLGEE',
+ 'key' => data_get($admin_password, 'key'),
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -275,7 +500,7 @@ class Service extends BaseModel
}
$fields->put('Tolgee', $data->toArray());
break;
- case str($image)?->contains('logto'):
+ case $image->contains('logto'):
$data = collect([]);
$logto_endpoint = $this->environment_variables()->where('key', 'LOGTO_ENDPOINT')->first();
$logto_admin_endpoint = $this->environment_variables()->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
@@ -299,7 +524,7 @@ class Service extends BaseModel
}
$fields->put('Logto', $data->toArray());
break;
- case str($image)?->contains('unleash-server'):
+ case $image->contains('unleash-server'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_UNLEASH')->first();
$data = $data->merge([
@@ -313,7 +538,7 @@ class Service extends BaseModel
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
- 'key' => 'SERVICE_PASSWORD_UNLEASH',
+ 'key' => data_get($admin_password, 'key'),
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -322,7 +547,7 @@ class Service extends BaseModel
}
$fields->put('Unleash', $data->toArray());
break;
- case str($image)?->contains('grafana'):
+ case $image->contains('grafana'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first();
$data = $data->merge([
@@ -336,7 +561,7 @@ class Service extends BaseModel
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
- 'key' => 'GF_SECURITY_ADMIN_PASSWORD',
+ 'key' => data_get($admin_password, 'key'),
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
@@ -345,7 +570,7 @@ class Service extends BaseModel
}
$fields->put('Grafana', $data->toArray());
break;
- case str($image)?->contains('directus'):
+ case $image->contains('directus'):
$data = collect([]);
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
@@ -371,7 +596,7 @@ class Service extends BaseModel
}
$fields->put('Directus', $data->toArray());
break;
- case str($image)?->contains('kong'):
+ case $image->contains('kong'):
$data = collect([]);
$dashboard_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
$dashboard_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
@@ -395,7 +620,7 @@ class Service extends BaseModel
]);
}
$fields->put('Supabase', $data->toArray());
- case str($image)?->contains('minio'):
+ case $image->contains('minio'):
$data = collect([]);
$console_url = $this->environment_variables()->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first();
$s3_api_url = $this->environment_variables()->where('key', 'MINIO_SERVER_URL')->first();
@@ -448,7 +673,7 @@ class Service extends BaseModel
$fields->put('MinIO', $data->toArray());
break;
- case str($image)?->contains('weblate'):
+ case $image->contains('weblate'):
$data = collect([]);
$admin_email = $this->environment_variables()->where('key', 'WEBLATE_ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_WEBLATE')->first();
@@ -474,7 +699,7 @@ class Service extends BaseModel
}
$fields->put('Weblate', $data->toArray());
break;
- case str($image)?->contains('meilisearch'):
+ case $image->contains('meilisearch'):
$data = collect([]);
$SERVICE_PASSWORD_MEILISEARCH = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_MEILISEARCH')->first();
if ($SERVICE_PASSWORD_MEILISEARCH) {
@@ -488,7 +713,7 @@ class Service extends BaseModel
}
$fields->put('Meilisearch', $data->toArray());
break;
- case str($image)?->contains('ghost'):
+ case $image->contains('ghost'):
$data = collect([]);
$MAIL_OPTIONS_AUTH_PASS = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_PASS')->first();
$MAIL_OPTIONS_AUTH_USER = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_USER')->first();
@@ -548,45 +773,8 @@ class Service extends BaseModel
$fields->put('Ghost', $data->toArray());
break;
- default:
- $data = collect([]);
- $admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
- // Chaskiq
- $admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
- $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
- if ($admin_user) {
- $data = $data->merge([
- 'User' => [
- 'key' => 'SERVICE_USER_ADMIN',
- 'value' => data_get($admin_user, 'value', 'admin'),
- 'readonly' => true,
- 'rules' => 'required',
- ],
- ]);
- }
- if ($admin_password) {
- $data = $data->merge([
- 'Password' => [
- 'key' => 'SERVICE_PASSWORD_ADMIN',
- 'value' => data_get($admin_password, 'value'),
- 'rules' => 'required',
- 'isPassword' => true,
- ],
- ]);
- }
- if ($admin_email) {
- $data = $data->merge([
- 'Email' => [
- 'key' => 'ADMIN_EMAIL',
- 'value' => data_get($admin_email, 'value'),
- 'rules' => 'required|email',
- ],
- ]);
- }
- $fields->put('Admin', $data->toArray());
- break;
- case str($image)?->contains('vaultwarden'):
+ case $image->contains('vaultwarden'):
$data = collect([]);
$DATABASE_URL = $this->environment_variables()->where('key', 'DATABASE_URL')->first();
@@ -652,7 +840,7 @@ class Service extends BaseModel
$fields->put('Vaultwarden', $data);
break;
- case str($image)->contains('gitlab/gitlab'):
+ case $image->contains('gitlab/gitlab'):
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GITLAB')->first();
$data = collect([]);
if ($password) {
@@ -676,7 +864,7 @@ class Service extends BaseModel
$fields->put('GitLab', $data->toArray());
break;
- case str($image)->contains('code-server'):
+ case $image->contains('code-server'):
$data = collect([]);
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_PASSWORDCODESERVER')->first();
if ($password) {
@@ -702,14 +890,78 @@ class Service extends BaseModel
}
$fields->put('Code Server', $data->toArray());
break;
+ case $image->contains('elestio/strapi'):
+ $data = collect([]);
+ $license = $this->environment_variables()->where('key', 'STRAPI_LICENSE')->first();
+ if ($license) {
+ $data = $data->merge([
+ 'License' => [
+ 'key' => data_get($license, 'key'),
+ 'value' => data_get($license, 'value'),
+ ],
+ ]);
+ }
+ $nodeEnv = $this->environment_variables()->where('key', 'NODE_ENV')->first();
+ if ($nodeEnv) {
+ $data = $data->merge([
+ 'Node Environment' => [
+ 'key' => data_get($nodeEnv, 'key'),
+ 'value' => data_get($nodeEnv, 'value'),
+ ],
+ ]);
+ }
+
+ $fields->put('Strapi', $data->toArray());
+ break;
+ default:
+ $data = collect([]);
+ $admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
+ // Chaskiq
+ $admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
+
+ $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
+ if ($admin_user) {
+ $data = $data->merge([
+ 'User' => [
+ 'key' => data_get($admin_user, 'key'),
+ 'value' => data_get($admin_user, 'value', 'admin'),
+ 'readonly' => true,
+ 'rules' => 'required',
+ ],
+ ]);
+ }
+ if ($admin_password) {
+ $data = $data->merge([
+ 'Password' => [
+ 'key' => data_get($admin_password, 'key'),
+ 'value' => data_get($admin_password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ }
+ if ($admin_email) {
+ $data = $data->merge([
+ 'Email' => [
+ 'key' => data_get($admin_email, 'key'),
+ 'value' => data_get($admin_email, 'value'),
+ 'rules' => 'required|email',
+ ],
+ ]);
+ }
+ $fields->put('Admin', $data->toArray());
+ break;
}
}
$databases = $this->databases()->get();
foreach ($databases as $database) {
- $image = str($database->image)->before(':')->value();
+ $image = str($database->image)->before(':');
+ if ($image->isEmpty()) {
+ continue;
+ }
switch ($image) {
- case str($image)->contains('postgres'):
+ case $image->contains('postgres'):
$userVariables = ['SERVICE_USER_POSTGRES', 'SERVICE_USER_POSTGRESQL'];
$passwordVariables = ['SERVICE_PASSWORD_POSTGRES', 'SERVICE_PASSWORD_POSTGRESQL'];
$dbNameVariables = ['POSTGRESQL_DATABASE', 'POSTGRES_DB'];
@@ -747,10 +999,10 @@ class Service extends BaseModel
}
$fields->put('PostgreSQL', $data->toArray());
break;
- case str($image)->contains('mysql'):
+ case $image->contains('mysql'):
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS', 'MYSQL_USER'];
- $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD'];
- $rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT'];
+ $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD', 'SERVICE_PASSWORD_64_MYSQL'];
+ $rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT', 'SERVICE_PASSWORD_64_MYSQLROOT'];
$dbNameVariables = ['MYSQL_DATABASE'];
$mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
$mysql_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
@@ -797,7 +1049,7 @@ class Service extends BaseModel
}
$fields->put('MySQL', $data->toArray());
break;
- case str($image)->contains('mariadb'):
+ case $image->contains('mariadb'):
$userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER', 'SERVICE_USER_MYSQL', 'MYSQL_USER'];
$passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS', 'MYSQL_PASSWORD'];
$rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS', 'MYSQL_ROOT_PASSWORD'];
@@ -848,7 +1100,6 @@ class Service extends BaseModel
}
$fields->put('MariaDB', $data->toArray());
break;
-
}
}
@@ -920,7 +1171,7 @@ class Service extends BaseModel
$services = get_service_templates();
$service = data_get($services, str($this->name)->beforeLast('-')->value, []);
- return data_get($service, 'documentation', config('constants.docs.base_url'));
+ return data_get($service, 'documentation', config('constants.urls.docs'));
}
public function applications()
@@ -983,13 +1234,12 @@ class Service extends BaseModel
public function environment_variables(): HasMany
{
-
- return $this->hasMany(EnvironmentVariable::class)->orderByRaw("key LIKE 'SERVICE%' DESC, value ASC");
+ return $this->hasMany(EnvironmentVariable::class)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC");
}
public function environment_variables_preview(): HasMany
{
- return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderBy('key', 'asc');
+ return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC");
}
public function workdir()
@@ -1027,7 +1277,21 @@ class Service extends BaseModel
return 3;
});
foreach ($sorted as $env) {
- $commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
+ if (version_compare($env->version, '4.0.0-beta.347', '<=')) {
+ $commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
+ } else {
+ $real_value = $env->real_value;
+ if ($env->version === '4.0.0-beta.239') {
+ $real_value = $env->real_value;
+ } else {
+ if ($env->is_literal || $env->is_multiline) {
+ $real_value = '\''.$real_value.'\'';
+ } else {
+ $real_value = escapeEnvVariables($env->real_value);
+ }
+ }
+ $commands[] = "echo \"{$env->key}={$real_value}\" >> .env";
+ }
}
if ($sorted->count() === 0) {
$commands[] = 'touch .env';
@@ -1037,20 +1301,33 @@ class Service extends BaseModel
public function parse(bool $isNew = false): Collection
{
- if ($this->compose_parsing_version === '3') {
+ if ((int) $this->compose_parsing_version >= 3) {
return newParser($this);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile($this, $isNew);
} else {
return collect([]);
}
-
}
public function networks()
{
- $networks = getTopLevelNetworks($this);
+ return getTopLevelNetworks($this);
+ }
- return $networks;
+ protected function isDeployable(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ $envs = $this->environment_variables()->where('is_required', true)->get();
+ foreach ($envs as $env) {
+ if ($env->is_really_required) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ );
}
}
diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php
index d312fab96..5cafc9042 100644
--- a/app/Models/ServiceApplication.php
+++ b/app/Models/ServiceApplication.php
@@ -19,6 +19,11 @@ class ServiceApplication extends BaseModel
$service->persistentStorages()->delete();
$service->fileStorages()->delete();
});
+ static::saving(function ($service) {
+ if ($service->isDirty('status')) {
+ $service->forceFill(['last_online_at' => now()]);
+ }
+ });
}
public function restart()
@@ -32,6 +37,11 @@ class ServiceApplication extends BaseModel
return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
}
+ public static function ownedByCurrentTeam()
+ {
+ return ServiceApplication::whereRelation('service.environment.project.team', 'id', currentTeam()->id)->orderBy('name');
+ }
+
public function isRunning()
{
return str($this->status)->contains('running');
@@ -112,4 +122,9 @@ class ServiceApplication extends BaseModel
{
getFilesystemVolumesFromServer($this, $isInit);
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return false;
+ }
}
diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php
index 6b96738e8..5fdd52637 100644
--- a/app/Models/ServiceDatabase.php
+++ b/app/Models/ServiceDatabase.php
@@ -17,6 +17,21 @@ class ServiceDatabase extends BaseModel
$service->persistentStorages()->delete();
$service->fileStorages()->delete();
});
+ static::saving(function ($service) {
+ if ($service->isDirty('status')) {
+ $service->forceFill(['last_online_at' => now()]);
+ }
+ });
+ }
+
+ public static function ownedByCurrentTeamAPI(int $teamId)
+ {
+ return ServiceDatabase::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
+ }
+
+ public static function ownedByCurrentTeam()
+ {
+ return ServiceDatabase::whereRelation('service.environment.project.team', 'id', currentTeam()->id)->orderBy('name');
}
public function restart()
@@ -115,4 +130,13 @@ class ServiceDatabase extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
+
+ public function isBackupSolutionAvailable()
+ {
+ return str($this->databaseType())->contains('mysql') ||
+ str($this->databaseType())->contains('postgres') ||
+ str($this->databaseType())->contains('postgis') ||
+ str($this->databaseType())->contains('mariadb') ||
+ str($this->databaseType())->contains('mongodb');
+ }
}
diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php
index ee5c3becc..6d66c6854 100644
--- a/app/Models/StandaloneClickhouse.php
+++ b/app/Models/StandaloneClickhouse.php
@@ -38,6 +38,11 @@ class StandaloneClickhouse extends BaseModel
$database->environment_variables()->delete();
$database->tags()->detach();
});
+ static::saving(function ($database) {
+ if ($database->isDirty('status')) {
+ $database->forceFill(['last_online_at' => now()]);
+ }
+ });
}
protected function serverStatus(): Attribute
@@ -266,32 +271,52 @@ class StandaloneClickhouse extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function isBackupSolutionAvailable()
+ {
+ return false;
}
}
diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php
index 361abf110..f7d83f0a3 100644
--- a/app/Models/StandaloneDragonfly.php
+++ b/app/Models/StandaloneDragonfly.php
@@ -38,6 +38,11 @@ class StandaloneDragonfly extends BaseModel
$database->environment_variables()->delete();
$database->tags()->detach();
});
+ static::saving(function ($database) {
+ if ($database->isDirty('status')) {
+ $database->forceFill(['last_online_at' => now()]);
+ }
+ });
}
protected function serverStatus(): Attribute
@@ -266,32 +271,52 @@ class StandaloneDragonfly extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function isBackupSolutionAvailable()
+ {
+ return false;
}
}
diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php
index e05879371..083c743d9 100644
--- a/app/Models/StandaloneKeydb.php
+++ b/app/Models/StandaloneKeydb.php
@@ -38,6 +38,11 @@ class StandaloneKeydb extends BaseModel
$database->environment_variables()->delete();
$database->tags()->detach();
});
+ static::saving(function ($database) {
+ if ($database->isDirty('status')) {
+ $database->forceFill(['last_online_at' => now()]);
+ }
+ });
}
protected function serverStatus(): Attribute
@@ -266,32 +271,52 @@ class StandaloneKeydb extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function isBackupSolutionAvailable()
+ {
+ return false;
}
}
diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php
index c1e6c85d7..833dad6c4 100644
--- a/app/Models/StandaloneMariadb.php
+++ b/app/Models/StandaloneMariadb.php
@@ -38,6 +38,11 @@ class StandaloneMariadb extends BaseModel
$database->environment_variables()->delete();
$database->tags()->detach();
});
+ static::saving(function ($database) {
+ if ($database->isDirty('status')) {
+ $database->forceFill(['last_online_at' => now()]);
+ }
+ });
}
protected function serverStatus(): Attribute
@@ -266,32 +271,52 @@ class StandaloneMariadb extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function isBackupSolutionAvailable()
+ {
+ return true;
}
}
diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php
index e5ed0a5f4..dd8893180 100644
--- a/app/Models/StandaloneMongodb.php
+++ b/app/Models/StandaloneMongodb.php
@@ -42,6 +42,11 @@ class StandaloneMongodb extends BaseModel
$database->environment_variables()->delete();
$database->tags()->detach();
});
+ static::saving(function ($database) {
+ if ($database->isDirty('status')) {
+ $database->forceFill(['last_online_at' => now()]);
+ }
+ });
}
protected function serverStatus(): Attribute
@@ -286,32 +291,52 @@ class StandaloneMongodb extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function isBackupSolutionAvailable()
+ {
+ return true;
}
}
diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php
index bd4a7abb7..710fea1bc 100644
--- a/app/Models/StandaloneMysql.php
+++ b/app/Models/StandaloneMysql.php
@@ -39,6 +39,11 @@ class StandaloneMysql extends BaseModel
$database->environment_variables()->delete();
$database->tags()->detach();
});
+ static::saving(function ($database) {
+ if ($database->isDirty('status')) {
+ $database->forceFill(['last_online_at' => now()]);
+ }
+ });
}
protected function serverStatus(): Attribute
@@ -267,32 +272,52 @@ class StandaloneMysql extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function isBackupSolutionAvailable()
+ {
+ return true;
}
}
diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php
index db771c7cd..4a457a6cf 100644
--- a/app/Models/StandalonePostgresql.php
+++ b/app/Models/StandalonePostgresql.php
@@ -39,6 +39,11 @@ class StandalonePostgresql extends BaseModel
$database->environment_variables()->delete();
$database->tags()->detach();
});
+ static::saving(function ($database) {
+ if ($database->isDirty('status')) {
+ $database->forceFill(['last_online_at' => now()]);
+ }
+ });
}
public function workdir()
@@ -71,7 +76,6 @@ class StandalonePostgresql extends BaseModel
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
- ray('Deleting volume: '.$storage->name);
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
@@ -268,32 +272,52 @@ class StandalonePostgresql extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function isBackupSolutionAvailable()
+ {
+ return true;
+ }
+
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
}
}
diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php
index c524d4d03..826bb951c 100644
--- a/app/Models/StandaloneRedis.php
+++ b/app/Models/StandaloneRedis.php
@@ -34,6 +34,11 @@ class StandaloneRedis extends BaseModel
$database->environment_variables()->delete();
$database->tags()->detach();
});
+ static::saving(function ($database) {
+ if ($database->isDirty('status')) {
+ $database->forceFill(['last_online_at' => now()]);
+ }
+ });
}
protected function serverStatus(): Attribute
@@ -210,7 +215,12 @@ class StandaloneRedis extends BaseModel
protected function internalDbUrl(): Attribute
{
return new Attribute(
- get: fn () => "redis://:{$this->redis_password}@{$this->uuid}:6379/0",
+ get: function () {
+ $redis_version = $this->getRedisVersion();
+ $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : '';
+
+ return "redis://{$username_part}{$this->redis_password}@{$this->uuid}:6379/0";
+ }
);
}
@@ -219,7 +229,10 @@ class StandaloneRedis extends BaseModel
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
- return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
+ $redis_version = $this->getRedisVersion();
+ $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : '';
+
+ return "redis://{$username_part}{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
}
return null;
@@ -227,6 +240,13 @@ class StandaloneRedis extends BaseModel
);
}
+ public function getRedisVersion()
+ {
+ $image_parts = explode(':', $this->image);
+
+ return $image_parts[1] ?? '0.0';
+ }
+
public function environment()
{
return $this->belongsTo(Environment::class);
@@ -262,32 +282,81 @@ class StandaloneRedis extends BaseModel
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
- public function getMetrics(int $mins = 5)
+ public function getCpuMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
- if ($server->isMetricsEnabled()) {
- $from = now()->subMinutes($mins)->toIso8601ZuluString();
- $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
- if (str($metrics)->contains('error')) {
- $error = json_decode($metrics, true);
- $error = data_get($error, 'error', 'Something is not okay, are you okay?');
- if ($error == 'Unauthorized') {
- $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
- }
- throw new \Exception($error);
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
- $metrics = str($metrics)->explode("\n")->skip(1)->all();
- $parsedCollection = collect($metrics)->flatMap(function ($item) {
- return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
- $cpu_usage_percent = number_format($cpu_usage_percent, 2);
-
- return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
- });
- });
-
- return $parsedCollection->toArray();
+ throw new \Exception($error);
}
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['percent']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error === 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = json_decode($metrics, true);
+ $parsedCollection = collect($metrics)->map(function ($metric) {
+ return [(int) $metric['time'], (float) $metric['used']];
+ });
+
+ return $parsedCollection->toArray();
+ }
+
+ public function isBackupSolutionAvailable()
+ {
+ return false;
+ }
+
+ public function redisPassword(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ $password = $this->runtime_environment_variables()->where('key', 'REDIS_PASSWORD')->first();
+ if (! $password) {
+ return null;
+ }
+
+ return $password->value;
+ },
+
+ );
+ }
+
+ public function redisUsername(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ $username = $this->runtime_environment_variables()->where('key', 'REDIS_USERNAME')->first();
+ if (! $username) {
+ return null;
+ }
+
+ return $username->value;
+ }
+ );
}
}
diff --git a/app/Models/Team.php b/app/Models/Team.php
index 3f8e97bc5..e21aa3a25 100644
--- a/app/Models/Team.php
+++ b/app/Models/Team.php
@@ -34,6 +34,7 @@ use OpenApi\Attributes as OA;
'smtp_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via SMTP.'],
'smtp_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via SMTP.'],
'smtp_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via SMTP.'],
+ 'smtp_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via SMTP.'],
'discord_enabled' => ['type' => 'boolean', 'description' => 'Whether Discord is enabled or not.'],
'discord_webhook_url' => ['type' => 'string', 'description' => 'The Discord webhook URL.'],
'discord_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Discord.'],
@@ -41,6 +42,7 @@ use OpenApi\Attributes as OA;
'discord_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Discord.'],
'discord_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Discord.'],
'discord_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Discord.'],
+ 'discord_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via Discord.'],
'show_boarding' => ['type' => 'boolean', 'description' => 'Whether to show the boarding screen or not.'],
'resend_enabled' => ['type' => 'boolean', 'description' => 'Whether to enable resending or not.'],
'resend_api_key' => ['type' => 'string', 'description' => 'The resending API key.'],
@@ -56,6 +58,7 @@ use OpenApi\Attributes as OA;
'telegram_notifications_deployments_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram deployment message thread ID.'],
'telegram_notifications_status_changes_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram status change message thread ID.'],
'telegram_notifications_database_backups_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram database backup message thread ID.'],
+
'custom_server_limit' => ['type' => 'string', 'description' => 'The custom server limit.'],
'telegram_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Telegram.'],
'telegram_notifications_scheduled_tasks_thread_id' => ['type' => 'string', 'description' => 'The Telegram scheduled task message thread ID.'],
@@ -90,27 +93,22 @@ class Team extends Model implements SendsDiscord, SendsEmail
static::deleting(function ($team) {
$keys = $team->privateKeys;
foreach ($keys as $key) {
- ray('Deleting key: '.$key->name);
$key->delete();
}
$sources = $team->sources();
foreach ($sources as $source) {
- ray('Deleting source: '.$source->name);
$source->delete();
}
$tags = Tag::whereTeamId($team->id)->get();
foreach ($tags as $tag) {
- ray('Deleting tag: '.$tag->name);
$tag->delete();
}
$shared_variables = $team->environment_variables();
foreach ($shared_variables as $shared_variable) {
- ray('Deleting team shared variable: '.$shared_variable->name);
$shared_variable->delete();
}
$s3s = $team->s3s;
foreach ($s3s as $s3) {
- ray('Deleting s3: '.$s3->name);
$s3->delete();
}
});
@@ -133,9 +131,7 @@ class Team extends Model implements SendsDiscord, SendsEmail
{
$recipients = data_get($notification, 'emails', null);
if (is_null($recipients)) {
- $recipients = $this->members()->pluck('email')->toArray();
-
- return $recipients;
+ return $this->members()->pluck('email')->toArray();
}
return explode(',', $recipients);
@@ -164,15 +160,19 @@ class Team extends Model implements SendsDiscord, SendsEmail
if (currentTeam()->id === 0 && isDev()) {
return 9999999;
}
+ $team = Team::find(currentTeam()->id);
+ if (! $team) {
+ return 0;
+ }
- return Team::find(currentTeam()->id)->limits['serverLimit'];
+ return data_get($team, 'limits', 0);
}
public function limits(): Attribute
{
return Attribute::make(
get: function () {
- if (config('coolify.self_hosted') || $this->id === 0) {
+ if (config('constants.coolify.self_hosted') || $this->id === 0) {
$subscription = 'self-hosted';
} else {
$subscription = data_get($this, 'subscription');
@@ -187,9 +187,8 @@ class Team extends Model implements SendsDiscord, SendsEmail
} else {
$serverLimit = config('constants.limits.server')[strtolower($subscription)];
}
- $sharedEmailEnabled = config('constants.limits.email')[strtolower($subscription)];
- return ['serverLimit' => $serverLimit, 'sharedEmailEnabled' => $sharedEmailEnabled];
+ return $serverLimit ?? 2;
}
);
@@ -249,9 +248,8 @@ class Team extends Model implements SendsDiscord, SendsEmail
$sources = collect([]);
$github_apps = $this->hasMany(GithubApp::class)->whereisPublic(false)->get();
$gitlab_apps = $this->hasMany(GitlabApp::class)->whereisPublic(false)->get();
- $sources = $sources->merge($github_apps)->merge($gitlab_apps);
- return $sources;
+ return $sources->merge($github_apps)->merge($gitlab_apps);
}
public function s3s()
@@ -259,8 +257,15 @@ class Team extends Model implements SendsDiscord, SendsEmail
return $this->hasMany(S3Storage::class)->where('is_usable', true);
}
- public function trialEnded()
+ public function subscriptionEnded()
{
+ $this->subscription->update([
+ 'stripe_subscription_id' => null,
+ 'stripe_plan_id' => null,
+ 'stripe_cancel_at_period_end' => false,
+ 'stripe_invoice_paid' => false,
+ 'stripe_trial_already_ended' => false,
+ ]);
foreach ($this->servers as $server) {
$server->settings()->update([
'is_usable' => false,
@@ -269,16 +274,6 @@ class Team extends Model implements SendsDiscord, SendsEmail
}
}
- public function trialEndedButSubscribed()
- {
- foreach ($this->servers as $server) {
- $server->settings()->update([
- 'is_usable' => true,
- 'is_reachable' => true,
- ]);
- }
- }
-
public function isAnyNotificationEnabled()
{
if (isCloud()) {
diff --git a/app/Models/TeamInvitation.php b/app/Models/TeamInvitation.php
index c202710e2..bc1a90d58 100644
--- a/app/Models/TeamInvitation.php
+++ b/app/Models/TeamInvitation.php
@@ -20,11 +20,16 @@ class TeamInvitation extends Model
return $this->belongsTo(Team::class);
}
+ public static function ownedByCurrentTeam()
+ {
+ return TeamInvitation::whereTeamId(currentTeam()->id);
+ }
+
public function isValid()
{
$createdAt = $this->created_at;
- $diff = $createdAt->diffInMinutes(now());
- if ($diff <= config('constants.invitation.link.expiration')) {
+ $diff = $createdAt->diffInDays(now());
+ if ($diff <= config('constants.invitation.link.expiration_days')) {
return true;
} else {
$this->delete();
diff --git a/app/Models/User.php b/app/Models/User.php
index ecc4ef6b6..25fb33d66 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -10,6 +10,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\URL;
@@ -158,7 +159,7 @@ class User extends Authenticatable implements SendsEmail
public function isAdminFromSession()
{
- if (auth()->user()->id === 0) {
+ if (Auth::id() === 0) {
return true;
}
$teams = $this->teams()->get();
@@ -178,9 +179,9 @@ class User extends Authenticatable implements SendsEmail
public function isInstanceAdmin()
{
- $found_root_team = auth()->user()->teams->filter(function ($team) {
+ $found_root_team = Auth::user()->teams->filter(function ($team) {
if ($team->id == 0) {
- if (! auth()->user()->isAdmin()) {
+ if (! Auth::user()->isAdmin()) {
return false;
}
@@ -195,9 +196,9 @@ class User extends Authenticatable implements SendsEmail
public function currentTeam()
{
- return Cache::remember('team:'.auth()->user()->id, 3600, function () {
- if (is_null(data_get(session('currentTeam'), 'id')) && auth()->user()->teams->count() > 0) {
- return auth()->user()->teams[0];
+ return Cache::remember('team:'.Auth::id(), 3600, function () {
+ if (is_null(data_get(session('currentTeam'), 'id')) && Auth::user()->teams->count() > 0) {
+ return Auth::user()->teams[0];
}
return Team::find(session('currentTeam')->id);
@@ -206,7 +207,7 @@ class User extends Authenticatable implements SendsEmail
public function otherTeams()
{
- return auth()->user()->teams->filter(function ($team) {
+ return Auth::user()->teams->filter(function ($team) {
return $team->id != currentTeam()->id;
});
}
@@ -216,7 +217,7 @@ class User extends Authenticatable implements SendsEmail
if (data_get($this, 'pivot')) {
return $this->pivot->role;
}
- $user = auth()->user()->teams->where('id', currentTeam()->id)->first();
+ $user = Auth::user()->teams->where('id', currentTeam()->id)->first();
return data_get($user, 'pivot.role');
}
diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php
index 1809da368..fae8951fd 100644
--- a/app/Notifications/Application/DeploymentFailed.php
+++ b/app/Notifications/Application/DeploymentFailed.php
@@ -4,6 +4,7 @@ namespace App\Notifications\Application;
use App\Models\Application;
use App\Models\ApplicationPreview;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -33,6 +34,7 @@ class DeploymentFailed extends Notification implements ShouldQueue
public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)
{
+ $this->onQueue('high');
$this->application = $application;
$this->deployment_uuid = $deployment_uuid;
$this->preview = $preview;
@@ -72,14 +74,42 @@ class DeploymentFailed extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
if ($this->preview) {
- $message = 'Coolify: Pull request #'.$this->preview->pull_request_id.' of '.$this->application_name.' ('.$this->preview->fqdn.') deployment failed: ';
- $message .= '[View Deployment Logs]('.$this->deployment_url.')';
+ $message = new DiscordMessage(
+ title: ':cross_mark: Deployment failed',
+ description: 'Pull request: '.$this->preview->pull_request_id,
+ color: DiscordMessage::errorColor(),
+ isCritical: true,
+ );
+
+ $message->addField('Project', data_get($this->application, 'environment.project.name'), true);
+ $message->addField('Environment', $this->environment_name, true);
+ $message->addField('Name', $this->application_name, true);
+
+ $message->addField('Deployment Logs', '[Link]('.$this->deployment_url.')');
+ if ($this->fqdn) {
+ $message->addField('Domain', $this->fqdn, true);
+ }
} else {
- $message = 'Coolify: Deployment failed of '.$this->application_name.' ('.$this->fqdn.'): ';
- $message .= '[View Deployment Logs]('.$this->deployment_url.')';
+ if ($this->fqdn) {
+ $description = '[Open application]('.$this->fqdn.')';
+ } else {
+ $description = '';
+ }
+ $message = new DiscordMessage(
+ title: ':cross_mark: Deployment failed',
+ description: $description,
+ color: DiscordMessage::errorColor(),
+ isCritical: true,
+ );
+
+ $message->addField('Project', data_get($this->application, 'environment.project.name'), true);
+ $message->addField('Environment', $this->environment_name, true);
+ $message->addField('Name', $this->application_name, true);
+
+ $message->addField('Deployment Logs', '[Link]('.$this->deployment_url.')');
}
return $message;
diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php
index 5085065c2..bfdef9f25 100644
--- a/app/Notifications/Application/DeploymentSuccess.php
+++ b/app/Notifications/Application/DeploymentSuccess.php
@@ -4,6 +4,7 @@ namespace App\Notifications\Application;
use App\Models\Application;
use App\Models\ApplicationPreview;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -33,6 +34,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)
{
+ $this->onQueue('high');
$this->application = $application;
$this->deployment_uuid = $deployment_uuid;
$this->preview = $preview;
@@ -51,7 +53,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
$channels = setNotificationChannels($notifiable, 'deployments');
if (isCloud()) {
// TODO: Make batch notifications work with email
- $channels = array_diff($channels, ['App\Notifications\Channels\EmailChannel']);
+ $channels = array_diff($channels, [\App\Notifications\Channels\EmailChannel::class]);
}
return $channels;
@@ -78,24 +80,39 @@ class DeploymentSuccess extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
if ($this->preview) {
- $message = 'Coolify: New PR'.$this->preview->pull_request_id.' version successfully deployed of '.$this->application_name.'
+ $message = new DiscordMessage(
+ title: ':white_check_mark: Preview deployment successful',
+ description: 'Pull request: '.$this->preview->pull_request_id,
+ color: DiscordMessage::successColor(),
+ );
-';
if ($this->preview->fqdn) {
- $message .= '[Open Application]('.$this->preview->fqdn.') | ';
+ $message->addField('Application', '[Link]('.$this->preview->fqdn.')');
}
- $message .= '[Deployment logs]('.$this->deployment_url.')';
- } else {
- $message = 'Coolify: New version successfully deployed of '.$this->application_name.'
-';
+ $message->addField('Project', data_get($this->application, 'environment.project.name'), true);
+ $message->addField('Environment', $this->environment_name, true);
+ $message->addField('Name', $this->application_name, true);
+ $message->addField('Deployment logs', '[Link]('.$this->deployment_url.')');
+ } else {
if ($this->fqdn) {
- $message .= '[Open Application]('.$this->fqdn.') | ';
+ $description = '[Open application]('.$this->fqdn.')';
+ } else {
+ $description = '';
}
- $message .= '[Deployment logs]('.$this->deployment_url.')';
+ $message = new DiscordMessage(
+ title: ':white_check_mark: New version successfully deployed',
+ description: $description,
+ color: DiscordMessage::successColor(),
+ );
+ $message->addField('Project', data_get($this->application, 'environment.project.name'), true);
+ $message->addField('Environment', $this->environment_name, true);
+ $message->addField('Name', $this->application_name, true);
+
+ $message->addField('Deployment logs', '[Link]('.$this->deployment_url.')');
}
return $message;
diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php
index 53ed8a589..3b062effb 100644
--- a/app/Notifications/Application/StatusChanged.php
+++ b/app/Notifications/Application/StatusChanged.php
@@ -3,6 +3,7 @@
namespace App\Notifications\Application;
use App\Models\Application;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -26,6 +27,7 @@ class StatusChanged extends Notification implements ShouldQueue
public function __construct(public Application $resource)
{
+ $this->onQueue('high');
$this->resource_name = data_get($resource, 'name');
$this->project_uuid = data_get($resource, 'environment.project.uuid');
$this->environment_name = data_get($resource, 'environment.name');
@@ -55,14 +57,14 @@ class StatusChanged extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = 'Coolify: '.$this->resource_name.' has been stopped.
-
-';
- $message .= '[Open Application in Coolify]('.$this->resource_url.')';
-
- return $message;
+ return new DiscordMessage(
+ title: ':cross_mark: Application stopped',
+ description: '[Open Application in Coolify]('.$this->resource_url.')',
+ color: DiscordMessage::errorColor(),
+ isCritical: true,
+ );
}
public function toTelegram(): array
diff --git a/app/Notifications/Channels/DiscordChannel.php b/app/Notifications/Channels/DiscordChannel.php
index f1706f138..df7040f8f 100644
--- a/app/Notifications/Channels/DiscordChannel.php
+++ b/app/Notifications/Channels/DiscordChannel.php
@@ -12,11 +12,11 @@ class DiscordChannel
*/
public function send(SendsDiscord $notifiable, Notification $notification): void
{
- $message = $notification->toDiscord($notifiable);
+ $message = $notification->toDiscord();
$webhookUrl = $notifiable->routeNotificationForDiscord();
if (! $webhookUrl) {
return;
}
- dispatch(new SendMessageToDiscordJob($message, $webhookUrl));
+ SendMessageToDiscordJob::dispatch($message, $webhookUrl);
}
}
diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php
index 413d3de53..af9af978d 100644
--- a/app/Notifications/Channels/EmailChannel.php
+++ b/app/Notifications/Channels/EmailChannel.php
@@ -32,7 +32,6 @@ class EmailChannel
if ($error === 'No email settings found.') {
throw $e;
}
- ray($e->getMessage());
$message = "EmailChannel error: {$e->getMessage()}. Failed to send email to:";
if (isset($recipients)) {
$message .= implode(', ', $recipients);
diff --git a/app/Notifications/Channels/TelegramChannel.php b/app/Notifications/Channels/TelegramChannel.php
index b1a607651..958c46c21 100644
--- a/app/Notifications/Channels/TelegramChannel.php
+++ b/app/Notifications/Channels/TelegramChannel.php
@@ -18,29 +18,29 @@ class TelegramChannel
$topicsInstance = get_class($notification);
switch ($topicsInstance) {
- case 'App\Notifications\Test':
+ case \App\Notifications\Test::class:
$topicId = data_get($notifiable, 'telegram_notifications_test_message_thread_id');
break;
- case 'App\Notifications\Application\StatusChanged':
- case 'App\Notifications\Container\ContainerRestarted':
- case 'App\Notifications\Container\ContainerStopped':
+ case \App\Notifications\Application\StatusChanged::class:
+ case \App\Notifications\Container\ContainerRestarted::class:
+ case \App\Notifications\Container\ContainerStopped::class:
$topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id');
break;
- case 'App\Notifications\Application\DeploymentSuccess':
- case 'App\Notifications\Application\DeploymentFailed':
+ case \App\Notifications\Application\DeploymentSuccess::class:
+ case \App\Notifications\Application\DeploymentFailed::class:
$topicId = data_get($notifiable, 'telegram_notifications_deployments_message_thread_id');
break;
- case 'App\Notifications\Database\BackupSuccess':
- case 'App\Notifications\Database\BackupFailed':
+ case \App\Notifications\Database\BackupSuccess::class:
+ case \App\Notifications\Database\BackupFailed::class:
$topicId = data_get($notifiable, 'telegram_notifications_database_backups_message_thread_id');
break;
- case 'App\Notifications\ScheduledTask\TaskFailed':
+ case \App\Notifications\ScheduledTask\TaskFailed::class:
$topicId = data_get($notifiable, 'telegram_notifications_scheduled_tasks_thread_id');
break;
}
if (! $telegramToken || ! $chatId || ! $message) {
return;
}
- dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId, $topicId));
+ SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $topicId);
}
}
diff --git a/app/Notifications/Channels/TransactionalEmailChannel.php b/app/Notifications/Channels/TransactionalEmailChannel.php
index 549fc6cd3..cc7d76ebf 100644
--- a/app/Notifications/Channels/TransactionalEmailChannel.php
+++ b/app/Notifications/Channels/TransactionalEmailChannel.php
@@ -13,7 +13,7 @@ class TransactionalEmailChannel
{
public function send(User $notifiable, Notification $notification): void
{
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) {
Log::info('SMTP/Resend not enabled');
diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php
index 23f6de264..90dae63d4 100644
--- a/app/Notifications/Container/ContainerRestarted.php
+++ b/app/Notifications/Container/ContainerRestarted.php
@@ -3,6 +3,7 @@
namespace App\Notifications\Container;
use App\Models\Server;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -14,7 +15,10 @@ class ContainerRestarted extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public string $name, public Server $server, public ?string $url = null) {}
+ public function __construct(public string $name, public Server $server, public ?string $url = null)
+ {
+ $this->onQueue('high');
+ }
public function via(object $notifiable): array
{
@@ -34,9 +38,17 @@ class ContainerRestarted extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = "Coolify: A resource ({$this->name}) has been restarted automatically on {$this->server->name}";
+ $message = new DiscordMessage(
+ title: ':warning: Resource restarted',
+ description: "{$this->name} has been restarted automatically on {$this->server->name}.",
+ color: DiscordMessage::infoColor(),
+ );
+
+ if ($this->url) {
+ $message->addField('Resource', '[Link]('.$this->url.')');
+ }
return $message;
}
diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php
index bcf5e67a5..3c8103568 100644
--- a/app/Notifications/Container/ContainerStopped.php
+++ b/app/Notifications/Container/ContainerStopped.php
@@ -3,6 +3,7 @@
namespace App\Notifications\Container;
use App\Models\Server;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -14,7 +15,10 @@ class ContainerStopped extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public string $name, public Server $server, public ?string $url = null) {}
+ public function __construct(public string $name, public Server $server, public ?string $url = null)
+ {
+ $this->onQueue('high');
+ }
public function via(object $notifiable): array
{
@@ -34,9 +38,17 @@ class ContainerStopped extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- $message = "Coolify: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}";
+ $message = new DiscordMessage(
+ title: ':cross_mark: Resource stopped',
+ description: "{$this->name} has been stopped unexpectedly on {$this->server->name}.",
+ color: DiscordMessage::errorColor(),
+ );
+
+ if ($this->url) {
+ $message->addField('Resource', '[Link]('.$this->url.')');
+ }
return $message;
}
diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php
index 77024c05b..ba67db4ae 100644
--- a/app/Notifications/Database/BackupFailed.php
+++ b/app/Notifications/Database/BackupFailed.php
@@ -3,6 +3,7 @@
namespace App\Notifications\Database;
use App\Models\ScheduledDatabaseBackup;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -22,6 +23,7 @@ class BackupFailed extends Notification implements ShouldQueue
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output, public $database_name)
{
+ $this->onQueue('high');
$this->name = $database->name;
$this->frequency = $backup->frequency;
}
@@ -45,9 +47,19 @@ class BackupFailed extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason:\n{$this->output}";
+ $message = new DiscordMessage(
+ title: ':cross_mark: Database backup failed',
+ description: "Database backup for {$this->name} (db:{$this->database_name}) has FAILED.",
+ color: DiscordMessage::errorColor(),
+ isCritical: true,
+ );
+
+ $message->addField('Frequency', $this->frequency, true);
+ $message->addField('Output', $this->output);
+
+ return $message;
}
public function toTelegram(): array
diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php
index f8dc6eb56..669a8a034 100644
--- a/app/Notifications/Database/BackupSuccess.php
+++ b/app/Notifications/Database/BackupSuccess.php
@@ -3,6 +3,7 @@
namespace App\Notifications\Database;
use App\Models\ScheduledDatabaseBackup;
+use App\Notifications\Dto\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -22,6 +23,7 @@ class BackupSuccess extends Notification implements ShouldQueue
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $database_name)
{
+ $this->onQueue('high');
$this->name = $database->name;
$this->frequency = $backup->frequency;
}
@@ -44,15 +46,22 @@ class BackupSuccess extends Notification implements ShouldQueue
return $mail;
}
- public function toDiscord(): string
+ public function toDiscord(): DiscordMessage
{
- return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
+ $message = new DiscordMessage(
+ title: ':white_check_mark: Database backup successful',
+ description: "Database backup for {$this->name} (db:{$this->database_name}) was successful.",
+ color: DiscordMessage::successColor(),
+ );
+
+ $message->addField('Frequency', $this->frequency, true);
+
+ return $message;
}
public function toTelegram(): array
{
$message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
- ray($message);
return [
'message' => $message,
diff --git a/app/Notifications/Database/DailyBackup.php b/app/Notifications/Database/DailyBackup.php
deleted file mode 100644
index a51ac6283..000000000
--- a/app/Notifications/Database/DailyBackup.php
+++ /dev/null
@@ -1,50 +0,0 @@
-subject('Coolify: Daily backup statuses');
- $mail->view('emails.daily-backup', [
- 'databases' => $this->databases,
- ]);
-
- return $mail;
- }
-
- public function toDiscord(): string
- {
- return 'Coolify: Daily backup statuses';
- }
-
- public function toTelegram(): array
- {
- $message = 'Coolify: Daily backup statuses';
-
- return [
- 'message' => $message,
- ];
- }
-}
diff --git a/app/Notifications/Dto/DiscordMessage.php b/app/Notifications/Dto/DiscordMessage.php
new file mode 100644
index 000000000..856753dca
--- /dev/null
+++ b/app/Notifications/Dto/DiscordMessage.php
@@ -0,0 +1,83 @@
+fields[] = [
+ 'name' => $name,
+ 'value' => $value,
+ 'inline' => $inline,
+ ];
+
+ return $this;
+ }
+
+ public function toPayload(): array
+ {
+ $footerText = 'Coolify v'.config('version');
+ if (isCloud()) {
+ $footerText = 'Coolify Cloud';
+ }
+ $payload = [
+ 'embeds' => [
+ [
+ 'title' => $this->title,
+ 'description' => $this->description,
+ 'color' => $this->color,
+ 'fields' => $this->addTimestampToFields($this->fields),
+ 'footer' => [
+ 'text' => $footerText,
+ ],
+ ],
+ ],
+ ];
+ if ($this->isCritical) {
+ $payload['content'] = '@here';
+ }
+
+ return $payload;
+ }
+
+ private function addTimestampToFields(array $fields): array
+ {
+ $fields[] = [
+ 'name' => 'Time',
+ 'value' => '
{$app->name}.");
+ throw new \RuntimeException("Domain $naked_domain is already in use by another resource:
Link: {$app->name}");
}
} elseif ($domain) {
- throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:
{$app->name}.");
+ throw new \RuntimeException("Domain $naked_domain is already in use by another resource:
Link: {$app->name}");
}
}
}
@@ -1155,16 +1207,16 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
if ($domains->contains($naked_domain)) {
if (data_get($resource, 'uuid')) {
if ($resource->uuid !== $app->uuid) {
- throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:
{$app->name}.");
+ throw new \RuntimeException("Domain $naked_domain is already in use by another resource:
Link: {$app->service->name}");
}
} elseif ($domain) {
- throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:
{$app->name}.");
+ throw new \RuntimeException("Domain $naked_domain is already in use by another resource:
Link: {$app->service->name}");
}
}
}
}
if ($resource) {
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
if (data_get($settings, 'fqdn')) {
$domain = data_get($settings, 'fqdn');
if (str($domain)->endsWith('/')) {
@@ -1181,12 +1233,26 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
function parseCommandsByLineForSudo(Collection $commands, Server $server): array
{
$commands = $commands->map(function ($line) {
- if (! str($line)->startsWith('cd') && ! str($line)->startsWith('command') && ! str($line)->startsWith('echo') && ! str($line)->startsWith('true')) {
+ if (
+ ! str(trim($line))->startsWith([
+ 'cd',
+ 'command',
+ 'echo',
+ 'true',
+ 'if',
+ 'fi',
+ ])
+ ) {
return "sudo $line";
}
+ if (str(trim($line))->startsWith('if')) {
+ return str_replace('if', 'if sudo', $line);
+ }
+
return $line;
});
+
$commands = $commands->map(function ($line) use ($server) {
if (Str::startsWith($line, 'sudo mkdir -p')) {
return "$line && sudo chown -R $server->user:$server->user ".Str::after($line, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($line, 'sudo mkdir -p');
@@ -1194,6 +1260,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
return $line;
});
+
$commands = $commands->map(function ($line) {
$line = str($line);
if (str($line)->contains('$(')) {
@@ -1238,8 +1305,6 @@ function parseLineForSudo(string $command, Server $server): string
function get_public_ips()
{
try {
- echo "Refreshing public ips!\n";
- $settings = \App\Models\InstanceSettings::get();
[$first, $second] = Process::concurrently(function (Pool $pool) {
$pool->path(__DIR__)->command('curl -4s https://ifconfig.io');
$pool->path(__DIR__)->command('curl -6s https://ifconfig.io');
@@ -1253,8 +1318,12 @@ function get_public_ips()
return;
}
- $settings->update(['public_ipv4' => $ipv4]);
+ InstanceSettings::get()->update(['public_ipv4' => $ipv4]);
}
+ } catch (\Exception $e) {
+ echo "Error: {$e->getMessage()}\n";
+ }
+ try {
$ipv6 = $second->output();
if ($ipv6) {
$ipv6 = trim($ipv6);
@@ -1264,7 +1333,7 @@ function get_public_ips()
return;
}
- $settings->update(['public_ipv6' => $ipv6]);
+ InstanceSettings::get()->update(['public_ipv6' => $ipv6]);
}
} catch (\Throwable $e) {
echo "Error: {$e->getMessage()}\n";
@@ -1283,13 +1352,6 @@ function isAnyDeploymentInprogress()
exit(0);
}
-function generateSentinelToken()
-{
- $token = Str::random(64);
-
- return $token;
-}
-
function isBase64Encoded($strValue)
{
return base64_encode(base64_decode($strValue, true)) === $strValue;
@@ -1361,7 +1423,7 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
return $volume;
}
- if (get_class($resource) === "App\Models\Application") {
+ if (get_class($resource) === \App\Models\Application::class) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
} else {
$dir = base_configuration_dir().'/services/'.$resource->service->uuid;
@@ -1401,7 +1463,7 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull
}
}
$slugWithoutUuid = Str::slug($source, '-');
- if (get_class($resource) === "App\Models\Application") {
+ if (get_class($resource) === \App\Models\Application::class) {
$name = "{$resource->uuid}_{$slugWithoutUuid}";
} else {
$name = "{$resource->service->uuid}_{$slugWithoutUuid}";
@@ -1444,7 +1506,7 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull
function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, ?int $preview_id = null)
{
- if ($resource->getMorphClass() === 'App\Models\Service') {
+ if ($resource->getMorphClass() === \App\Models\Service::class) {
if ($resource->docker_compose_raw) {
try {
$yaml = Yaml::parse($resource->docker_compose_raw);
@@ -1588,7 +1650,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
- $topLevelNetworks->put($networkDetails, null);
+ if (is_string($networkDetails) || is_int($networkDetails)) {
+ $topLevelNetworks->put($networkDetails, null);
+ }
}
}
}
@@ -2156,10 +2220,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} else {
return collect([]);
}
- } elseif ($resource->getMorphClass() === 'App\Models\Application') {
+ } elseif ($resource->getMorphClass() === \App\Models\Application::class) {
try {
$yaml = Yaml::parse($resource->docker_compose_raw);
- } catch (\Exception $e) {
+ } catch (\Exception) {
return;
}
$server = $resource->destination->server;
@@ -2503,7 +2567,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
- $topLevelNetworks->put($networkDetails, null);
+ if (is_string($networkDetails) || is_int($networkDetails)) {
+ $topLevelNetworks->put($networkDetails, null);
+ }
}
}
}
@@ -2903,7 +2969,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
try {
$yaml = Yaml::parse($compose);
- } catch (\Exception $e) {
+ } catch (\Exception) {
return collect([]);
}
@@ -2932,10 +2998,11 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
}
$parsedServices = collect([]);
- ray()->clearAll();
+ // ray()->clearAll();
$allMagicEnvironments = collect([]);
foreach ($services as $serviceName => $service) {
+ $predefinedPort = null;
$magicEnvironments = collect([]);
$image = data_get_str($service, 'image');
$environment = collect(data_get($service, 'environment', []));
@@ -2944,12 +3011,41 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
if ($isService) {
+ $containerName = "$serviceName-{$resource->uuid}";
+
+ if ($serviceName === 'registry') {
+ $tempServiceName = 'docker-registry';
+ } else {
+ $tempServiceName = $serviceName;
+ }
+ if (str(data_get($service, 'image'))->contains('glitchtip')) {
+ $tempServiceName = 'glitchtip';
+ }
+ if ($serviceName === 'supabase-kong') {
+ $tempServiceName = 'supabase';
+ }
+ $serviceDefinition = data_get($allServices, $tempServiceName);
+ $predefinedPort = data_get($serviceDefinition, 'port');
+ if ($serviceName === 'plausible') {
+ $predefinedPort = '8000';
+ }
if ($isDatabase) {
- $savedService = ServiceDatabase::firstOrCreate([
- 'name' => $serviceName,
- 'image' => $image,
- 'service_id' => $resource->id,
- ]);
+ $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
+ if ($applicationFound) {
+ $savedService = $applicationFound;
+ $savedService = ServiceDatabase::firstOrCreate([
+ 'name' => $applicationFound->name,
+ 'image' => $applicationFound->image,
+ 'service_id' => $applicationFound->service_id,
+ ]);
+ $applicationFound->delete();
+ } else {
+ $savedService = ServiceDatabase::firstOrCreate([
+ 'name' => $serviceName,
+ 'image' => $image,
+ 'service_id' => $resource->id,
+ ]);
+ }
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
@@ -2995,8 +3091,10 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
if (substr_count(str($key)->value(), '_') === 3) {
$fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value();
+ $port = $key->afterLast('_')->value();
} else {
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
+ $port = null;
}
if ($isApplication) {
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
@@ -3007,19 +3105,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$fqdn = generateFqdn($server, "{$savedService->name}-$uuid");
}
}
- if ($value && get_class($value) === 'Illuminate\Support\Stringable' && $value->startsWith('/')) {
+
+ if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) {
$path = $value->value();
if ($path !== '/') {
$fqdn = "$fqdn$path";
}
}
+ $fqdnWithPort = $fqdn;
+ if ($port) {
+ $fqdnWithPort = "$fqdn:$port";
+ }
if ($isApplication && is_null($resource->fqdn)) {
data_forget($resource, 'environment_variables');
data_forget($resource, 'environment_variables_preview');
- $resource->fqdn = $fqdn;
+ $resource->fqdn = $fqdnWithPort;
$resource->save();
} elseif ($isService && is_null($savedService->fqdn)) {
- $savedService->fqdn = $fqdn;
+ $savedService->fqdn = $fqdnWithPort;
$savedService->save();
}
@@ -3048,12 +3151,11 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
}
$allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
-
if ($magicEnvironments->count() > 0) {
foreach ($magicEnvironments as $key => $value) {
$key = str($key);
$value = replaceVariables($value);
- $command = $key->after('SERVICE_')->before('_');
+ $command = parseCommandFromMagicEnvVariable($key);
$found = $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->first();
if ($found) {
continue;
@@ -3086,6 +3188,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
} elseif ($isService) {
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
}
+ $fqdn = str($fqdn)->replace('http://', '')->replace('https://', '');
$resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([
'key' => $key->value(),
$nameOfId => $resource->id,
@@ -3094,7 +3197,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
'is_build_time' => false,
'is_preview' => false,
]);
-
} else {
$value = generateEnvValue($command, $resource);
$resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([
@@ -3164,12 +3266,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if ($serviceName === 'plausible') {
$predefinedPort = '8000';
}
+
if ($isDatabase) {
- $savedService = ServiceDatabase::firstOrCreate([
- 'name' => $serviceName,
- 'image' => $image,
- 'service_id' => $resource->id,
- ]);
+ $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
+ if ($applicationFound) {
+ $savedService = $applicationFound;
+ $savedService = ServiceDatabase::firstOrCreate([
+ 'name' => $applicationFound->name,
+ 'image' => $applicationFound->image,
+ 'service_id' => $applicationFound->service_id,
+ ]);
+ $applicationFound->delete();
+ } else {
+ $savedService = ServiceDatabase::firstOrCreate([
+ 'name' => $serviceName,
+ 'image' => $image,
+ 'service_id' => $resource->id,
+ ]);
+ }
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
@@ -3239,7 +3353,15 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
} elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') {
$volume = $source->value().':'.$target->value();
} else {
- $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
+ if ((int) $resource->compose_parsing_version >= 4) {
+ if ($isApplication) {
+ $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
+ } elseif ($isService) {
+ $mainDirectory = str(base_configuration_dir().'/services/'.$uuid);
+ }
+ } else {
+ $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
+ }
$source = replaceLocalSource($source, $mainDirectory);
if ($isApplication && $isPullRequest) {
$source = $source."-pr-$pullRequestId";
@@ -3259,6 +3381,17 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
'resource_type' => get_class($originalResource),
]
);
+ if (isDev()) {
+ if ((int) $resource->compose_parsing_version >= 4) {
+ if ($isApplication) {
+ $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
+ } elseif ($isService) {
+ $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid);
+ }
+ } else {
+ $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
+ }
+ }
$volume = "$source:$target";
}
} elseif ($type->value() === 'volume') {
@@ -3442,6 +3575,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
]);
} else {
if ($value->startsWith('$')) {
+ $isRequired = false;
if ($value->contains(':-')) {
$value = replaceVariables($value);
$key = $value->before(':');
@@ -3456,13 +3590,28 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$key = $value->before(':');
$value = $value->after(':?');
+ $isRequired = true;
} elseif ($value->contains('?')) {
$value = replaceVariables($value);
$key = $value->before('?');
$value = $value->after('?');
+ $isRequired = true;
}
if ($originalValue->value() === $value->value()) {
+ // This means the variable does not have a default value, so it needs to be created in Coolify
+ $parsedKeyValue = replaceVariables($value);
+ $resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->firstOrCreate([
+ 'key' => $parsedKeyValue,
+ $nameOfId => $resource->id,
+ ], [
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ 'is_required' => $isRequired,
+ ]);
+ // Add the variable to the environment so it will be shown in the deployable compose file
+ $environment[$parsedKeyValue->value()] = $resource->environment_variables()->where('key', $parsedKeyValue)->where($nameOfId, $resource->id)->first()->value;
+
continue;
}
$resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([
@@ -3472,9 +3621,9 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
'value' => $value,
'is_build_time' => false,
'is_preview' => false,
+ 'is_required' => $isRequired,
]);
}
-
}
}
if ($isApplication) {
@@ -3555,9 +3704,32 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if ($environment->count() > 0) {
$environment = $environment->filter(function ($value, $key) {
return ! str($key)->startsWith('SERVICE_FQDN_');
+ })->map(function ($value, $key) use ($resource) {
+ // if value is empty, set it to null so if you set the environment variable in the .env file (Coolify's UI), it will used
+ if (str($value)->isEmpty()) {
+ if ($resource->environment_variables()->where('key', $key)->exists()) {
+ $value = $resource->environment_variables()->where('key', $key)->first()->value;
+ } else {
+ $value = null;
+ }
+ }
+
+ return $value;
});
}
$serviceLabels = $labels->merge($defaultLabels);
+ if ($serviceLabels->count() > 0) {
+ if ($isApplication) {
+ $isContainerLabelEscapeEnabled = data_get($resource, 'settings.is_container_label_escape_enabled');
+ } else {
+ $isContainerLabelEscapeEnabled = data_get($resource, 'is_container_label_escape_enabled');
+ }
+ if ($isContainerLabelEscapeEnabled) {
+ $serviceLabels = $serviceLabels->map(function ($value, $key) {
+ return escapeDollarSign($value);
+ });
+ }
+ }
if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
if ($isApplication) {
$shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels;
@@ -3625,7 +3797,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
service_name: $serviceName,
image: $image,
predefinedPort: $predefinedPort
-
));
}
}
@@ -3639,6 +3810,14 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
data_forget($service, 'volumes.*.is_directory');
data_forget($service, 'exclude_from_hc');
+ $volumesParsed = $volumesParsed->map(function ($volume) {
+ data_forget($volume, 'content');
+ data_forget($volume, 'is_directory');
+ data_forget($volume, 'isDirectory');
+
+ return $volume;
+ });
+
$payload = collect($service)->merge([
'container_name' => $containerName,
'restart' => $restart->value(),
@@ -3669,6 +3848,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$parsedServices->put($serviceName, $payload);
}
$topLevel->put('services', $parsedServices);
+
$customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
$topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
@@ -3725,6 +3905,8 @@ function isAssociativeArray($array)
*/
function add_coolify_default_environment_variables(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|Application|Service $resource, Collection &$where_to_add, ?Collection $where_to_check = null)
{
+ // Currently disabled
+ return;
if ($resource instanceof Service) {
$ip = $resource->server->ip;
} else {
@@ -3769,18 +3951,206 @@ function convertComposeEnvironmentToArray($environment)
{
$convertedServiceVariables = collect([]);
if (isAssociativeArray($environment)) {
+ // Example: $environment = ['FOO' => 'bar', 'BAZ' => 'qux'];
+ if ($environment instanceof Collection) {
+ $changedEnvironment = collect([]);
+ $environment->each(function ($value, $key) use ($changedEnvironment) {
+ if (is_numeric($key)) {
+ $parts = explode('=', $value, 2);
+ if (count($parts) === 2) {
+ $key = $parts[0];
+ $realValue = $parts[1] ?? '';
+ $changedEnvironment->put($key, $realValue);
+ } else {
+ $changedEnvironment->put($key, $value);
+ }
+ } else {
+ $changedEnvironment->put($key, $value);
+ }
+ });
+
+ return $changedEnvironment;
+ }
$convertedServiceVariables = $environment;
} else {
+ // Example: $environment = ['FOO=bar', 'BAZ=qux'];
foreach ($environment as $value) {
- $parts = explode('=', $value, 2);
- $key = $parts[0];
- $realValue = $parts[1] ?? '';
- if ($key) {
- $convertedServiceVariables->put($key, $realValue);
+ if (is_string($value)) {
+ $parts = explode('=', $value, 2);
+ $key = $parts[0];
+ $realValue = $parts[1] ?? '';
+ if ($key) {
+ $convertedServiceVariables->put($key, $realValue);
+ }
}
}
}
return $convertedServiceVariables;
-
+}
+function instanceSettings()
+{
+ return InstanceSettings::get();
+}
+
+function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id)
+{
+ $server = Server::find($server_id)->where('team_id', $team_id)->first();
+ if (! $server) {
+ return;
+ }
+ $uuid = new Cuid2;
+ $cloneCommand = "git clone --no-checkout -b $branch $repository .";
+ $workdir = rtrim($base_directory, '/');
+ $fileList = collect([".$workdir/coolify.json"]);
+ $commands = collect([
+ "rm -rf /tmp/{$uuid}",
+ "mkdir -p /tmp/{$uuid}",
+ "cd /tmp/{$uuid}",
+ $cloneCommand,
+ 'git sparse-checkout init --cone',
+ "git sparse-checkout set {$fileList->implode(' ')}",
+ 'git read-tree -mu HEAD',
+ "cat .$workdir/coolify.json",
+ 'rm -rf /tmp/{$uuid}',
+ ]);
+ try {
+ return instant_remote_process($commands, $server);
+ } catch (\Exception) {
+ // continue
+ }
+}
+
+function loggy($message = null, array $context = [])
+{
+ if (! isDev()) {
+ return;
+ }
+ if (function_exists('ray') && config('app.debug')) {
+ ray($message, $context);
+ }
+ if (is_null($message)) {
+ return app('log');
+ }
+
+ return app('log')->debug($message, $context);
+}
+function sslipDomainWarning(string $domains)
+{
+ $domains = str($domains)->trim()->explode(',');
+ $showSslipHttpsWarning = false;
+ $domains->each(function ($domain) use (&$showSslipHttpsWarning) {
+ if (str($domain)->contains('https') && str($domain)->contains('sslip')) {
+ $showSslipHttpsWarning = true;
+ }
+ });
+
+ return $showSslipHttpsWarning;
+}
+
+function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?callable $callbackOnSuccess = null): bool
+{
+ if (isDev()) {
+ $decaySeconds = 120;
+ }
+ $rateLimited = false;
+ $executed = RateLimiter::attempt(
+ $limiterKey,
+ $maxAttempts = 0,
+ function () use (&$rateLimited, &$limiterKey, $callbackOnSuccess) {
+ isDev() && loggy('Rate limit not reached for '.$limiterKey);
+ $rateLimited = false;
+
+ if ($callbackOnSuccess) {
+ $callbackOnSuccess();
+ }
+ },
+ $decaySeconds,
+ );
+ if (! $executed) {
+ isDev() && loggy('Rate limit reached for '.$limiterKey.'. Rate limiter will be disabled for '.$decaySeconds.' seconds.');
+ $rateLimited = true;
+ }
+
+ return $rateLimited;
+}
+
+function defaultNginxConfiguration(): string
+{
+ return 'server {
+ location / {
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ try_files $uri $uri.html $uri/index.html $uri/index.htm $uri/ /index.html /index.htm =404;
+ }
+
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root /usr/share/nginx/html;
+ try_files $uri @redirect_to_index;
+ internal;
+ }
+
+ error_page 404 = @handle_404;
+
+ location @handle_404 {
+ root /usr/share/nginx/html;
+ try_files /404.html @redirect_to_index;
+ internal;
+ }
+
+ location @redirect_to_index {
+ return 302 /;
+ }
+}';
+}
+
+function convertGitUrl(string $gitRepository, string $deploymentType, ?GithubApp $source = null): array
+{
+ $repository = $gitRepository;
+ $providerInfo = [
+ 'host' => null,
+ 'user' => 'git',
+ 'port' => 22,
+ 'repository' => $gitRepository,
+ ];
+ $sshMatches = [];
+ $matches = [];
+
+ // Let's try and parse the string to detect if it's a valid SSH string or not
+ preg_match('/((.*?)\:\/\/)?(.*@.*:.*)/', $gitRepository, $sshMatches);
+
+ if ($deploymentType === 'deploy_key' && empty($sshMatches) && $source) {
+ // If this happens, the user may have provided an HTTP URL when they needed an SSH one
+ // Let's try and fix that for known Git providers
+ switch ($source->getMorphClass()) {
+ case \App\Models\GithubApp::class:
+ $providerInfo['host'] = Url::fromString($source->html_url)->getHost();
+ $providerInfo['port'] = $source->custom_port;
+ $providerInfo['user'] = $source->custom_user;
+ break;
+ }
+ if (! empty($providerInfo['host'])) {
+ // Until we do not support more providers with App (like GithubApp), this will be always true, port will be 22
+ if ($providerInfo['port'] === 22) {
+ $repository = "{$providerInfo['user']}@{$providerInfo['host']}:{$providerInfo['repository']}";
+ } else {
+ $repository = "ssh://{$providerInfo['user']}@{$providerInfo['host']}:{$providerInfo['port']}/{$providerInfo['repository']}";
+ }
+ }
+ }
+
+ preg_match('/(?<=:)\d+(?=\/)/', $gitRepository, $matches);
+
+ if (count($matches) === 1) {
+ $providerInfo['port'] = $matches[0];
+ $gitHost = str($gitRepository)->before(':');
+ $gitRepo = str($gitRepository)->after('/');
+ $repository = "$gitHost:$gitRepo";
+ }
+
+ return [
+ 'repository' => $repository,
+ 'port' => $providerInfo['port'],
+ ];
}
diff --git a/bootstrap/helpers/socialite.php b/bootstrap/helpers/socialite.php
index a23dc24d3..cad9de7fa 100644
--- a/bootstrap/helpers/socialite.php
+++ b/bootstrap/helpers/socialite.php
@@ -7,7 +7,7 @@ function get_socialite_provider(string $provider)
{
$oauth_setting = OauthSetting::firstWhere('provider', $provider);
- if ($provider == 'azure') {
+ if ($provider === 'azure') {
$azure_config = new \SocialiteProviders\Manager\Config(
$oauth_setting->client_id,
$oauth_setting->client_secret,
diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php
index aadd2dd34..8ddb1331c 100644
--- a/bootstrap/helpers/subscriptions.php
+++ b/bootstrap/helpers/subscriptions.php
@@ -55,12 +55,11 @@ function getStripeCustomerPortalSession(Team $team)
if (! $stripe_customer_id) {
return null;
}
- $session = \Stripe\BillingPortal\Session::create([
+
+ return \Stripe\BillingPortal\Session::create([
'customer' => $stripe_customer_id,
'return_url' => $return_url,
]);
-
- return $session;
}
function allowedPathsForUnsubscribedAccounts()
{
diff --git a/composer.json b/composer.json
index e8b46105d..694bad882 100644
--- a/composer.json
+++ b/composer.json
@@ -1,96 +1,96 @@
{
- "name": "laravel/laravel",
+ "name": "coollabsio/coolify",
+ "description": "The Coolify project.",
+ "license": "Apache-2.0",
"type": "project",
- "description": "The Laravel Framework.",
"keywords": [
- "framework",
- "laravel"
+ "coolify",
+ "deployment",
+ "docker",
+ "self-hosted",
+ "server"
],
- "license": "MIT",
"require": {
"php": "^8.2",
+ "3sidedcube/laravel-redoc": "^1.0",
"danharrin/livewire-rate-limiting": "^1.1",
- "doctrine/dbal": "^3.6",
+ "doctrine/dbal": "^4.2",
"guzzlehttp/guzzle": "^7.5.0",
- "laravel/fortify": "^v1.16.0",
- "laravel/framework": "^v11",
- "laravel/horizon": "^5.27.1",
- "laravel/prompts": "^0.1.6",
- "laravel/sanctum": "^v4.0",
- "laravel/socialite": "^v5.14.0",
- "laravel/telescope": "^5.2",
- "laravel/tinker": "^v2.8.1",
+ "laravel/fortify": "^1.16.0",
+ "laravel/framework": "^11.0",
+ "laravel/horizon": "^5.29.1",
+ "laravel/pail": "^1.1",
+ "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0",
+ "laravel/sanctum": "^4.0",
+ "laravel/socialite": "^5.14.0",
+ "laravel/tinker": "^2.8.1",
"laravel/ui": "^4.2",
"lcobucci/jwt": "^5.0.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/flysystem-sftp-v3": "^3.0",
- "livewire/livewire": "3.4.9",
+ "livewire/livewire": "^3.5",
"log1x/laravel-webfonts": "^1.0",
"lorisleiva/laravel-actions": "^2.7",
"nubs/random-name-generator": "^2.2",
- "phpseclib/phpseclib": "~3.0",
+ "phpseclib/phpseclib": "^3.0",
"pion/laravel-chunk-upload": "^1.5",
"poliander/cron": "^3.0",
"purplepixie/phpdns": "^2.1",
"pusher/pusher-php-server": "^7.2",
- "resend/resend-laravel": "^0.13.0",
+ "resend/resend-laravel": "^0.15.0",
"sentry/sentry-laravel": "^4.6",
"socialiteproviders/microsoft-azure": "^5.1",
"spatie/laravel-activitylog": "^4.7.3",
- "spatie/laravel-data": "^3.4.3",
- "spatie/laravel-ray": "^1.32.4",
+ "spatie/laravel-data": "^4.11",
+ "spatie/laravel-ray": "^1.37",
"spatie/laravel-schemaless-attributes": "^2.4",
"spatie/url": "^2.2",
- "stripe/stripe-php": "^12.0",
- "symfony/yaml": "^6.2",
- "visus/cuid2": "^2.0.0",
+ "stripe/stripe-php": "^16.2.0",
+ "symfony/yaml": "^7.1.6",
+ "visus/cuid2": "^4.1.0",
"yosymfony/toml": "^1.0",
"zircote/swagger-php": "^4.10"
},
"require-dev": {
- "fakerphp/faker": "^v1.21.0",
- "laravel/dusk": "^v8.0",
+ "barryvdh/laravel-debugbar": "^3.13",
+ "fakerphp/faker": "^1.21.0",
+ "laravel/dusk": "^8.0",
"laravel/pint": "^1.16",
+ "laravel/telescope": "^5.2",
"mockery/mockery": "^1.5.1",
- "nunomaduro/collision": "^v8.1",
- "pestphp/pest": "^2.16",
- "phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^10.0.19",
- "serversideup/spin": "^v1.1.0",
+ "nunomaduro/collision": "^8.1",
+ "pestphp/pest": "^3.5",
+ "phpstan/phpstan": "^1.12.10",
+ "phpunit/phpunit": "^11.4",
+ "serversideup/spin": "^2.3",
"spatie/laravel-ignition": "^2.1.0",
- "symfony/http-client": "^6.2"
+ "symfony/http-client": "^7.1"
},
+ "minimum-stability": "stable",
+ "prefer-stable": true,
"autoload": {
- "files": [
- "bootstrap/includeHelpers.php"
- ],
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
- }
+ },
+ "files": [
+ "bootstrap/includeHelpers.php"
+ ]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
- "scripts": {
- "post-autoload-dump": [
- "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
- "@php artisan package:discover --ansi"
- ],
- "post-update-cmd": [
- "@php artisan vendor:publish --tag=laravel-assets --ansi --force",
- "Illuminate\\Foundation\\ComposerScripts::postUpdate"
- ],
- "post-install-cmd": [],
- "post-root-package-install": [
- "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
- ],
- "post-create-project-cmd": [
- "@php artisan key:generate --ansi"
- ]
+ "config": {
+ "allow-plugins": {
+ "pestphp/pest-plugin": true,
+ "php-http/discovery": true
+ },
+ "optimize-autoloader": true,
+ "preferred-install": "dist",
+ "sort-packages": true
},
"extra": {
"laravel": {
@@ -99,15 +99,25 @@
]
}
},
- "config": {
- "optimize-autoloader": true,
- "preferred-install": "dist",
- "sort-packages": true,
- "allow-plugins": {
- "pestphp/pest-plugin": true,
- "php-http/discovery": true
- }
- },
- "minimum-stability": "stable",
- "prefer-stable": true
+ "scripts": {
+ "post-install-cmd": [
+ "cp -r 'hooks/' '.git/hooks/'",
+ "php -r \"copy('hooks/pre-commit', '.git/hooks/pre-commit');\"",
+ "php -r \"chmod('.git/hooks/pre-commit', 0777);\""
+ ],
+ "post-update-cmd": [
+ "@php artisan vendor:publish --tag=laravel-assets --ansi --force",
+ "Illuminate\\Foundation\\ComposerScripts::postUpdate"
+ ],
+ "post-autoload-dump": [
+ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
+ "@php artisan package:discover --ansi"
+ ],
+ "post-root-package-install": [
+ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
+ ],
+ "post-create-project-cmd": [
+ "@php artisan key:generate --ansi"
+ ]
+ }
}
diff --git a/composer.lock b/composer.lock
index fffb320d3..8ea0d9a5a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,66 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "96f8146407d0e6e897ff097c5eccd3a4",
+ "content-hash": "f50de759f43a3eefb58ce9ebbb02d33b",
"packages": [
+ {
+ "name": "3sidedcube/laravel-redoc",
+ "version": "v1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/3sidedcube/laravel-redoc.git",
+ "reference": "c33a563885dcdf1e0f623df5a56c106d130261da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/3sidedcube/laravel-redoc/zipball/c33a563885dcdf1e0f623df5a56c106d130261da",
+ "reference": "c33a563885dcdf1e0f623df5a56c106d130261da",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/routing": "^8.0|^9.0|^10.0|^11.0",
+ "illuminate/support": "^8.0|^9.0|^10.0|^11.0",
+ "php": "^7.4|^8.0|^8.1|^8.2|^8.3"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.3",
+ "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "ThreeSidedCube\\LaravelRedoc\\RedocServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "ThreeSidedCube\\LaravelRedoc\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ben Sherred",
+ "role": "Developer"
+ }
+ ],
+ "description": "A lightweight package for rendering API documentation using OpenAPI and Redoc.",
+ "homepage": "https://github.com/3sidedcube/laravel-redoc",
+ "keywords": [
+ "3sidedcube",
+ "laravel-redoc"
+ ],
+ "support": {
+ "issues": "https://github.com/3sidedcube/laravel-redoc/issues",
+ "source": "https://github.com/3sidedcube/laravel-redoc/tree/v1.0.1"
+ },
+ "time": "2024-05-20T11:37:55+00:00"
+ },
{
"name": "amphp/amp",
"version": "v3.0.2",
@@ -317,16 +375,16 @@
},
{
"name": "amphp/parallel",
- "version": "v2.2.9",
+ "version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/parallel.git",
- "reference": "73d293f1fc4df1bebc3c4fce1432e82dd7032238"
+ "reference": "9777db1460d1535bc2a843840684fb1205225b87"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/amphp/parallel/zipball/73d293f1fc4df1bebc3c4fce1432e82dd7032238",
- "reference": "73d293f1fc4df1bebc3c4fce1432e82dd7032238",
+ "url": "https://api.github.com/repos/amphp/parallel/zipball/9777db1460d1535bc2a843840684fb1205225b87",
+ "reference": "9777db1460d1535bc2a843840684fb1205225b87",
"shasum": ""
},
"require": {
@@ -389,7 +447,7 @@
],
"support": {
"issues": "https://github.com/amphp/parallel/issues",
- "source": "https://github.com/amphp/parallel/tree/v2.2.9"
+ "source": "https://github.com/amphp/parallel/tree/v2.3.0"
},
"funding": [
{
@@ -397,7 +455,7 @@
"type": "github"
}
],
- "time": "2024-03-24T18:27:44+00:00"
+ "time": "2024-09-14T19:16:14+00:00"
},
{
"name": "amphp/parser",
@@ -867,16 +925,16 @@
},
{
"name": "aws/aws-crt-php",
- "version": "v1.2.6",
+ "version": "v1.2.7",
"source": {
"type": "git",
"url": "https://github.com/awslabs/aws-crt-php.git",
- "reference": "a63485b65b6b3367039306496d49737cf1995408"
+ "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/a63485b65b6b3367039306496d49737cf1995408",
- "reference": "a63485b65b6b3367039306496d49737cf1995408",
+ "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e",
+ "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e",
"shasum": ""
},
"require": {
@@ -915,22 +973,22 @@
],
"support": {
"issues": "https://github.com/awslabs/aws-crt-php/issues",
- "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.6"
+ "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7"
},
- "time": "2024-06-13T17:21:28+00:00"
+ "time": "2024-10-18T22:15:13+00:00"
},
{
"name": "aws/aws-sdk-php",
- "version": "3.321.9",
+ "version": "3.327.1",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "5de5099cfe0e17cb3eb2fe51de0101c99bc9442a"
+ "reference": "3d52ec587989b136e486f94eff3dd316465aeb42"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5de5099cfe0e17cb3eb2fe51de0101c99bc9442a",
- "reference": "5de5099cfe0e17cb3eb2fe51de0101c99bc9442a",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3d52ec587989b136e486f94eff3dd316465aeb42",
+ "reference": "3d52ec587989b136e486f94eff3dd316465aeb42",
"shasum": ""
},
"require": {
@@ -1013,22 +1071,22 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.321.9"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.327.1"
},
- "time": "2024-09-11T18:15:49+00:00"
+ "time": "2024-11-15T01:53:30+00:00"
},
{
"name": "bacon/bacon-qr-code",
- "version": "v3.0.0",
+ "version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/Bacon/BaconQrCode.git",
- "reference": "510de6eca6248d77d31b339d62437cc995e2fb41"
+ "reference": "f9cc1f52b5a463062251d666761178dbdb6b544f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/510de6eca6248d77d31b339d62437cc995e2fb41",
- "reference": "510de6eca6248d77d31b339d62437cc995e2fb41",
+ "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/f9cc1f52b5a463062251d666761178dbdb6b544f",
+ "reference": "f9cc1f52b5a463062251d666761178dbdb6b544f",
"shasum": ""
},
"require": {
@@ -1067,9 +1125,9 @@
"homepage": "https://github.com/Bacon/BaconQrCode",
"support": {
"issues": "https://github.com/Bacon/BaconQrCode/issues",
- "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.0"
+ "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.1"
},
- "time": "2024-04-18T11:16:25+00:00"
+ "time": "2024-10-01T13:55:55+00:00"
},
{
"name": "brick/math",
@@ -1133,26 +1191,26 @@
},
{
"name": "carbonphp/carbon-doctrine-types",
- "version": "2.1.0",
+ "version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/CarbonPHP/carbon-doctrine-types.git",
- "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb"
+ "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb",
- "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
+ "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
"shasum": ""
},
"require": {
- "php": "^7.4 || ^8.0"
+ "php": "^8.1"
},
"conflict": {
- "doctrine/dbal": "<3.7.0 || >=4.0.0"
+ "doctrine/dbal": "<4.0.0 || >=5.0.0"
},
"require-dev": {
- "doctrine/dbal": "^3.7.0",
+ "doctrine/dbal": "^4.0.0",
"nesbot/carbon": "^2.71.0 || ^3.0.0",
"phpunit/phpunit": "^10.3"
},
@@ -1182,7 +1240,7 @@
],
"support": {
"issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues",
- "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0"
+ "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0"
},
"funding": [
{
@@ -1198,7 +1256,7 @@
"type": "tidelift"
}
],
- "time": "2023-12-11T17:09:12+00:00"
+ "time": "2024-02-09T16:56:22+00:00"
},
{
"name": "danharrin/livewire-rate-limiting",
@@ -1423,142 +1481,44 @@
},
"time": "2024-07-08T12:26:09+00:00"
},
- {
- "name": "doctrine/cache",
- "version": "2.2.0",
- "source": {
- "type": "git",
- "url": "https://github.com/doctrine/cache.git",
- "reference": "1ca8f21980e770095a31456042471a57bc4c68fb"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb",
- "reference": "1ca8f21980e770095a31456042471a57bc4c68fb",
- "shasum": ""
- },
- "require": {
- "php": "~7.1 || ^8.0"
- },
- "conflict": {
- "doctrine/common": ">2.2,<2.4"
- },
- "require-dev": {
- "cache/integration-tests": "dev-master",
- "doctrine/coding-standard": "^9",
- "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
- "psr/cache": "^1.0 || ^2.0 || ^3.0",
- "symfony/cache": "^4.4 || ^5.4 || ^6",
- "symfony/var-exporter": "^4.4 || ^5.4 || ^6"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Guilherme Blanco",
- "email": "guilhermeblanco@gmail.com"
- },
- {
- "name": "Roman Borschel",
- "email": "roman@code-factory.org"
- },
- {
- "name": "Benjamin Eberlei",
- "email": "kontakt@beberlei.de"
- },
- {
- "name": "Jonathan Wage",
- "email": "jonwage@gmail.com"
- },
- {
- "name": "Johannes Schmitt",
- "email": "schmittjoh@gmail.com"
- }
- ],
- "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.",
- "homepage": "https://www.doctrine-project.org/projects/cache.html",
- "keywords": [
- "abstraction",
- "apcu",
- "cache",
- "caching",
- "couchdb",
- "memcached",
- "php",
- "redis",
- "xcache"
- ],
- "support": {
- "issues": "https://github.com/doctrine/cache/issues",
- "source": "https://github.com/doctrine/cache/tree/2.2.0"
- },
- "funding": [
- {
- "url": "https://www.doctrine-project.org/sponsorship.html",
- "type": "custom"
- },
- {
- "url": "https://www.patreon.com/phpdoctrine",
- "type": "patreon"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache",
- "type": "tidelift"
- }
- ],
- "time": "2022-05-20T20:07:39+00:00"
- },
{
"name": "doctrine/dbal",
- "version": "3.9.1",
+ "version": "4.2.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
- "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7"
+ "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/dbal/zipball/d7dc08f98cba352b2bab5d32c5e58f7e745c11a7",
- "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/dadd35300837a3a2184bd47d403333b15d0a9bd0",
+ "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0",
"shasum": ""
},
"require": {
- "composer-runtime-api": "^2",
- "doctrine/cache": "^1.11|^2.0",
"doctrine/deprecations": "^0.5.3|^1",
- "doctrine/event-manager": "^1|^2",
- "php": "^7.4 || ^8.0",
+ "php": "^8.1",
"psr/cache": "^1|^2|^3",
"psr/log": "^1|^2|^3"
},
"require-dev": {
"doctrine/coding-standard": "12.0.0",
"fig/log-test": "^1",
- "jetbrains/phpstorm-stubs": "2023.1",
- "phpstan/phpstan": "1.12.0",
+ "jetbrains/phpstorm-stubs": "2023.2",
+ "phpstan/phpstan": "1.12.6",
+ "phpstan/phpstan-phpunit": "1.4.0",
"phpstan/phpstan-strict-rules": "^1.6",
- "phpunit/phpunit": "9.6.20",
- "psalm/plugin-phpunit": "0.18.4",
+ "phpunit/phpunit": "10.5.30",
+ "psalm/plugin-phpunit": "0.19.0",
"slevomat/coding-standard": "8.13.1",
"squizlabs/php_codesniffer": "3.10.2",
- "symfony/cache": "^5.4|^6.0|^7.0",
- "symfony/console": "^4.4|^5.4|^6.0|^7.0",
- "vimeo/psalm": "4.30.0"
+ "symfony/cache": "^6.3.8|^7.0",
+ "symfony/console": "^5.4|^6.3|^7.0",
+ "vimeo/psalm": "5.25.0"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
},
- "bin": [
- "bin/doctrine-dbal"
- ],
"type": "library",
"autoload": {
"psr-4": {
@@ -1611,7 +1571,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
- "source": "https://github.com/doctrine/dbal/tree/3.9.1"
+ "source": "https://github.com/doctrine/dbal/tree/4.2.1"
},
"funding": [
{
@@ -1627,7 +1587,7 @@
"type": "tidelift"
}
],
- "time": "2024-09-01T13:49:23+00:00"
+ "time": "2024-10-10T18:01:27+00:00"
},
{
"name": "doctrine/deprecations",
@@ -1676,97 +1636,6 @@
},
"time": "2024-01-30T19:34:25+00:00"
},
- {
- "name": "doctrine/event-manager",
- "version": "2.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/doctrine/event-manager.git",
- "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e",
- "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e",
- "shasum": ""
- },
- "require": {
- "php": "^8.1"
- },
- "conflict": {
- "doctrine/common": "<2.9"
- },
- "require-dev": {
- "doctrine/coding-standard": "^12",
- "phpstan/phpstan": "^1.8.8",
- "phpunit/phpunit": "^10.5",
- "vimeo/psalm": "^5.24"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Doctrine\\Common\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Guilherme Blanco",
- "email": "guilhermeblanco@gmail.com"
- },
- {
- "name": "Roman Borschel",
- "email": "roman@code-factory.org"
- },
- {
- "name": "Benjamin Eberlei",
- "email": "kontakt@beberlei.de"
- },
- {
- "name": "Jonathan Wage",
- "email": "jonwage@gmail.com"
- },
- {
- "name": "Johannes Schmitt",
- "email": "schmittjoh@gmail.com"
- },
- {
- "name": "Marco Pivetta",
- "email": "ocramius@gmail.com"
- }
- ],
- "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
- "homepage": "https://www.doctrine-project.org/projects/event-manager.html",
- "keywords": [
- "event",
- "event dispatcher",
- "event manager",
- "event system",
- "events"
- ],
- "support": {
- "issues": "https://github.com/doctrine/event-manager/issues",
- "source": "https://github.com/doctrine/event-manager/tree/2.0.1"
- },
- "funding": [
- {
- "url": "https://www.doctrine-project.org/sponsorship.html",
- "type": "custom"
- },
- {
- "url": "https://www.patreon.com/phpdoctrine",
- "type": "patreon"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager",
- "type": "tidelift"
- }
- ],
- "time": "2024-05-22T20:47:39+00:00"
- },
{
"name": "doctrine/inflector",
"version": "2.0.10",
@@ -1937,16 +1806,16 @@
},
{
"name": "dragonmantank/cron-expression",
- "version": "v3.3.3",
+ "version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/dragonmantank/cron-expression.git",
- "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a"
+ "reference": "8c784d071debd117328803d86b2097615b457500"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a",
- "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a",
+ "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500",
+ "reference": "8c784d071debd117328803d86b2097615b457500",
"shasum": ""
},
"require": {
@@ -1959,10 +1828,14 @@
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^1.0",
- "phpstan/phpstan-webmozart-assert": "^1.0",
"phpunit/phpunit": "^7.0|^8.0|^9.0"
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
"autoload": {
"psr-4": {
"Cron\\": "src/Cron/"
@@ -1986,7 +1859,7 @@
],
"support": {
"issues": "https://github.com/dragonmantank/cron-expression/issues",
- "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3"
+ "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0"
},
"funding": [
{
@@ -1994,7 +1867,7 @@
"type": "github"
}
],
- "time": "2023-08-10T19:36:49+00:00"
+ "time": "2024-10-09T13:47:03+00:00"
},
{
"name": "egulias/email-validator",
@@ -2387,16 +2260,16 @@
},
{
"name": "guzzlehttp/promises",
- "version": "2.0.3",
+ "version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8"
+ "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
- "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
+ "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
"shasum": ""
},
"require": {
@@ -2450,7 +2323,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
- "source": "https://github.com/guzzle/promises/tree/2.0.3"
+ "source": "https://github.com/guzzle/promises/tree/2.0.4"
},
"funding": [
{
@@ -2466,7 +2339,7 @@
"type": "tidelift"
}
],
- "time": "2024-07-18T10:29:17+00:00"
+ "time": "2024-10-17T10:06:22+00:00"
},
{
"name": "guzzlehttp/psr7",
@@ -2789,16 +2662,16 @@
},
{
"name": "laravel/fortify",
- "version": "v1.24.1",
+ "version": "v1.24.5",
"source": {
"type": "git",
"url": "https://github.com/laravel/fortify.git",
- "reference": "8158ba0960bb5f4aae509d01d74a95e16e30de20"
+ "reference": "bba8c2ecc3fcc78e8632e0d719ae10bef6343eef"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/fortify/zipball/8158ba0960bb5f4aae509d01d74a95e16e30de20",
- "reference": "8158ba0960bb5f4aae509d01d74a95e16e30de20",
+ "url": "https://api.github.com/repos/laravel/fortify/zipball/bba8c2ecc3fcc78e8632e0d719ae10bef6343eef",
+ "reference": "bba8c2ecc3fcc78e8632e0d719ae10bef6343eef",
"shasum": ""
},
"require": {
@@ -2850,20 +2723,20 @@
"issues": "https://github.com/laravel/fortify/issues",
"source": "https://github.com/laravel/fortify"
},
- "time": "2024-09-03T10:02:14+00:00"
+ "time": "2024-11-12T14:51:12+00:00"
},
{
"name": "laravel/framework",
- "version": "v11.23.2",
+ "version": "v11.31.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3"
+ "reference": "365090ed2c68244e3141cdb5e247cdf3dfba2c40"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3",
- "reference": "d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/365090ed2c68244e3141cdb5e247cdf3dfba2c40",
+ "reference": "365090ed2c68244e3141cdb5e247cdf3dfba2c40",
"shasum": ""
},
"require": {
@@ -2882,7 +2755,7 @@
"fruitcake/php-cors": "^1.3",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/uri-template": "^1.0",
- "laravel/prompts": "^0.1.18",
+ "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0",
"laravel/serializable-closure": "^1.3",
"league/commonmark": "^2.2.1",
"league/flysystem": "^3.8.0",
@@ -2968,7 +2841,7 @@
"league/flysystem-sftp-v3": "^3.0",
"mockery/mockery": "^1.6",
"nyholm/psr7": "^1.2",
- "orchestra/testbench-core": "^9.4.0",
+ "orchestra/testbench-core": "^9.5",
"pda/pheanstalk": "^5.0",
"phpstan/phpstan": "^1.11.5",
"phpunit/phpunit": "^10.5|^11.0",
@@ -3027,6 +2900,7 @@
"src/Illuminate/Filesystem/functions.php",
"src/Illuminate/Foundation/helpers.php",
"src/Illuminate/Log/functions.php",
+ "src/Illuminate/Support/functions.php",
"src/Illuminate/Support/helpers.php"
],
"psr-4": {
@@ -3058,20 +2932,20 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2024-09-11T21:59:23+00:00"
+ "time": "2024-11-12T15:36:15+00:00"
},
{
"name": "laravel/horizon",
- "version": "v5.28.1",
+ "version": "v5.29.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/horizon.git",
- "reference": "9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f"
+ "reference": "a48d242759704e598242074daf0060bbeb6ed46d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/horizon/zipball/9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f",
- "reference": "9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f",
+ "url": "https://api.github.com/repos/laravel/horizon/zipball/a48d242759704e598242074daf0060bbeb6ed46d",
+ "reference": "a48d242759704e598242074daf0060bbeb6ed46d",
"shasum": ""
},
"require": {
@@ -3086,6 +2960,7 @@
"ramsey/uuid": "^4.0",
"symfony/console": "^6.0|^7.0",
"symfony/error-handler": "^6.0|^7.0",
+ "symfony/polyfill-php83": "^1.28",
"symfony/process": "^6.0|^7.0"
},
"require-dev": {
@@ -3135,27 +3010,105 @@
],
"support": {
"issues": "https://github.com/laravel/horizon/issues",
- "source": "https://github.com/laravel/horizon/tree/v5.28.1"
+ "source": "https://github.com/laravel/horizon/tree/v5.29.3"
},
- "time": "2024-09-04T14:06:50+00:00"
+ "time": "2024-11-07T21:51:45+00:00"
},
{
- "name": "laravel/prompts",
- "version": "v0.1.25",
+ "name": "laravel/pail",
+ "version": "v1.2.1",
"source": {
"type": "git",
- "url": "https://github.com/laravel/prompts.git",
- "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95"
+ "url": "https://github.com/laravel/pail.git",
+ "reference": "353ac12134b98e2e7c3333d916bd3e523931e583"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/prompts/zipball/7b4029a84c37cb2725fc7f011586e2997040bc95",
- "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95",
+ "url": "https://api.github.com/repos/laravel/pail/zipball/353ac12134b98e2e7c3333d916bd3e523931e583",
+ "reference": "353ac12134b98e2e7c3333d916bd3e523931e583",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
- "illuminate/collections": "^10.0|^11.0",
+ "illuminate/console": "^10.24|^11.0",
+ "illuminate/contracts": "^10.24|^11.0",
+ "illuminate/log": "^10.24|^11.0",
+ "illuminate/process": "^10.24|^11.0",
+ "illuminate/support": "^10.24|^11.0",
+ "nunomaduro/termwind": "^1.15|^2.0",
+ "php": "^8.2",
+ "symfony/console": "^6.0|^7.0"
+ },
+ "require-dev": {
+ "laravel/framework": "^10.24|^11.0",
+ "laravel/pint": "^1.13",
+ "orchestra/testbench-core": "^8.12|^9.0",
+ "pestphp/pest": "^2.20",
+ "pestphp/pest-plugin-type-coverage": "^2.3",
+ "phpstan/phpstan": "^1.10",
+ "symfony/var-dumper": "^6.3|^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.x-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Laravel\\Pail\\PailServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Pail\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ },
+ {
+ "name": "Nuno Maduro",
+ "email": "enunomaduro@gmail.com"
+ }
+ ],
+ "description": "Easily delve into your Laravel application's log files directly from the command line.",
+ "homepage": "https://github.com/laravel/pail",
+ "keywords": [
+ "laravel",
+ "logs",
+ "php",
+ "tail"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/pail/issues",
+ "source": "https://github.com/laravel/pail"
+ },
+ "time": "2024-10-23T12:56:23+00:00"
+ },
+ {
+ "name": "laravel/prompts",
+ "version": "v0.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/prompts.git",
+ "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/prompts/zipball/0e0535747c6b8d6d10adca8b68293cf4517abb0f",
+ "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f",
+ "shasum": ""
+ },
+ "require": {
+ "composer-runtime-api": "^2.2",
+ "ext-mbstring": "*",
"php": "^8.1",
"symfony/console": "^6.2|^7.0"
},
@@ -3164,8 +3117,9 @@
"laravel/framework": ">=10.17.0 <10.25.0"
},
"require-dev": {
+ "illuminate/collections": "^10.0|^11.0",
"mockery/mockery": "^1.5",
- "pestphp/pest": "^2.3",
+ "pestphp/pest": "^2.3|^3.4",
"phpstan/phpstan": "^1.11",
"phpstan/phpstan-mockery": "^1.1"
},
@@ -3175,7 +3129,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "0.1.x-dev"
+ "dev-main": "0.3.x-dev"
}
},
"autoload": {
@@ -3193,22 +3147,22 @@
"description": "Add beautiful and user-friendly forms to your command-line applications.",
"support": {
"issues": "https://github.com/laravel/prompts/issues",
- "source": "https://github.com/laravel/prompts/tree/v0.1.25"
+ "source": "https://github.com/laravel/prompts/tree/v0.3.2"
},
- "time": "2024-08-12T22:06:33+00:00"
+ "time": "2024-11-12T14:59:47+00:00"
},
{
"name": "laravel/sanctum",
- "version": "v4.0.2",
+ "version": "v4.0.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
- "reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1"
+ "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/sanctum/zipball/9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
- "reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
+ "url": "https://api.github.com/repos/laravel/sanctum/zipball/54aea9d13743ae8a6cdd3c28dbef128a17adecab",
+ "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab",
"shasum": ""
},
"require": {
@@ -3259,20 +3213,20 @@
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
- "time": "2024-04-10T19:39:58+00:00"
+ "time": "2024-09-27T14:55:41+00:00"
},
{
"name": "laravel/serializable-closure",
- "version": "v1.3.4",
+ "version": "v1.3.6",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
- "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81"
+ "reference": "f865a58ea3a0107c336b7045104c75243fa59d96"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
- "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f865a58ea3a0107c336b7045104c75243fa59d96",
+ "reference": "f865a58ea3a0107c336b7045104c75243fa59d96",
"shasum": ""
},
"require": {
@@ -3320,7 +3274,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
- "time": "2024-08-02T07:48:17+00:00"
+ "time": "2024-11-11T17:06:04+00:00"
},
{
"name": "laravel/socialite",
@@ -3394,87 +3348,18 @@
},
"time": "2024-09-03T09:46:57+00:00"
},
- {
- "name": "laravel/telescope",
- "version": "v5.2.2",
- "source": {
- "type": "git",
- "url": "https://github.com/laravel/telescope.git",
- "reference": "daaf95dee9fab2dd80f59b5f6611c6c0eff44878"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/laravel/telescope/zipball/daaf95dee9fab2dd80f59b5f6611c6c0eff44878",
- "reference": "daaf95dee9fab2dd80f59b5f6611c6c0eff44878",
- "shasum": ""
- },
- "require": {
- "ext-json": "*",
- "laravel/framework": "^8.37|^9.0|^10.0|^11.0",
- "php": "^8.0",
- "symfony/console": "^5.3|^6.0|^7.0",
- "symfony/var-dumper": "^5.0|^6.0|^7.0"
- },
- "require-dev": {
- "ext-gd": "*",
- "guzzlehttp/guzzle": "^6.0|^7.0",
- "laravel/octane": "^1.4|^2.0|dev-develop",
- "orchestra/testbench": "^6.40|^7.37|^8.17|^9.0",
- "phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^9.0|^10.5"
- },
- "type": "library",
- "extra": {
- "laravel": {
- "providers": [
- "Laravel\\Telescope\\TelescopeServiceProvider"
- ]
- }
- },
- "autoload": {
- "psr-4": {
- "Laravel\\Telescope\\": "src/",
- "Laravel\\Telescope\\Database\\Factories\\": "database/factories/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Taylor Otwell",
- "email": "taylor@laravel.com"
- },
- {
- "name": "Mohamed Said",
- "email": "mohamed@laravel.com"
- }
- ],
- "description": "An elegant debug assistant for the Laravel framework.",
- "keywords": [
- "debugging",
- "laravel",
- "monitoring"
- ],
- "support": {
- "issues": "https://github.com/laravel/telescope/issues",
- "source": "https://github.com/laravel/telescope/tree/v5.2.2"
- },
- "time": "2024-08-26T12:40:52+00:00"
- },
{
"name": "laravel/tinker",
- "version": "v2.9.0",
+ "version": "v2.10.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/tinker.git",
- "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe"
+ "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/tinker/zipball/502e0fe3f0415d06d5db1f83a472f0f3b754bafe",
- "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe",
+ "url": "https://api.github.com/repos/laravel/tinker/zipball/ba4d51eb56de7711b3a37d63aa0643e99a339ae5",
+ "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5",
"shasum": ""
},
"require": {
@@ -3525,9 +3410,9 @@
],
"support": {
"issues": "https://github.com/laravel/tinker/issues",
- "source": "https://github.com/laravel/tinker/tree/v2.9.0"
+ "source": "https://github.com/laravel/tinker/tree/v2.10.0"
},
- "time": "2024-01-04T16:10:04+00:00"
+ "time": "2024-09-23T13:32:56+00:00"
},
{
"name": "laravel/ui",
@@ -3594,38 +3479,38 @@
},
{
"name": "lcobucci/jwt",
- "version": "5.3.0",
+ "version": "5.4.2",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/jwt.git",
- "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83"
+ "reference": "ea1ce71cbf9741e445a5914e2f67cdbb484ff712"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/lcobucci/jwt/zipball/08071d8d2c7f4b00222cc4b1fb6aa46990a80f83",
- "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83",
+ "url": "https://api.github.com/repos/lcobucci/jwt/zipball/ea1ce71cbf9741e445a5914e2f67cdbb484ff712",
+ "reference": "ea1ce71cbf9741e445a5914e2f67cdbb484ff712",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-sodium": "*",
- "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
+ "php": "~8.2.0 || ~8.3.0 || ~8.4.0",
"psr/clock": "^1.0"
},
"require-dev": {
- "infection/infection": "^0.27.0",
- "lcobucci/clock": "^3.0",
+ "infection/infection": "^0.29",
+ "lcobucci/clock": "^3.2",
"lcobucci/coding-standard": "^11.0",
- "phpbench/phpbench": "^1.2.9",
+ "phpbench/phpbench": "^1.2",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.10.7",
"phpstan/phpstan-deprecation-rules": "^1.1.3",
"phpstan/phpstan-phpunit": "^1.3.10",
"phpstan/phpstan-strict-rules": "^1.5.0",
- "phpunit/phpunit": "^10.2.6"
+ "phpunit/phpunit": "^11.1"
},
"suggest": {
- "lcobucci/clock": ">= 3.0"
+ "lcobucci/clock": ">= 3.2"
},
"type": "library",
"autoload": {
@@ -3651,7 +3536,7 @@
],
"support": {
"issues": "https://github.com/lcobucci/jwt/issues",
- "source": "https://github.com/lcobucci/jwt/tree/5.3.0"
+ "source": "https://github.com/lcobucci/jwt/tree/5.4.2"
},
"funding": [
{
@@ -3663,7 +3548,7 @@
"type": "patreon"
}
],
- "time": "2024-04-11T23:07:54+00:00"
+ "time": "2024-11-07T12:54:35+00:00"
},
{
"name": "league/commonmark",
@@ -3855,16 +3740,16 @@
},
{
"name": "league/flysystem",
- "version": "3.28.0",
+ "version": "3.29.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
- "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c"
+ "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c",
- "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319",
+ "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319",
"shasum": ""
},
"require": {
@@ -3932,22 +3817,22 @@
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
- "source": "https://github.com/thephpleague/flysystem/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem/tree/3.29.1"
},
- "time": "2024-05-22T10:09:12+00:00"
+ "time": "2024-10-08T08:58:34+00:00"
},
{
"name": "league/flysystem-aws-s3-v3",
- "version": "3.28.0",
+ "version": "3.29.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
- "reference": "22071ef1604bc776f5ff2468ac27a752514665c8"
+ "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/22071ef1604bc776f5ff2468ac27a752514665c8",
- "reference": "22071ef1604bc776f5ff2468ac27a752514665c8",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/c6ff6d4606e48249b63f269eba7fabdb584e76a9",
+ "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9",
"shasum": ""
},
"require": {
@@ -3987,22 +3872,22 @@
"storage"
],
"support": {
- "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.29.0"
},
- "time": "2024-05-06T20:05:52+00:00"
+ "time": "2024-08-17T13:10:48+00:00"
},
{
"name": "league/flysystem-local",
- "version": "3.28.0",
+ "version": "3.29.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-local.git",
- "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40"
+ "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40",
- "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27",
+ "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27",
"shasum": ""
},
"require": {
@@ -4036,22 +3921,22 @@
"local"
],
"support": {
- "source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0"
},
- "time": "2024-05-06T20:05:52+00:00"
+ "time": "2024-08-09T21:24:39+00:00"
},
{
"name": "league/flysystem-sftp-v3",
- "version": "3.28.0",
+ "version": "3.29.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-sftp-v3.git",
- "reference": "abedadd3c64d4f0e276d6ecc796ec8194d136b41"
+ "reference": "ce9b209e2fbe33122c755ffc18eb4d5bd256f252"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem-sftp-v3/zipball/abedadd3c64d4f0e276d6ecc796ec8194d136b41",
- "reference": "abedadd3c64d4f0e276d6ecc796ec8194d136b41",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-sftp-v3/zipball/ce9b209e2fbe33122c755ffc18eb4d5bd256f252",
+ "reference": "ce9b209e2fbe33122c755ffc18eb4d5bd256f252",
"shasum": ""
},
"require": {
@@ -4085,22 +3970,22 @@
"sftp"
],
"support": {
- "source": "https://github.com/thephpleague/flysystem-sftp-v3/tree/3.28.0"
+ "source": "https://github.com/thephpleague/flysystem-sftp-v3/tree/3.29.0"
},
- "time": "2024-05-06T20:05:52+00:00"
+ "time": "2024-08-14T19:35:54+00:00"
},
{
"name": "league/mime-type-detection",
- "version": "1.15.0",
+ "version": "1.16.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/mime-type-detection.git",
- "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301"
+ "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301",
- "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301",
+ "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9",
+ "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9",
"shasum": ""
},
"require": {
@@ -4131,7 +4016,7 @@
"description": "Mime-type detection for Flysystem",
"support": {
"issues": "https://github.com/thephpleague/mime-type-detection/issues",
- "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0"
+ "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0"
},
"funding": [
{
@@ -4143,7 +4028,7 @@
"type": "tidelift"
}
],
- "time": "2024-01-28T23:22:08+00:00"
+ "time": "2024-09-21T08:32:55+00:00"
},
{
"name": "league/oauth1-client",
@@ -4397,16 +4282,16 @@
},
{
"name": "livewire/livewire",
- "version": "v3.4.9",
+ "version": "v3.5.12",
"source": {
"type": "git",
"url": "https://github.com/livewire/livewire.git",
- "reference": "c65b3f0798ab2c9338213ede3588c3cdf4e6fcc0"
+ "reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/livewire/livewire/zipball/c65b3f0798ab2c9338213ede3588c3cdf4e6fcc0",
- "reference": "c65b3f0798ab2c9338213ede3588c3cdf4e6fcc0",
+ "url": "https://api.github.com/repos/livewire/livewire/zipball/3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d",
+ "reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d",
"shasum": ""
},
"require": {
@@ -4414,17 +4299,18 @@
"illuminate/routing": "^10.0|^11.0",
"illuminate/support": "^10.0|^11.0",
"illuminate/validation": "^10.0|^11.0",
+ "laravel/prompts": "^0.1.24|^0.2|^0.3",
"league/mime-type-detection": "^1.9",
"php": "^8.1",
+ "symfony/console": "^6.0|^7.0",
"symfony/http-kernel": "^6.2|^7.0"
},
"require-dev": {
"calebporzio/sushi": "^2.1",
- "laravel/framework": "^10.0|^11.0",
- "laravel/prompts": "^0.1.6",
+ "laravel/framework": "^10.15.0|^11.0",
"mockery/mockery": "^1.3.1",
- "orchestra/testbench": "8.20.0|^9.0",
- "orchestra/testbench-dusk": "8.20.0|^9.0",
+ "orchestra/testbench": "^8.21.0|^9.0",
+ "orchestra/testbench-dusk": "^8.24|^9.1",
"phpunit/phpunit": "^10.4",
"psy/psysh": "^0.11.22|^0.12"
},
@@ -4460,7 +4346,7 @@
"description": "A front-end framework for Laravel.",
"support": {
"issues": "https://github.com/livewire/livewire/issues",
- "source": "https://github.com/livewire/livewire/tree/v3.4.9"
+ "source": "https://github.com/livewire/livewire/tree/v3.5.12"
},
"funding": [
{
@@ -4468,31 +4354,31 @@
"type": "github"
}
],
- "time": "2024-03-14T14:03:32+00:00"
+ "time": "2024-10-15T19:35:06+00:00"
},
{
"name": "log1x/laravel-webfonts",
- "version": "v1.0.1",
+ "version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/Log1x/laravel-webfonts.git",
- "reference": "0d38122aa7f5501394006a6715f7d97dac223507"
+ "reference": "128a20af26f02db84df21abc6524e5a069cf20a4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Log1x/laravel-webfonts/zipball/0d38122aa7f5501394006a6715f7d97dac223507",
- "reference": "0d38122aa7f5501394006a6715f7d97dac223507",
+ "url": "https://api.github.com/repos/Log1x/laravel-webfonts/zipball/128a20af26f02db84df21abc6524e5a069cf20a4",
+ "reference": "128a20af26f02db84df21abc6524e5a069cf20a4",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^7.8",
- "laravel/prompts": "^0.1.15",
+ "laravel/prompts": "^0.1|^0.2|^0.3",
"php": ">=8.1"
},
"require-dev": {
- "illuminate/console": "^10.41",
- "illuminate/http": "^10.41",
- "illuminate/support": "^10.41",
+ "illuminate/console": "^10.0|^11.0",
+ "illuminate/http": "^10.0|^11.0",
+ "illuminate/support": "^10.0|^11.0",
"laravel/pint": "^1.13"
},
"type": "package",
@@ -4522,7 +4408,7 @@
"description": "Download, install, and preload over 1500 Google fonts locally in your Laravel project",
"support": {
"issues": "https://github.com/Log1x/laravel-webfonts/issues",
- "source": "https://github.com/Log1x/laravel-webfonts/tree/v1.0.1"
+ "source": "https://github.com/Log1x/laravel-webfonts/tree/v1.0.2"
},
"funding": [
{
@@ -4530,7 +4416,7 @@
"type": "github"
}
],
- "time": "2024-03-28T11:53:11+00:00"
+ "time": "2024-11-12T19:00:31+00:00"
},
{
"name": "lorisleiva/laravel-actions",
@@ -4682,16 +4568,16 @@
},
{
"name": "monolog/monolog",
- "version": "3.7.0",
+ "version": "3.8.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
- "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8"
+ "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8",
- "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/32e515fdc02cdafbe4593e30a9350d486b125b67",
+ "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67",
"shasum": ""
},
"require": {
@@ -4711,12 +4597,14 @@
"guzzlehttp/psr7": "^2.2",
"mongodb/mongodb": "^1.8",
"php-amqplib/php-amqplib": "~2.4 || ^3",
- "phpstan/phpstan": "^1.9",
- "phpstan/phpstan-deprecation-rules": "^1.0",
- "phpstan/phpstan-strict-rules": "^1.4",
- "phpunit/phpunit": "^10.5.17",
+ "php-console/php-console": "^3.1.8",
+ "phpstan/phpstan": "^2",
+ "phpstan/phpstan-deprecation-rules": "^2",
+ "phpstan/phpstan-strict-rules": "^2",
+ "phpunit/phpunit": "^10.5.17 || ^11.0.7",
"predis/predis": "^1.1 || ^2",
- "ruflin/elastica": "^7",
+ "rollbar/rollbar": "^4.0",
+ "ruflin/elastica": "^7 || ^8",
"symfony/mailer": "^5.4 || ^6",
"symfony/mime": "^5.4 || ^6"
},
@@ -4767,7 +4655,7 @@
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
- "source": "https://github.com/Seldaek/monolog/tree/3.7.0"
+ "source": "https://github.com/Seldaek/monolog/tree/3.8.0"
},
"funding": [
{
@@ -4779,7 +4667,7 @@
"type": "tidelift"
}
],
- "time": "2024-06-28T09:40:51+00:00"
+ "time": "2024-11-12T13:57:08+00:00"
},
{
"name": "mtdowling/jmespath.php",
@@ -4849,20 +4737,20 @@
},
{
"name": "nesbot/carbon",
- "version": "3.8.0",
+ "version": "3.8.2",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
- "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f"
+ "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bbd3eef89af8ba66a3aa7952b5439168fbcc529f",
- "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f",
+ "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e1268cdbc486d97ce23fef2c666dc3c6b6de9947",
+ "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947",
"shasum": ""
},
"require": {
- "carbonphp/carbon-doctrine-types": "*",
+ "carbonphp/carbon-doctrine-types": "<100.0",
"ext-json": "*",
"php": "^8.1",
"psr/clock": "^1.0",
@@ -4951,28 +4839,28 @@
"type": "tidelift"
}
],
- "time": "2024-08-19T06:22:39+00:00"
+ "time": "2024-11-07T17:46:48+00:00"
},
{
"name": "nette/schema",
- "version": "v1.3.0",
+ "version": "v1.3.2",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
- "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188"
+ "reference": "da801d52f0354f70a638673c4a0f04e16529431d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188",
- "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188",
+ "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d",
+ "reference": "da801d52f0354f70a638673c4a0f04e16529431d",
"shasum": ""
},
"require": {
"nette/utils": "^4.0",
- "php": "8.1 - 8.3"
+ "php": "8.1 - 8.4"
},
"require-dev": {
- "nette/tester": "^2.4",
+ "nette/tester": "^2.5.2",
"phpstan/phpstan-nette": "^1.0",
"tracy/tracy": "^2.8"
},
@@ -5011,9 +4899,9 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
- "source": "https://github.com/nette/schema/tree/v1.3.0"
+ "source": "https://github.com/nette/schema/tree/v1.3.2"
},
- "time": "2023-12-11T11:54:22+00:00"
+ "time": "2024-10-06T23:10:23+00:00"
},
{
"name": "nette/utils",
@@ -5103,16 +4991,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.1.0",
+ "version": "v5.3.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1"
+ "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1",
- "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b",
+ "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b",
"shasum": ""
},
"require": {
@@ -5155,9 +5043,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1"
},
- "time": "2024-07-01T20:03:41+00:00"
+ "time": "2024-10-08T18:51:32+00:00"
},
{
"name": "nubs/random-name-generator",
@@ -5214,32 +5102,31 @@
},
{
"name": "nunomaduro/termwind",
- "version": "v2.1.0",
+ "version": "v2.2.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/termwind.git",
- "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a"
+ "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/e5f21eade88689536c0cdad4c3cd75f3ed26e01a",
- "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a",
+ "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/42c84e4e8090766bbd6445d06cd6e57650626ea3",
+ "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^8.2",
- "symfony/console": "^7.0.4"
+ "symfony/console": "^7.1.5"
},
"require-dev": {
- "ergebnis/phpstan-rules": "^2.2.0",
- "illuminate/console": "^11.1.1",
- "laravel/pint": "^1.15.0",
- "mockery/mockery": "^1.6.11",
- "pestphp/pest": "^2.34.6",
- "phpstan/phpstan": "^1.10.66",
- "phpstan/phpstan-strict-rules": "^1.5.2",
- "symfony/var-dumper": "^7.0.4",
+ "illuminate/console": "^11.28.0",
+ "laravel/pint": "^1.18.1",
+ "mockery/mockery": "^1.6.12",
+ "pestphp/pest": "^2.36.0",
+ "phpstan/phpstan": "^1.12.6",
+ "phpstan/phpstan-strict-rules": "^1.6.1",
+ "symfony/var-dumper": "^7.1.5",
"thecodingmachine/phpstan-strict-rules": "^1.0.0"
},
"type": "library",
@@ -5282,7 +5169,7 @@
],
"support": {
"issues": "https://github.com/nunomaduro/termwind/issues",
- "source": "https://github.com/nunomaduro/termwind/tree/v2.1.0"
+ "source": "https://github.com/nunomaduro/termwind/tree/v2.2.0"
},
"funding": [
{
@@ -5298,7 +5185,7 @@
"type": "github"
}
],
- "time": "2024-09-05T15:25:50+00:00"
+ "time": "2024-10-15T16:15:16+00:00"
},
{
"name": "nyholm/psr7",
@@ -5497,30 +5384,35 @@
},
{
"name": "paragonie/sodium_compat",
- "version": "v1.21.1",
+ "version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/sodium_compat.git",
- "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37"
+ "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/bb312875dcdd20680419564fe42ba1d9564b9e37",
- "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37",
+ "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/a673d5f310477027cead2e2f2b6db5d8368157cb",
+ "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb",
"shasum": ""
},
"require": {
- "paragonie/random_compat": ">=1",
- "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8"
+ "php": "^8.1",
+ "php-64bit": "*"
},
"require-dev": {
- "phpunit/phpunit": "^3|^4|^5|^6|^7|^8|^9"
+ "phpunit/phpunit": "^7|^8|^9",
+ "vimeo/psalm": "^4|^5"
},
"suggest": {
- "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.",
- "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
+ "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
"autoload": {
"files": [
"autoload.php"
@@ -5577,9 +5469,9 @@
],
"support": {
"issues": "https://github.com/paragonie/sodium_compat/issues",
- "source": "https://github.com/paragonie/sodium_compat/tree/v1.21.1"
+ "source": "https://github.com/paragonie/sodium_compat/tree/v2.1.0"
},
- "time": "2024-04-22T22:05:04+00:00"
+ "time": "2024-09-04T12:51:01+00:00"
},
{
"name": "php-di/invoker",
@@ -5709,6 +5601,73 @@
],
"time": "2024-07-21T15:55:45+00:00"
},
+ {
+ "name": "phpdocumentor/reflection",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/Reflection.git",
+ "reference": "61e2f1fe7683e9647b9ed8d9e53d08699385267d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/61e2f1fe7683e9647b9ed8d9e53d08699385267d",
+ "reference": "61e2f1fe7683e9647b9ed8d9e53d08699385267d",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "~4.18 || ^5.0",
+ "php": "8.1.*|8.2.*|8.3.*",
+ "phpdocumentor/reflection-common": "^2.1",
+ "phpdocumentor/reflection-docblock": "^5",
+ "phpdocumentor/type-resolver": "^1.2",
+ "symfony/polyfill-php80": "^1.28",
+ "webmozart/assert": "^1.7"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "doctrine/coding-standard": "^12.0",
+ "mikey179/vfsstream": "~1.2",
+ "mockery/mockery": "~1.6.0",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.8",
+ "phpstan/phpstan-webmozart-assert": "^1.2",
+ "phpunit/phpunit": "^10.0",
+ "psalm/phar": "^5.24",
+ "rector/rector": "^1.0.0",
+ "squizlabs/php_codesniffer": "^3.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-5.x": "5.3.x-dev",
+ "dev-6.x": "6.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\": "src/phpDocumentor"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Reflection library to do Static Analysis for PHP Projects",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phpDocumentor/Reflection/issues",
+ "source": "https://github.com/phpDocumentor/Reflection/tree/6.0.0"
+ },
+ "time": "2024-05-23T19:28:12+00:00"
+ },
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
@@ -5763,24 +5722,88 @@
"time": "2020-06-27T09:03:43+00:00"
},
{
- "name": "phpdocumentor/type-resolver",
- "version": "1.8.2",
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "5.6.0",
"source": {
"type": "git",
- "url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "153ae662783729388a584b4361f2545e4d841e3c"
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c",
- "reference": "153ae662783729388a584b4361f2545e4d841e3c",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/f3558a4c23426d12bffeaab463f8a8d8b681193c",
+ "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/deprecations": "^1.1",
+ "ext-filter": "*",
+ "php": "^7.4 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.2",
+ "phpdocumentor/type-resolver": "^1.7",
+ "phpstan/phpdoc-parser": "^1.7|^2.0",
+ "webmozart/assert": "^1.9.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.3.5 || ~1.6.0",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.8",
+ "phpstan/phpstan-mockery": "^1.1",
+ "phpstan/phpstan-webmozart-assert": "^1.2",
+ "phpunit/phpunit": "^9.5",
+ "psalm/phar": "^5.26"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ },
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.0"
+ },
+ "time": "2024-11-12T11:25:25+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a",
+ "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a",
"shasum": ""
},
"require": {
"doctrine/deprecations": "^1.0",
"php": "^7.3 || ^8.0",
"phpdocumentor/reflection-common": "^2.0",
- "phpstan/phpdoc-parser": "^1.13"
+ "phpstan/phpdoc-parser": "^1.18|^2.0"
},
"require-dev": {
"ext-tokenizer": "*",
@@ -5816,9 +5839,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
- "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2"
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0"
},
- "time": "2024-02-23T11:10:43+00:00"
+ "time": "2024-11-09T15:12:26+00:00"
},
{
"name": "phpoption/phpoption",
@@ -5897,16 +5920,16 @@
},
{
"name": "phpseclib/phpseclib",
- "version": "3.0.41",
+ "version": "3.0.42",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
- "reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb"
+ "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/621c73f7dcb310b61de34d1da4c4204e8ace6ceb",
- "reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db92f1b1987b12b13f248fe76c3a52cadb67bb98",
+ "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98",
"shasum": ""
},
"require": {
@@ -5987,7 +6010,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
- "source": "https://github.com/phpseclib/phpseclib/tree/3.0.41"
+ "source": "https://github.com/phpseclib/phpseclib/tree/3.0.42"
},
"funding": [
{
@@ -6003,34 +6026,34 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T00:13:54+00:00"
+ "time": "2024-09-16T03:06:04+00:00"
},
{
"name": "phpstan/phpdoc-parser",
- "version": "1.30.1",
+ "version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "51b95ec8670af41009e2b2b56873bad96682413e"
+ "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e",
- "reference": "51b95ec8670af41009e2b2b56873bad96682413e",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299",
+ "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299",
"shasum": ""
},
"require": {
- "php": "^7.2 || ^8.0"
+ "php": "^7.4 || ^8.0"
},
"require-dev": {
"doctrine/annotations": "^2.0",
- "nikic/php-parser": "^4.15",
+ "nikic/php-parser": "^5.3.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/extension-installer": "^1.0",
- "phpstan/phpstan": "^1.5",
- "phpstan/phpstan-phpunit": "^1.1",
- "phpstan/phpstan-strict-rules": "^1.0",
- "phpunit/phpunit": "^9.5",
+ "phpstan/phpstan": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^9.6",
"symfony/process": "^5.2"
},
"type": "library",
@@ -6048,22 +6071,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1"
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0"
},
- "time": "2024-09-07T20:13:05+00:00"
+ "time": "2024-10-13T11:29:49+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "1.12.3",
+ "version": "1.12.10",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009"
+ "reference": "fc463b5d0fe906dcf19689be692c65c50406a071"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009",
- "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fc463b5d0fe906dcf19689be692c65c50406a071",
+ "reference": "fc463b5d0fe906dcf19689be692c65c50406a071",
"shasum": ""
},
"require": {
@@ -6108,7 +6131,7 @@
"type": "github"
}
],
- "time": "2024-09-09T08:10:35+00:00"
+ "time": "2024-11-11T15:37:09+00:00"
},
{
"name": "pion/laravel-chunk-upload",
@@ -6814,16 +6837,16 @@
},
{
"name": "purplepixie/phpdns",
- "version": "2.1.1",
+ "version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/purplepixie/phpdns.git",
- "reference": "18cd3a43fadcfd16e2789e3c78a264945f6cbfad"
+ "reference": "2b77de5bb218bc4e5d9c4a4a12bd18fe80a6ab4d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/purplepixie/phpdns/zipball/18cd3a43fadcfd16e2789e3c78a264945f6cbfad",
- "reference": "18cd3a43fadcfd16e2789e3c78a264945f6cbfad",
+ "url": "https://api.github.com/repos/purplepixie/phpdns/zipball/2b77de5bb218bc4e5d9c4a4a12bd18fe80a6ab4d",
+ "reference": "2b77de5bb218bc4e5d9c4a4a12bd18fe80a6ab4d",
"shasum": ""
},
"require": {
@@ -6856,29 +6879,29 @@
"description": "PHP DNS Direct Query Module",
"support": {
"issues": "https://github.com/purplepixie/phpdns/issues",
- "source": "https://github.com/purplepixie/phpdns/tree/2.1.1"
+ "source": "https://github.com/purplepixie/phpdns/tree/2.2.0"
},
- "time": "2024-05-27T13:27:50+00:00"
+ "time": "2024-09-26T14:39:58+00:00"
},
{
"name": "pusher/pusher-php-server",
- "version": "7.2.4",
+ "version": "7.2.6",
"source": {
"type": "git",
"url": "https://github.com/pusher/pusher-http-php.git",
- "reference": "de2f72296808f9cafa6a4462b15a768ff130cddb"
+ "reference": "d89e9997191d18fb0fe03a956fa3ccfe0af524ea"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/de2f72296808f9cafa6a4462b15a768ff130cddb",
- "reference": "de2f72296808f9cafa6a4462b15a768ff130cddb",
+ "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/d89e9997191d18fb0fe03a956fa3ccfe0af524ea",
+ "reference": "d89e9997191d18fb0fe03a956fa3ccfe0af524ea",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"guzzlehttp/guzzle": "^7.2",
- "paragonie/sodium_compat": "^1.6",
+ "paragonie/sodium_compat": "^1.6|^2.0",
"php": "^7.3|^8.0",
"psr/log": "^1.0|^2.0|^3.0"
},
@@ -6917,9 +6940,9 @@
],
"support": {
"issues": "https://github.com/pusher/pusher-http-php/issues",
- "source": "https://github.com/pusher/pusher-http-php/tree/7.2.4"
+ "source": "https://github.com/pusher/pusher-http-php/tree/7.2.6"
},
- "time": "2023-12-15T10:58:53+00:00"
+ "time": "2024-10-18T12:04:31+00:00"
},
{
"name": "ralouphie/getallheaders",
@@ -7148,21 +7171,21 @@
},
{
"name": "rector/rector",
- "version": "1.2.5",
+ "version": "1.2.10",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
- "reference": "e98aa793ca3fcd17e893cfaf9103ac049775d339"
+ "reference": "40f9cf38c05296bd32f444121336a521a293fa61"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/e98aa793ca3fcd17e893cfaf9103ac049775d339",
- "reference": "e98aa793ca3fcd17e893cfaf9103ac049775d339",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/40f9cf38c05296bd32f444121336a521a293fa61",
+ "reference": "40f9cf38c05296bd32f444121336a521a293fa61",
"shasum": ""
},
"require": {
"php": "^7.2|^8.0",
- "phpstan/phpstan": "^1.12.2"
+ "phpstan/phpstan": "^1.12.5"
},
"conflict": {
"rector/rector-doctrine": "*",
@@ -7195,7 +7218,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/1.2.5"
+ "source": "https://github.com/rectorphp/rector/tree/1.2.10"
},
"funding": [
{
@@ -7203,27 +7226,27 @@
"type": "github"
}
],
- "time": "2024-09-08T17:43:24+00:00"
+ "time": "2024-11-08T13:59:10+00:00"
},
{
"name": "resend/resend-laravel",
- "version": "v0.13.0",
+ "version": "v0.15.0",
"source": {
"type": "git",
"url": "https://github.com/resend/resend-laravel.git",
- "reference": "23aed22df0d0b23c2952da2aaed6a8b88d301a8a"
+ "reference": "af914817abc6abaa4522b5cfb177f3519493fd6e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/resend/resend-laravel/zipball/23aed22df0d0b23c2952da2aaed6a8b88d301a8a",
- "reference": "23aed22df0d0b23c2952da2aaed6a8b88d301a8a",
+ "url": "https://api.github.com/repos/resend/resend-laravel/zipball/af914817abc6abaa4522b5cfb177f3519493fd6e",
+ "reference": "af914817abc6abaa4522b5cfb177f3519493fd6e",
"shasum": ""
},
"require": {
"illuminate/http": "^10.0|^11.0",
"illuminate/support": "^10.0|^11.0",
"php": "^8.1",
- "resend/resend-php": "^0.12.0",
+ "resend/resend-php": "^0.14.0",
"symfony/mailer": "^6.2|^7.0"
},
"require-dev": {
@@ -7270,22 +7293,22 @@
],
"support": {
"issues": "https://github.com/resend/resend-laravel/issues",
- "source": "https://github.com/resend/resend-laravel/tree/v0.13.0"
+ "source": "https://github.com/resend/resend-laravel/tree/v0.15.0"
},
- "time": "2024-07-08T18:51:42+00:00"
+ "time": "2024-11-04T18:34:08+00:00"
},
{
"name": "resend/resend-php",
- "version": "v0.12.0",
+ "version": "v0.14.0",
"source": {
"type": "git",
"url": "https://github.com/resend/resend-php.git",
- "reference": "37fb79bb8160ce2de521bf37484ba59e89236521"
+ "reference": "d7900752bb9839421d40d9e66362bffb3ec07aac"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/resend/resend-php/zipball/37fb79bb8160ce2de521bf37484ba59e89236521",
- "reference": "37fb79bb8160ce2de521bf37484ba59e89236521",
+ "url": "https://api.github.com/repos/resend/resend-php/zipball/d7900752bb9839421d40d9e66362bffb3ec07aac",
+ "reference": "d7900752bb9839421d40d9e66362bffb3ec07aac",
"shasum": ""
},
"require": {
@@ -7327,9 +7350,9 @@
],
"support": {
"issues": "https://github.com/resend/resend-php/issues",
- "source": "https://github.com/resend/resend-php/tree/v0.12.0"
+ "source": "https://github.com/resend/resend-php/tree/v0.14.0"
},
- "time": "2024-03-04T03:16:28+00:00"
+ "time": "2024-11-01T02:00:44+00:00"
},
{
"name": "revolt/event-loop",
@@ -7405,16 +7428,16 @@
},
{
"name": "sentry/sentry",
- "version": "4.9.0",
+ "version": "4.10.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-php.git",
- "reference": "788ec170f51ebb22f2809a1e3f78b19ccd39b70d"
+ "reference": "2af937d47d8aadb8dab0b1d7b9557e495dd12856"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/788ec170f51ebb22f2809a1e3f78b19ccd39b70d",
- "reference": "788ec170f51ebb22f2809a1e3f78b19ccd39b70d",
+ "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/2af937d47d8aadb8dab0b1d7b9557e495dd12856",
+ "reference": "2af937d47d8aadb8dab0b1d7b9557e495dd12856",
"shasum": ""
},
"require": {
@@ -7432,12 +7455,12 @@
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.4",
- "guzzlehttp/promises": "^1.0|^2.0",
+ "guzzlehttp/promises": "^2.0.3",
"guzzlehttp/psr7": "^1.8.4|^2.1.1",
"monolog/monolog": "^1.6|^2.0|^3.0",
"phpbench/phpbench": "^1.0",
"phpstan/phpstan": "^1.3",
- "phpunit/phpunit": "^8.5.14|^9.4",
+ "phpunit/phpunit": "^8.5|^9.6",
"symfony/phpunit-bridge": "^5.2|^6.0|^7.0",
"vimeo/psalm": "^4.17"
},
@@ -7478,7 +7501,7 @@
],
"support": {
"issues": "https://github.com/getsentry/sentry-php/issues",
- "source": "https://github.com/getsentry/sentry-php/tree/4.9.0"
+ "source": "https://github.com/getsentry/sentry-php/tree/4.10.0"
},
"funding": [
{
@@ -7490,27 +7513,27 @@
"type": "custom"
}
],
- "time": "2024-08-08T14:40:50+00:00"
+ "time": "2024-11-06T07:44:19+00:00"
},
{
"name": "sentry/sentry-laravel",
- "version": "4.8.0",
+ "version": "4.10.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-laravel.git",
- "reference": "2bbcb7e81097993cf64d5b296eaa6d396cddd5a7"
+ "reference": "cbdd224cc5a224528bf6b19507ad76187b3bccfa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/2bbcb7e81097993cf64d5b296eaa6d396cddd5a7",
- "reference": "2bbcb7e81097993cf64d5b296eaa6d396cddd5a7",
+ "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/cbdd224cc5a224528bf6b19507ad76187b3bccfa",
+ "reference": "cbdd224cc5a224528bf6b19507ad76187b3bccfa",
"shasum": ""
},
"require": {
"illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0",
"nyholm/psr7": "^1.0",
"php": "^7.2 | ^8.0",
- "sentry/sentry": "^4.9",
+ "sentry/sentry": "^4.10",
"symfony/psr-http-message-bridge": "^1.0 | ^2.0 | ^6.0 | ^7.0"
},
"require-dev": {
@@ -7567,7 +7590,7 @@
],
"support": {
"issues": "https://github.com/getsentry/sentry-laravel/issues",
- "source": "https://github.com/getsentry/sentry-laravel/tree/4.8.0"
+ "source": "https://github.com/getsentry/sentry-laravel/tree/4.10.0"
},
"funding": [
{
@@ -7579,20 +7602,20 @@
"type": "custom"
}
],
- "time": "2024-08-15T19:03:01+00:00"
+ "time": "2024-11-07T08:05:24+00:00"
},
{
"name": "socialiteproviders/manager",
- "version": "v4.6.0",
+ "version": "v4.7.0",
"source": {
"type": "git",
"url": "https://github.com/SocialiteProviders/Manager.git",
- "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21"
+ "reference": "ab0691b82cec77efd90154c78f1854903455c82f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/dea5190981c31b89e52259da9ab1ca4e2b258b21",
- "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21",
+ "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/ab0691b82cec77efd90154c78f1854903455c82f",
+ "reference": "ab0691b82cec77efd90154c78f1854903455c82f",
"shasum": ""
},
"require": {
@@ -7653,7 +7676,7 @@
"issues": "https://github.com/socialiteproviders/manager/issues",
"source": "https://github.com/socialiteproviders/manager"
},
- "time": "2024-05-04T07:57:39+00:00"
+ "time": "2024-11-10T01:56:18+00:00"
},
{
"name": "socialiteproviders/microsoft-azure",
@@ -7771,16 +7794,16 @@
},
{
"name": "spatie/laravel-activitylog",
- "version": "4.8.0",
+ "version": "4.9.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-activitylog.git",
- "reference": "eb6f37dd40af950ce10cf5280f0acfa3e08c3bff"
+ "reference": "e0fc28178515a5396f48e107ed697719189bbe02"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/eb6f37dd40af950ce10cf5280f0acfa3e08c3bff",
- "reference": "eb6f37dd40af950ce10cf5280f0acfa3e08c3bff",
+ "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/e0fc28178515a5396f48e107ed697719189bbe02",
+ "reference": "e0fc28178515a5396f48e107ed697719189bbe02",
"shasum": ""
},
"require": {
@@ -7846,7 +7869,7 @@
],
"support": {
"issues": "https://github.com/spatie/laravel-activitylog/issues",
- "source": "https://github.com/spatie/laravel-activitylog/tree/4.8.0"
+ "source": "https://github.com/spatie/laravel-activitylog/tree/4.9.0"
},
"funding": [
{
@@ -7858,47 +7881,47 @@
"type": "github"
}
],
- "time": "2024-03-08T22:28:17+00:00"
+ "time": "2024-10-18T13:38:47+00:00"
},
{
"name": "spatie/laravel-data",
- "version": "3.12.0",
+ "version": "4.11.1",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-data.git",
- "reference": "d44e04839407bc32b029be59ba80090a5f720e91"
+ "reference": "df5b58baebae34475ca35338b4e9a131c9e2a8e0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/spatie/laravel-data/zipball/d44e04839407bc32b029be59ba80090a5f720e91",
- "reference": "d44e04839407bc32b029be59ba80090a5f720e91",
+ "url": "https://api.github.com/repos/spatie/laravel-data/zipball/df5b58baebae34475ca35338b4e9a131c9e2a8e0",
+ "reference": "df5b58baebae34475ca35338b4e9a131c9e2a8e0",
"shasum": ""
},
"require": {
- "illuminate/contracts": "^9.30|^10.0|^11.0",
+ "illuminate/contracts": "^10.0|^11.0",
"php": "^8.1",
- "phpdocumentor/type-resolver": "^1.5",
+ "phpdocumentor/reflection": "^6.0",
"spatie/laravel-package-tools": "^1.9.0",
"spatie/php-structure-discoverer": "^2.0"
},
"require-dev": {
"fakerphp/faker": "^1.14",
"friendsofphp/php-cs-fixer": "^3.0",
- "inertiajs/inertia-laravel": "^0.6.3",
+ "inertiajs/inertia-laravel": "^1.2",
+ "livewire/livewire": "^3.0",
"mockery/mockery": "^1.6",
"nesbot/carbon": "^2.63",
- "nette/php-generator": "^3.5",
"nunomaduro/larastan": "^2.0",
- "orchestra/testbench": "^7.6|^8.0",
- "pestphp/pest": "^1.22",
- "pestphp/pest-plugin-laravel": "^1.3",
+ "orchestra/testbench": "^8.0|^9.0",
+ "pestphp/pest": "^2.31",
+ "pestphp/pest-plugin-laravel": "^2.0",
+ "pestphp/pest-plugin-livewire": "^2.1",
"phpbench/phpbench": "^1.2",
"phpstan/extension-installer": "^1.1",
- "phpunit/phpunit": "^9.3",
+ "phpunit/phpunit": "^10.0",
"spatie/invade": "^1.0",
- "spatie/laravel-typescript-transformer": "^2.1.6",
- "spatie/pest-plugin-snapshots": "^1.1",
- "spatie/phpunit-snapshot-assertions": "^4.2",
+ "spatie/laravel-typescript-transformer": "^2.5",
+ "spatie/pest-plugin-snapshots": "^2.1",
"spatie/test-time": "^1.2"
},
"type": "library",
@@ -7911,8 +7934,7 @@
},
"autoload": {
"psr-4": {
- "Spatie\\LaravelData\\": "src",
- "Spatie\\LaravelData\\Database\\Factories\\": "database/factories"
+ "Spatie\\LaravelData\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -7935,7 +7957,7 @@
],
"support": {
"issues": "https://github.com/spatie/laravel-data/issues",
- "source": "https://github.com/spatie/laravel-data/tree/3.12.0"
+ "source": "https://github.com/spatie/laravel-data/tree/4.11.1"
},
"funding": [
{
@@ -7943,7 +7965,7 @@
"type": "github"
}
],
- "time": "2024-04-24T09:27:45+00:00"
+ "time": "2024-10-23T07:14:53+00:00"
},
{
"name": "spatie/laravel-package-tools",
@@ -8447,16 +8469,16 @@
},
{
"name": "stripe/stripe-php",
- "version": "v12.8.0",
+ "version": "v16.2.0",
"source": {
"type": "git",
"url": "https://github.com/stripe/stripe-php.git",
- "reference": "6b6f4a775ad46fee4b1df2df4fdfa574365b1621"
+ "reference": "813ae4961755af28a13bda451689f7a6ed6498cb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/stripe/stripe-php/zipball/6b6f4a775ad46fee4b1df2df4fdfa574365b1621",
- "reference": "6b6f4a775ad46fee4b1df2df4fdfa574365b1621",
+ "url": "https://api.github.com/repos/stripe/stripe-php/zipball/813ae4961755af28a13bda451689f7a6ed6498cb",
+ "reference": "813ae4961755af28a13bda451689f7a6ed6498cb",
"shasum": ""
},
"require": {
@@ -8500,22 +8522,22 @@
],
"support": {
"issues": "https://github.com/stripe/stripe-php/issues",
- "source": "https://github.com/stripe/stripe-php/tree/v12.8.0"
+ "source": "https://github.com/stripe/stripe-php/tree/v16.2.0"
},
- "time": "2023-10-16T18:04:12+00:00"
+ "time": "2024-10-29T21:15:53+00:00"
},
{
"name": "symfony/clock",
- "version": "v7.1.1",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/clock.git",
- "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7"
+ "reference": "97bebc53548684c17ed696bc8af016880f0f098d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7",
- "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7",
+ "url": "https://api.github.com/repos/symfony/clock/zipball/97bebc53548684c17ed696bc8af016880f0f098d",
+ "reference": "97bebc53548684c17ed696bc8af016880f0f098d",
"shasum": ""
},
"require": {
@@ -8560,7 +8582,7 @@
"time"
],
"support": {
- "source": "https://github.com/symfony/clock/tree/v7.1.1"
+ "source": "https://github.com/symfony/clock/tree/v7.1.6"
},
"funding": [
{
@@ -8576,20 +8598,20 @@
"type": "tidelift"
}
],
- "time": "2024-05-31T14:57:53+00:00"
+ "time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/console",
- "version": "v7.1.4",
+ "version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111"
+ "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111",
- "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111",
+ "url": "https://api.github.com/repos/symfony/console/zipball/ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
+ "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5",
"shasum": ""
},
"require": {
@@ -8653,7 +8675,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.1.4"
+ "source": "https://github.com/symfony/console/tree/v7.1.8"
},
"funding": [
{
@@ -8669,20 +8691,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-15T22:48:53+00:00"
+ "time": "2024-11-06T14:23:19+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v7.1.1",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4"
+ "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c7cee86c6f812896af54434f8ce29c8d94f9ff4",
- "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/4aa4f6b3d6749c14d3aa815eef8226632e7bbc66",
+ "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66",
"shasum": ""
},
"require": {
@@ -8718,7 +8740,7 @@
"description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/css-selector/tree/v7.1.1"
+ "source": "https://github.com/symfony/css-selector/tree/v7.1.6"
},
"funding": [
{
@@ -8734,7 +8756,7 @@
"type": "tidelift"
}
],
- "time": "2024-05-31T14:57:53+00:00"
+ "time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -8805,16 +8827,16 @@
},
{
"name": "symfony/error-handler",
- "version": "v7.1.3",
+ "version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
- "reference": "432bb369952795c61ca1def65e078c4a80dad13c"
+ "reference": "010e44661f4c6babaf8c4862fe68c24a53903342"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c",
- "reference": "432bb369952795c61ca1def65e078c4a80dad13c",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/010e44661f4c6babaf8c4862fe68c24a53903342",
+ "reference": "010e44661f4c6babaf8c4862fe68c24a53903342",
"shasum": ""
},
"require": {
@@ -8860,7 +8882,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/error-handler/tree/v7.1.3"
+ "source": "https://github.com/symfony/error-handler/tree/v7.1.7"
},
"funding": [
{
@@ -8876,20 +8898,20 @@
"type": "tidelift"
}
],
- "time": "2024-07-26T13:02:51+00:00"
+ "time": "2024-11-05T15:34:55+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v7.1.1",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7"
+ "reference": "87254c78dd50721cfd015b62277a8281c5589702"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
- "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702",
+ "reference": "87254c78dd50721cfd015b62277a8281c5589702",
"shasum": ""
},
"require": {
@@ -8940,7 +8962,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1"
+ "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.6"
},
"funding": [
{
@@ -8956,7 +8978,7 @@
"type": "tidelift"
}
],
- "time": "2024-05-31T14:57:53+00:00"
+ "time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -9036,16 +9058,16 @@
},
{
"name": "symfony/finder",
- "version": "v7.1.4",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "d95bbf319f7d052082fb7af147e0f835a695e823"
+ "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823",
- "reference": "d95bbf319f7d052082fb7af147e0f835a695e823",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/2cb89664897be33f78c65d3d2845954c8d7a43b8",
+ "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8",
"shasum": ""
},
"require": {
@@ -9080,7 +9102,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v7.1.4"
+ "source": "https://github.com/symfony/finder/tree/v7.1.6"
},
"funding": [
{
@@ -9096,20 +9118,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-13T14:28:19+00:00"
+ "time": "2024-10-01T08:31:23+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v7.1.3",
+ "version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a"
+ "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a",
- "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f4419ec69ccfc3f725a4de7c20e4e57626d10112",
+ "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112",
"shasum": ""
},
"require": {
@@ -9119,12 +9141,12 @@
},
"conflict": {
"doctrine/dbal": "<3.6",
- "symfony/cache": "<6.4"
+ "symfony/cache": "<6.4.12|>=7.0,<7.1.5"
},
"require-dev": {
"doctrine/dbal": "^3.6|^4",
"predis/predis": "^1.1|^2.0",
- "symfony/cache": "^6.4|^7.0",
+ "symfony/cache": "^6.4.12|^7.1.5",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/expression-language": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
@@ -9157,7 +9179,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v7.1.3"
+ "source": "https://github.com/symfony/http-foundation/tree/v7.1.8"
},
"funding": [
{
@@ -9173,20 +9195,20 @@
"type": "tidelift"
}
],
- "time": "2024-07-26T12:41:01+00:00"
+ "time": "2024-11-09T09:16:45+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v7.1.4",
+ "version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "6efcbd1b3f444f631c386504fc83eeca25963747"
+ "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6efcbd1b3f444f631c386504fc83eeca25963747",
- "reference": "6efcbd1b3f444f631c386504fc83eeca25963747",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/33fef24e3dc79d6d30bf4936531f2f4bd2ca189e",
+ "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e",
"shasum": ""
},
"require": {
@@ -9271,7 +9293,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v7.1.4"
+ "source": "https://github.com/symfony/http-kernel/tree/v7.1.8"
},
"funding": [
{
@@ -9287,20 +9309,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-30T17:02:28+00:00"
+ "time": "2024-11-13T14:25:32+00:00"
},
{
"name": "symfony/mailer",
- "version": "v7.1.2",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/mailer.git",
- "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee"
+ "reference": "69c9948451fb3a6a4d47dc8261d1794734e76cdd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mailer/zipball/8fcff0af9043c8f8a8e229437cea363e282f9aee",
- "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee",
+ "url": "https://api.github.com/repos/symfony/mailer/zipball/69c9948451fb3a6a4d47dc8261d1794734e76cdd",
+ "reference": "69c9948451fb3a6a4d47dc8261d1794734e76cdd",
"shasum": ""
},
"require": {
@@ -9351,7 +9373,7 @@
"description": "Helps sending emails",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/mailer/tree/v7.1.2"
+ "source": "https://github.com/symfony/mailer/tree/v7.1.6"
},
"funding": [
{
@@ -9367,20 +9389,20 @@
"type": "tidelift"
}
],
- "time": "2024-06-28T08:00:31+00:00"
+ "time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/mime",
- "version": "v7.1.4",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
- "reference": "ccaa6c2503db867f472a587291e764d6a1e58758"
+ "reference": "caa1e521edb2650b8470918dfe51708c237f0598"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mime/zipball/ccaa6c2503db867f472a587291e764d6a1e58758",
- "reference": "ccaa6c2503db867f472a587291e764d6a1e58758",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/caa1e521edb2650b8470918dfe51708c237f0598",
+ "reference": "caa1e521edb2650b8470918dfe51708c237f0598",
"shasum": ""
},
"require": {
@@ -9435,7 +9457,7 @@
"mime-type"
],
"support": {
- "source": "https://github.com/symfony/mime/tree/v7.1.4"
+ "source": "https://github.com/symfony/mime/tree/v7.1.6"
},
"funding": [
{
@@ -9451,20 +9473,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-13T14:28:19+00:00"
+ "time": "2024-10-25T15:11:02+00:00"
},
{
"name": "symfony/options-resolver",
- "version": "v7.1.1",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
- "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55"
+ "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55",
- "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55",
+ "url": "https://api.github.com/repos/symfony/options-resolver/zipball/85e95eeede2d41cd146146e98c9c81d9214cae85",
+ "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85",
"shasum": ""
},
"require": {
@@ -9502,7 +9524,7 @@
"options"
],
"support": {
- "source": "https://github.com/symfony/options-resolver/tree/v7.1.1"
+ "source": "https://github.com/symfony/options-resolver/tree/v7.1.6"
},
"funding": [
{
@@ -9518,7 +9540,7 @@
"type": "tidelift"
}
],
- "time": "2024-05-31T14:57:53+00:00"
+ "time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -10238,16 +10260,16 @@
},
{
"name": "symfony/process",
- "version": "v7.1.3",
+ "version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca"
+ "reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca",
- "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca",
+ "url": "https://api.github.com/repos/symfony/process/zipball/42783370fda6e538771f7c7a36e9fa2ee3a84892",
+ "reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892",
"shasum": ""
},
"require": {
@@ -10279,7 +10301,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v7.1.3"
+ "source": "https://github.com/symfony/process/tree/v7.1.8"
},
"funding": [
{
@@ -10295,20 +10317,20 @@
"type": "tidelift"
}
],
- "time": "2024-07-26T12:44:47+00:00"
+ "time": "2024-11-06T14:23:19+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
- "version": "v7.1.4",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/psr-http-message-bridge.git",
- "reference": "405a7bcd872f1563966f64be19f1362d94ce71ab"
+ "reference": "f16471bb19f6685b9ccf0a2c03c213840ae68cd6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/405a7bcd872f1563966f64be19f1362d94ce71ab",
- "reference": "405a7bcd872f1563966f64be19f1362d94ce71ab",
+ "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/f16471bb19f6685b9ccf0a2c03c213840ae68cd6",
+ "reference": "f16471bb19f6685b9ccf0a2c03c213840ae68cd6",
"shasum": ""
},
"require": {
@@ -10362,7 +10384,7 @@
"psr-7"
],
"support": {
- "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.1.4"
+ "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.1.6"
},
"funding": [
{
@@ -10378,20 +10400,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-15T22:48:53+00:00"
+ "time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/routing",
- "version": "v7.1.4",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7"
+ "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/1500aee0094a3ce1c92626ed8cf3c2037e86f5a7",
- "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/66a2c469f6c22d08603235c46a20007c0701ea0a",
+ "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a",
"shasum": ""
},
"require": {
@@ -10443,7 +10465,7 @@
"url"
],
"support": {
- "source": "https://github.com/symfony/routing/tree/v7.1.4"
+ "source": "https://github.com/symfony/routing/tree/v7.1.6"
},
"funding": [
{
@@ -10459,7 +10481,7 @@
"type": "tidelift"
}
],
- "time": "2024-08-29T08:16:25+00:00"
+ "time": "2024-10-01T08:31:23+00:00"
},
{
"name": "symfony/service-contracts",
@@ -10546,16 +10568,16 @@
},
{
"name": "symfony/stopwatch",
- "version": "v7.1.1",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
- "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d"
+ "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
- "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
+ "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8b4a434e6e7faf6adedffb48783a5c75409a1a05",
+ "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05",
"shasum": ""
},
"require": {
@@ -10588,7 +10610,7 @@
"description": "Provides a way to profile code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/stopwatch/tree/v7.1.1"
+ "source": "https://github.com/symfony/stopwatch/tree/v7.1.6"
},
"funding": [
{
@@ -10604,20 +10626,20 @@
"type": "tidelift"
}
],
- "time": "2024-05-31T14:57:53+00:00"
+ "time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/string",
- "version": "v7.1.4",
+ "version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b"
+ "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b",
- "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b",
+ "url": "https://api.github.com/repos/symfony/string/zipball/591ebd41565f356fcd8b090fe64dbb5878f50281",
+ "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281",
"shasum": ""
},
"require": {
@@ -10675,7 +10697,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.1.4"
+ "source": "https://github.com/symfony/string/tree/v7.1.8"
},
"funding": [
{
@@ -10691,20 +10713,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T09:59:40+00:00"
+ "time": "2024-11-13T13:31:21+00:00"
},
{
"name": "symfony/translation",
- "version": "v7.1.3",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1"
+ "reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/8d5e50c813ba2859a6dfc99a0765c550507934a1",
- "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/b9f72ab14efdb6b772f85041fa12f820dee8d55f",
+ "reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f",
"shasum": ""
},
"require": {
@@ -10769,7 +10791,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v7.1.3"
+ "source": "https://github.com/symfony/translation/tree/v7.1.6"
},
"funding": [
{
@@ -10785,7 +10807,7 @@
"type": "tidelift"
}
],
- "time": "2024-07-26T12:41:01+00:00"
+ "time": "2024-09-28T12:35:13+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -10867,16 +10889,16 @@
},
{
"name": "symfony/uid",
- "version": "v7.1.4",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/uid.git",
- "reference": "82177535395109075cdb45a70533aa3d7a521cdf"
+ "reference": "65befb3bb2d503bbffbd08c815aa38b472999917"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/uid/zipball/82177535395109075cdb45a70533aa3d7a521cdf",
- "reference": "82177535395109075cdb45a70533aa3d7a521cdf",
+ "url": "https://api.github.com/repos/symfony/uid/zipball/65befb3bb2d503bbffbd08c815aa38b472999917",
+ "reference": "65befb3bb2d503bbffbd08c815aa38b472999917",
"shasum": ""
},
"require": {
@@ -10921,7 +10943,7 @@
"uuid"
],
"support": {
- "source": "https://github.com/symfony/uid/tree/v7.1.4"
+ "source": "https://github.com/symfony/uid/tree/v7.1.6"
},
"funding": [
{
@@ -10937,20 +10959,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T09:59:40+00:00"
+ "time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v7.1.4",
+ "version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa"
+ "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a5fa7481b199090964d6fd5dab6294d5a870c7aa",
- "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8",
+ "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8",
"shasum": ""
},
"require": {
@@ -11004,7 +11026,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v7.1.4"
+ "source": "https://github.com/symfony/var-dumper/tree/v7.1.8"
},
"funding": [
{
@@ -11020,32 +11042,31 @@
"type": "tidelift"
}
],
- "time": "2024-08-30T16:12:47+00:00"
+ "time": "2024-11-08T15:46:42+00:00"
},
{
"name": "symfony/yaml",
- "version": "v6.4.11",
+ "version": "v7.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "be37e7f13195e05ab84ca5269365591edd240335"
+ "reference": "3ced3f29e4f0d6bce2170ff26719f1fe9aacc671"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/be37e7f13195e05ab84ca5269365591edd240335",
- "reference": "be37e7f13195e05ab84ca5269365591edd240335",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/3ced3f29e4f0d6bce2170ff26719f1fe9aacc671",
+ "reference": "3ced3f29e4f0d6bce2170ff26719f1fe9aacc671",
"shasum": ""
},
"require": {
- "php": ">=8.1",
- "symfony/deprecation-contracts": "^2.5|^3",
+ "php": ">=8.2",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
- "symfony/console": "<5.4"
+ "symfony/console": "<6.4"
},
"require-dev": {
- "symfony/console": "^5.4|^6.0|^7.0"
+ "symfony/console": "^6.4|^7.0"
},
"bin": [
"Resources/bin/yaml-lint"
@@ -11076,7 +11097,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v6.4.11"
+ "source": "https://github.com/symfony/yaml/tree/v7.1.6"
},
"funding": [
{
@@ -11092,7 +11113,7 @@
"type": "tidelift"
}
],
- "time": "2024-08-12T09:55:28+00:00"
+ "time": "2024-09-25T14:20:29+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@@ -11149,25 +11170,24 @@
},
{
"name": "visus/cuid2",
- "version": "2.0.0",
+ "version": "4.1.0",
"source": {
"type": "git",
"url": "https://github.com/visus-io/php-cuid2.git",
- "reference": "907919cadd8dfeb24ffecf7209ec4988fb9b3fc0"
+ "reference": "17c9b3098d556bb2556a084c948211333cc19c79"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/visus-io/php-cuid2/zipball/907919cadd8dfeb24ffecf7209ec4988fb9b3fc0",
- "reference": "907919cadd8dfeb24ffecf7209ec4988fb9b3fc0",
+ "url": "https://api.github.com/repos/visus-io/php-cuid2/zipball/17c9b3098d556bb2556a084c948211333cc19c79",
+ "reference": "17c9b3098d556bb2556a084c948211333cc19c79",
"shasum": ""
},
"require": {
- "php": "^8.0"
+ "php": "^8.1"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.29",
"ext-ctype": "*",
- "php-parallel-lint/php-parallel-lint": "^1.3",
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^10.0",
"squizlabs/php_codesniffer": "^3.7",
@@ -11187,7 +11207,7 @@
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "Apache-2.0"
+ "MIT"
],
"authors": [
{
@@ -11202,9 +11222,9 @@
],
"support": {
"issues": "https://github.com/visus-io/php-cuid2/issues",
- "source": "https://github.com/visus-io/php-cuid2/tree/2.0.0"
+ "source": "https://github.com/visus-io/php-cuid2/tree/4.1.0"
},
- "time": "2023-03-23T19:18:36+00:00"
+ "time": "2024-05-14T13:23:35+00:00"
},
{
"name": "vlucas/phpdotenv",
@@ -11742,16 +11762,16 @@
},
{
"name": "zircote/swagger-php",
- "version": "4.10.6",
+ "version": "4.11.1",
"source": {
"type": "git",
"url": "https://github.com/zircote/swagger-php.git",
- "reference": "e462ff5269ea0ec91070edd5d51dc7215bdea3b6"
+ "reference": "7df10e8ec47db07c031db317a25bef962b4e5de1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/zircote/swagger-php/zipball/e462ff5269ea0ec91070edd5d51dc7215bdea3b6",
- "reference": "e462ff5269ea0ec91070edd5d51dc7215bdea3b6",
+ "url": "https://api.github.com/repos/zircote/swagger-php/zipball/7df10e8ec47db07c031db317a25bef962b4e5de1",
+ "reference": "7df10e8ec47db07c031db317a25bef962b4e5de1",
"shasum": ""
},
"require": {
@@ -11765,7 +11785,7 @@
"require-dev": {
"composer/package-versions-deprecated": "^1.11",
"doctrine/annotations": "^1.7 || ^2.0",
- "friendsofphp/php-cs-fixer": "^2.17 || ^3.47.1",
+ "friendsofphp/php-cs-fixer": "^2.17 || 3.62.0",
"phpstan/phpstan": "^1.6",
"phpunit/phpunit": ">=8",
"vimeo/psalm": "^4.23"
@@ -11817,24 +11837,108 @@
],
"support": {
"issues": "https://github.com/zircote/swagger-php/issues",
- "source": "https://github.com/zircote/swagger-php/tree/4.10.6"
+ "source": "https://github.com/zircote/swagger-php/tree/4.11.1"
},
- "time": "2024-07-26T03:04:43+00:00"
+ "time": "2024-10-15T19:20:02+00:00"
}
],
"packages-dev": [
{
- "name": "brianium/paratest",
- "version": "v7.4.3",
+ "name": "barryvdh/laravel-debugbar",
+ "version": "v3.14.7",
"source": {
"type": "git",
- "url": "https://github.com/paratestphp/paratest.git",
- "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec"
+ "url": "https://github.com/barryvdh/laravel-debugbar.git",
+ "reference": "f484b8c9124de0b163da39958331098ffcd4a65e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/paratestphp/paratest/zipball/64fcfd0e28a6b8078a19dbf9127be2ee645b92ec",
- "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec",
+ "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f484b8c9124de0b163da39958331098ffcd4a65e",
+ "reference": "f484b8c9124de0b163da39958331098ffcd4a65e",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/routing": "^9|^10|^11",
+ "illuminate/session": "^9|^10|^11",
+ "illuminate/support": "^9|^10|^11",
+ "maximebf/debugbar": "~1.23.0",
+ "php": "^8.0",
+ "symfony/finder": "^6|^7"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.3.3",
+ "orchestra/testbench-dusk": "^5|^6|^7|^8|^9",
+ "phpunit/phpunit": "^9.6|^10.5",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.14-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Barryvdh\\Debugbar\\ServiceProvider"
+ ],
+ "aliases": {
+ "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar"
+ }
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/helpers.php"
+ ],
+ "psr-4": {
+ "Barryvdh\\Debugbar\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Barry vd. Heuvel",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "PHP Debugbar integration for Laravel",
+ "keywords": [
+ "debug",
+ "debugbar",
+ "laravel",
+ "profiler",
+ "webprofiler"
+ ],
+ "support": {
+ "issues": "https://github.com/barryvdh/laravel-debugbar/issues",
+ "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.7"
+ },
+ "funding": [
+ {
+ "url": "https://fruitcake.nl",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/barryvdh",
+ "type": "github"
+ }
+ ],
+ "time": "2024-11-14T09:12:35+00:00"
+ },
+ {
+ "name": "brianium/paratest",
+ "version": "v7.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paratestphp/paratest.git",
+ "reference": "68ff89a8de47d086588e391a516d2a5b5fde6254"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paratestphp/paratest/zipball/68ff89a8de47d086588e391a516d2a5b5fde6254",
+ "reference": "68ff89a8de47d086588e391a516d2a5b5fde6254",
"shasum": ""
},
"require": {
@@ -11842,31 +11946,30 @@
"ext-pcre": "*",
"ext-reflection": "*",
"ext-simplexml": "*",
- "fidry/cpu-core-counter": "^1.1.0",
- "jean85/pretty-package-versions": "^2.0.5",
- "php": "~8.2.0 || ~8.3.0",
- "phpunit/php-code-coverage": "^10.1.11 || ^11.0.0",
- "phpunit/php-file-iterator": "^4.1.0 || ^5.0.0",
- "phpunit/php-timer": "^6.0.0 || ^7.0.0",
- "phpunit/phpunit": "^10.5.9 || ^11.0.3",
- "sebastian/environment": "^6.0.1 || ^7.0.0",
- "symfony/console": "^6.4.3 || ^7.0.3",
- "symfony/process": "^6.4.3 || ^7.0.3"
+ "fidry/cpu-core-counter": "^1.2.0",
+ "jean85/pretty-package-versions": "^2.0.6",
+ "php": "~8.2.0 || ~8.3.0 || ~8.4.0",
+ "phpunit/php-code-coverage": "^11.0.7",
+ "phpunit/php-file-iterator": "^5.1.0",
+ "phpunit/php-timer": "^7.0.1",
+ "phpunit/phpunit": "^11.4.1",
+ "sebastian/environment": "^7.2.0",
+ "symfony/console": "^6.4.11 || ^7.1.5",
+ "symfony/process": "^6.4.8 || ^7.1.5"
},
"require-dev": {
"doctrine/coding-standard": "^12.0.0",
"ext-pcov": "*",
"ext-posix": "*",
- "phpstan/phpstan": "^1.10.58",
- "phpstan/phpstan-deprecation-rules": "^1.1.4",
- "phpstan/phpstan-phpunit": "^1.3.15",
- "phpstan/phpstan-strict-rules": "^1.5.2",
- "squizlabs/php_codesniffer": "^3.9.0",
- "symfony/filesystem": "^6.4.3 || ^7.0.3"
+ "phpstan/phpstan": "^1.12.6",
+ "phpstan/phpstan-deprecation-rules": "^1.2.1",
+ "phpstan/phpstan-phpunit": "^1.4.0",
+ "phpstan/phpstan-strict-rules": "^1.6.1",
+ "squizlabs/php_codesniffer": "^3.10.3",
+ "symfony/filesystem": "^6.4.9 || ^7.1.5"
},
"bin": [
"bin/paratest",
- "bin/paratest.bat",
"bin/paratest_for_phpstorm"
],
"type": "library",
@@ -11903,7 +12006,7 @@
],
"support": {
"issues": "https://github.com/paratestphp/paratest/issues",
- "source": "https://github.com/paratestphp/paratest/tree/v7.4.3"
+ "source": "https://github.com/paratestphp/paratest/tree/v7.6.0"
},
"funding": [
{
@@ -11915,20 +12018,20 @@
"type": "paypal"
}
],
- "time": "2024-02-20T07:24:02+00:00"
+ "time": "2024-10-15T12:38:31+00:00"
},
{
"name": "fakerphp/faker",
- "version": "v1.23.1",
+ "version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/FakerPHP/Faker.git",
- "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b"
+ "reference": "a136842a532bac9ecd8a1c723852b09915d7db50"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b",
- "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b",
+ "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/a136842a532bac9ecd8a1c723852b09915d7db50",
+ "reference": "a136842a532bac9ecd8a1c723852b09915d7db50",
"shasum": ""
},
"require": {
@@ -11976,9 +12079,9 @@
],
"support": {
"issues": "https://github.com/FakerPHP/Faker/issues",
- "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1"
+ "source": "https://github.com/FakerPHP/Faker/tree/v1.24.0"
},
- "time": "2024-01-02T13:46:09+00:00"
+ "time": "2024-11-07T15:11:20+00:00"
},
{
"name": "fidry/cpu-core-counter",
@@ -12043,26 +12146,26 @@
},
{
"name": "filp/whoops",
- "version": "2.15.4",
+ "version": "2.16.0",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
- "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546"
+ "reference": "befcdc0e5dce67252aa6322d82424be928214fa2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546",
- "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546",
+ "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2",
+ "reference": "befcdc0e5dce67252aa6322d82424be928214fa2",
"shasum": ""
},
"require": {
- "php": "^5.5.9 || ^7.0 || ^8.0",
+ "php": "^7.1 || ^8.0",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"require-dev": {
- "mockery/mockery": "^0.9 || ^1.0",
- "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
- "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
+ "mockery/mockery": "^1.0",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
+ "symfony/var-dumper": "^4.0 || ^5.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
@@ -12102,7 +12205,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
- "source": "https://github.com/filp/whoops/tree/2.15.4"
+ "source": "https://github.com/filp/whoops/tree/2.16.0"
},
"funding": [
{
@@ -12110,7 +12213,7 @@
"type": "github"
}
],
- "time": "2023-11-03T12:00:00+00:00"
+ "time": "2024-09-25T12:00:00+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@@ -12165,16 +12268,16 @@
},
{
"name": "laravel/dusk",
- "version": "v8.2.5",
+ "version": "v8.2.11",
"source": {
"type": "git",
"url": "https://github.com/laravel/dusk.git",
- "reference": "e641800393ce4ad39f0a47133f51aae67ceb01ad"
+ "reference": "c667db6d8795f0ccc8f63d54a7780ce8a0cc3d3c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/dusk/zipball/e641800393ce4ad39f0a47133f51aae67ceb01ad",
- "reference": "e641800393ce4ad39f0a47133f51aae67ceb01ad",
+ "url": "https://api.github.com/repos/laravel/dusk/zipball/c667db6d8795f0ccc8f63d54a7780ce8a0cc3d3c",
+ "reference": "c667db6d8795f0ccc8f63d54a7780ce8a0cc3d3c",
"shasum": ""
},
"require": {
@@ -12231,22 +12334,22 @@
],
"support": {
"issues": "https://github.com/laravel/dusk/issues",
- "source": "https://github.com/laravel/dusk/tree/v8.2.5"
+ "source": "https://github.com/laravel/dusk/tree/v8.2.11"
},
- "time": "2024-08-26T12:34:33+00:00"
+ "time": "2024-11-07T21:51:32+00:00"
},
{
"name": "laravel/pint",
- "version": "v1.17.3",
+ "version": "v1.18.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
- "reference": "9d77be916e145864f10788bb94531d03e1f7b482"
+ "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/pint/zipball/9d77be916e145864f10788bb94531d03e1f7b482",
- "reference": "9d77be916e145864f10788bb94531d03e1f7b482",
+ "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9",
+ "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9",
"shasum": ""
},
"require": {
@@ -12299,7 +12402,144 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
- "time": "2024-09-03T15:00:28+00:00"
+ "time": "2024-09-24T17:22:50+00:00"
+ },
+ {
+ "name": "laravel/telescope",
+ "version": "v5.2.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/telescope.git",
+ "reference": "f68386a8d816c9e3a011b8301bfd263213bf00d4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/telescope/zipball/f68386a8d816c9e3a011b8301bfd263213bf00d4",
+ "reference": "f68386a8d816c9e3a011b8301bfd263213bf00d4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "laravel/framework": "^8.37|^9.0|^10.0|^11.0",
+ "php": "^8.0",
+ "symfony/console": "^5.3|^6.0|^7.0",
+ "symfony/var-dumper": "^5.0|^6.0|^7.0"
+ },
+ "require-dev": {
+ "ext-gd": "*",
+ "guzzlehttp/guzzle": "^6.0|^7.0",
+ "laravel/octane": "^1.4|^2.0|dev-develop",
+ "orchestra/testbench": "^6.40|^7.37|^8.17|^9.0",
+ "phpstan/phpstan": "^1.10",
+ "phpunit/phpunit": "^9.0|^10.5"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Laravel\\Telescope\\TelescopeServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\Telescope\\": "src/",
+ "Laravel\\Telescope\\Database\\Factories\\": "database/factories/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ },
+ {
+ "name": "Mohamed Said",
+ "email": "mohamed@laravel.com"
+ }
+ ],
+ "description": "An elegant debug assistant for the Laravel framework.",
+ "keywords": [
+ "debugging",
+ "laravel",
+ "monitoring"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/telescope/issues",
+ "source": "https://github.com/laravel/telescope/tree/v5.2.5"
+ },
+ "time": "2024-10-31T17:06:07+00:00"
+ },
+ {
+ "name": "maximebf/debugbar",
+ "version": "v1.23.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/maximebf/php-debugbar.git",
+ "reference": "687400043d77943ef95e8417cb44e1673ee57844"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/687400043d77943ef95e8417cb44e1673ee57844",
+ "reference": "687400043d77943ef95e8417cb44e1673ee57844",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2|^8",
+ "psr/log": "^1|^2|^3",
+ "symfony/var-dumper": "^4|^5|^6|^7"
+ },
+ "require-dev": {
+ "dbrekelmans/bdi": "^1",
+ "phpunit/phpunit": "^8|^9",
+ "symfony/panther": "^1|^2.1",
+ "twig/twig": "^1.38|^2.7|^3.0"
+ },
+ "suggest": {
+ "kriswallsmith/assetic": "The best way to manage assets",
+ "monolog/monolog": "Log using Monolog",
+ "predis/predis": "Redis storage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.23-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "DebugBar\\": "src/DebugBar/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Maxime Bouroumeau-Fuseau",
+ "email": "maxime.bouroumeau@gmail.com",
+ "homepage": "http://maximebf.com"
+ },
+ {
+ "name": "Barry vd. Heuvel",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "Debug bar in the browser for php application",
+ "homepage": "https://github.com/maximebf/php-debugbar",
+ "keywords": [
+ "debug",
+ "debugbar"
+ ],
+ "support": {
+ "issues": "https://github.com/maximebf/php-debugbar/issues",
+ "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.3"
+ },
+ "time": "2024-10-29T12:24:25+00:00"
},
{
"name": "mockery/mockery",
@@ -12386,16 +12626,16 @@
},
{
"name": "myclabs/deep-copy",
- "version": "1.12.0",
+ "version": "1.12.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c"
+ "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
- "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
+ "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
"shasum": ""
},
"require": {
@@ -12434,7 +12674,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0"
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
},
"funding": [
{
@@ -12442,27 +12682,27 @@
"type": "tidelift"
}
],
- "time": "2024-06-12T14:39:25+00:00"
+ "time": "2024-11-08T17:47:46+00:00"
},
{
"name": "nunomaduro/collision",
- "version": "v8.4.0",
+ "version": "v8.5.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
- "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a"
+ "reference": "f5c101b929c958e849a633283adff296ed5f38f5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nunomaduro/collision/zipball/e7d1aa8ed753f63fa816932bbc89678238843b4a",
- "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a",
+ "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f5c101b929c958e849a633283adff296ed5f38f5",
+ "reference": "f5c101b929c958e849a633283adff296ed5f38f5",
"shasum": ""
},
"require": {
- "filp/whoops": "^2.15.4",
- "nunomaduro/termwind": "^2.0.1",
+ "filp/whoops": "^2.16.0",
+ "nunomaduro/termwind": "^2.1.0",
"php": "^8.2.0",
- "symfony/console": "^7.1.3"
+ "symfony/console": "^7.1.5"
},
"conflict": {
"laravel/framework": "<11.0.0 || >=12.0.0",
@@ -12470,14 +12710,14 @@
},
"require-dev": {
"larastan/larastan": "^2.9.8",
- "laravel/framework": "^11.19.0",
- "laravel/pint": "^1.17.1",
- "laravel/sail": "^1.31.0",
- "laravel/sanctum": "^4.0.2",
- "laravel/tinker": "^2.9.0",
- "orchestra/testbench-core": "^9.2.3",
- "pestphp/pest": "^2.35.0 || ^3.0.0",
- "sebastian/environment": "^6.1.0 || ^7.0.0"
+ "laravel/framework": "^11.28.0",
+ "laravel/pint": "^1.18.1",
+ "laravel/sail": "^1.36.0",
+ "laravel/sanctum": "^4.0.3",
+ "laravel/tinker": "^2.10.0",
+ "orchestra/testbench-core": "^9.5.3",
+ "pestphp/pest": "^2.36.0 || ^3.4.0",
+ "sebastian/environment": "^6.1.0 || ^7.2.0"
},
"type": "library",
"extra": {
@@ -12539,40 +12779,42 @@
"type": "patreon"
}
],
- "time": "2024-08-03T15:32:23+00:00"
+ "time": "2024-10-15T16:06:32+00:00"
},
{
"name": "pestphp/pest",
- "version": "v2.35.1",
+ "version": "v3.5.1",
"source": {
"type": "git",
"url": "https://github.com/pestphp/pest.git",
- "reference": "b13acb630df52c06123588d321823c31fc685545"
+ "reference": "179d46ce97d52bcb3f791449ae94025c3f32e3e3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pestphp/pest/zipball/b13acb630df52c06123588d321823c31fc685545",
- "reference": "b13acb630df52c06123588d321823c31fc685545",
+ "url": "https://api.github.com/repos/pestphp/pest/zipball/179d46ce97d52bcb3f791449ae94025c3f32e3e3",
+ "reference": "179d46ce97d52bcb3f791449ae94025c3f32e3e3",
"shasum": ""
},
"require": {
- "brianium/paratest": "^7.3.1",
- "nunomaduro/collision": "^7.10.0|^8.4.0",
- "nunomaduro/termwind": "^1.15.1|^2.0.1",
- "pestphp/pest-plugin": "^2.1.1",
- "pestphp/pest-plugin-arch": "^2.7.0",
- "php": "^8.1.0",
- "phpunit/phpunit": "^10.5.17"
+ "brianium/paratest": "^7.6.0",
+ "nunomaduro/collision": "^8.5.0",
+ "nunomaduro/termwind": "^2.2.0",
+ "pestphp/pest-plugin": "^3.0.0",
+ "pestphp/pest-plugin-arch": "^3.0.0",
+ "pestphp/pest-plugin-mutate": "^3.0.5",
+ "php": "^8.2.0",
+ "phpunit/phpunit": "^11.4.3"
},
"conflict": {
- "phpunit/phpunit": ">10.5.17",
- "sebastian/exporter": "<5.1.0",
+ "filp/whoops": "<2.16.0",
+ "phpunit/phpunit": ">11.4.3",
+ "sebastian/exporter": "<6.0.0",
"webmozart/assert": "<1.11.0"
},
"require-dev": {
- "pestphp/pest-dev-tools": "^2.16.0",
- "pestphp/pest-plugin-type-coverage": "^2.8.5",
- "symfony/process": "^6.4.0|^7.1.3"
+ "pestphp/pest-dev-tools": "^3.3.0",
+ "pestphp/pest-plugin-type-coverage": "^3.1.0",
+ "symfony/process": "^7.1.6"
},
"bin": [
"bin/pest"
@@ -12581,6 +12823,8 @@
"extra": {
"pest": {
"plugins": [
+ "Pest\\Mutate\\Plugins\\Mutate",
+ "Pest\\Plugins\\Configuration",
"Pest\\Plugins\\Bail",
"Pest\\Plugins\\Cache",
"Pest\\Plugins\\Coverage",
@@ -12635,7 +12879,7 @@
],
"support": {
"issues": "https://github.com/pestphp/pest/issues",
- "source": "https://github.com/pestphp/pest/tree/v2.35.1"
+ "source": "https://github.com/pestphp/pest/tree/v3.5.1"
},
"funding": [
{
@@ -12647,34 +12891,34 @@
"type": "github"
}
],
- "time": "2024-08-20T21:41:50+00:00"
+ "time": "2024-10-31T16:12:45+00:00"
},
{
"name": "pestphp/pest-plugin",
- "version": "v2.1.1",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/pestphp/pest-plugin.git",
- "reference": "e05d2859e08c2567ee38ce8b005d044e72648c0b"
+ "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/e05d2859e08c2567ee38ce8b005d044e72648c0b",
- "reference": "e05d2859e08c2567ee38ce8b005d044e72648c0b",
+ "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/e79b26c65bc11c41093b10150c1341cc5cdbea83",
+ "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83",
"shasum": ""
},
"require": {
"composer-plugin-api": "^2.0.0",
"composer-runtime-api": "^2.2.2",
- "php": "^8.1"
+ "php": "^8.2"
},
"conflict": {
- "pestphp/pest": "<2.2.3"
+ "pestphp/pest": "<3.0.0"
},
"require-dev": {
- "composer/composer": "^2.5.8",
- "pestphp/pest": "^2.16.0",
- "pestphp/pest-dev-tools": "^2.16.0"
+ "composer/composer": "^2.7.9",
+ "pestphp/pest": "^3.0.0",
+ "pestphp/pest-dev-tools": "^3.0.0"
},
"type": "composer-plugin",
"extra": {
@@ -12701,7 +12945,7 @@
"unit"
],
"support": {
- "source": "https://github.com/pestphp/pest-plugin/tree/v2.1.1"
+ "source": "https://github.com/pestphp/pest-plugin/tree/v3.0.0"
},
"funding": [
{
@@ -12717,31 +12961,30 @@
"type": "patreon"
}
],
- "time": "2023-08-22T08:40:06+00:00"
+ "time": "2024-09-08T23:21:41+00:00"
},
{
"name": "pestphp/pest-plugin-arch",
- "version": "v2.7.0",
+ "version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/pestphp/pest-plugin-arch.git",
- "reference": "d23b2d7498475354522c3818c42ef355dca3fcda"
+ "reference": "0a27e55a270cfe73d8cb70551b91002ee2cb64b0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/d23b2d7498475354522c3818c42ef355dca3fcda",
- "reference": "d23b2d7498475354522c3818c42ef355dca3fcda",
+ "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/0a27e55a270cfe73d8cb70551b91002ee2cb64b0",
+ "reference": "0a27e55a270cfe73d8cb70551b91002ee2cb64b0",
"shasum": ""
},
"require": {
- "nunomaduro/collision": "^7.10.0|^8.1.0",
- "pestphp/pest-plugin": "^2.1.1",
- "php": "^8.1",
+ "pestphp/pest-plugin": "^3.0.0",
+ "php": "^8.2",
"ta-tikoma/phpunit-architecture-test": "^0.8.4"
},
"require-dev": {
- "pestphp/pest": "^2.33.0",
- "pestphp/pest-dev-tools": "^2.16.0"
+ "pestphp/pest": "^3.0.0",
+ "pestphp/pest-dev-tools": "^3.0.0"
},
"type": "library",
"extra": {
@@ -12776,7 +13019,7 @@
"unit"
],
"support": {
- "source": "https://github.com/pestphp/pest-plugin-arch/tree/v2.7.0"
+ "source": "https://github.com/pestphp/pest-plugin-arch/tree/v3.0.0"
},
"funding": [
{
@@ -12788,7 +13031,79 @@
"type": "github"
}
],
- "time": "2024-01-26T09:46:42+00:00"
+ "time": "2024-09-08T23:23:55+00:00"
+ },
+ {
+ "name": "pestphp/pest-plugin-mutate",
+ "version": "v3.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/pestphp/pest-plugin-mutate.git",
+ "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/pestphp/pest-plugin-mutate/zipball/e10dbdc98c9e2f3890095b4fe2144f63a5717e08",
+ "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.2.0",
+ "pestphp/pest-plugin": "^3.0.0",
+ "php": "^8.2",
+ "psr/simple-cache": "^3.0.0"
+ },
+ "require-dev": {
+ "pestphp/pest": "^3.0.8",
+ "pestphp/pest-dev-tools": "^3.0.0",
+ "pestphp/pest-plugin-type-coverage": "^3.0.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Pest\\Mutate\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Sandro Gehri",
+ "email": "sandrogehri@gmail.com"
+ }
+ ],
+ "description": "Mutates your code to find untested cases",
+ "keywords": [
+ "framework",
+ "mutate",
+ "mutation",
+ "pest",
+ "php",
+ "plugin",
+ "test",
+ "testing",
+ "unit"
+ ],
+ "support": {
+ "source": "https://github.com/pestphp/pest-plugin-mutate/tree/v3.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.com/paypalme/enunomaduro",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/gehrisandro",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nunomaduro",
+ "type": "github"
+ }
+ ],
+ "time": "2024-09-22T07:54:40+00:00"
},
{
"name": "phar-io/manifest",
@@ -12974,101 +13289,37 @@
},
"time": "2023-10-20T12:21:20+00:00"
},
- {
- "name": "phpdocumentor/reflection-docblock",
- "version": "5.4.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c",
- "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c",
- "shasum": ""
- },
- "require": {
- "doctrine/deprecations": "^1.1",
- "ext-filter": "*",
- "php": "^7.4 || ^8.0",
- "phpdocumentor/reflection-common": "^2.2",
- "phpdocumentor/type-resolver": "^1.7",
- "phpstan/phpdoc-parser": "^1.7",
- "webmozart/assert": "^1.9.1"
- },
- "require-dev": {
- "mockery/mockery": "~1.3.5",
- "phpstan/extension-installer": "^1.1",
- "phpstan/phpstan": "^1.8",
- "phpstan/phpstan-mockery": "^1.1",
- "phpstan/phpstan-webmozart-assert": "^1.2",
- "phpunit/phpunit": "^9.5",
- "vimeo/psalm": "^5.13"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "5.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- },
- {
- "name": "Jaap van Otterdijk",
- "email": "opensource@ijaap.nl"
- }
- ],
- "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
- "support": {
- "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
- "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1"
- },
- "time": "2024-05-21T05:55:05+00:00"
- },
{
"name": "phpunit/php-code-coverage",
- "version": "10.1.16",
+ "version": "11.0.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "7e308268858ed6baedc8704a304727d20bc07c77"
+ "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77",
- "reference": "7e308268858ed6baedc8704a304727d20bc07c77",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca",
+ "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^4.19.1 || ^5.1.0",
- "php": ">=8.1",
- "phpunit/php-file-iterator": "^4.1.0",
- "phpunit/php-text-template": "^3.0.1",
- "sebastian/code-unit-reverse-lookup": "^3.0.0",
- "sebastian/complexity": "^3.2.0",
- "sebastian/environment": "^6.1.0",
- "sebastian/lines-of-code": "^2.0.2",
- "sebastian/version": "^4.0.1",
+ "nikic/php-parser": "^5.3.1",
+ "php": ">=8.2",
+ "phpunit/php-file-iterator": "^5.1.0",
+ "phpunit/php-text-template": "^4.0.1",
+ "sebastian/code-unit-reverse-lookup": "^4.0.1",
+ "sebastian/complexity": "^4.0.1",
+ "sebastian/environment": "^7.2.0",
+ "sebastian/lines-of-code": "^3.0.1",
+ "sebastian/version": "^5.0.2",
"theseer/tokenizer": "^1.2.3"
},
"require-dev": {
- "phpunit/phpunit": "^10.1"
+ "phpunit/phpunit": "^11.4.1"
},
"suggest": {
"ext-pcov": "PHP extension that provides line coverage",
@@ -13077,7 +13328,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "10.1.x-dev"
+ "dev-main": "11.0.x-dev"
}
},
"autoload": {
@@ -13106,7 +13357,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7"
},
"funding": [
{
@@ -13114,32 +13365,32 @@
"type": "github"
}
],
- "time": "2024-08-22T04:31:57+00:00"
+ "time": "2024-10-09T06:21:38+00:00"
},
{
"name": "phpunit/php-file-iterator",
- "version": "4.1.0",
+ "version": "5.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c"
+ "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c",
- "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6",
+ "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "4.0-dev"
+ "dev-main": "5.0-dev"
}
},
"autoload": {
@@ -13167,7 +13418,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
"security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
- "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0"
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0"
},
"funding": [
{
@@ -13175,28 +13426,28 @@
"type": "github"
}
],
- "time": "2023-08-31T06:24:48+00:00"
+ "time": "2024-08-27T05:02:59+00:00"
},
{
"name": "phpunit/php-invoker",
- "version": "4.0.0",
+ "version": "5.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-invoker.git",
- "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7"
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
- "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2",
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
"ext-pcntl": "*",
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"suggest": {
"ext-pcntl": "*"
@@ -13204,7 +13455,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "4.0-dev"
+ "dev-main": "5.0-dev"
}
},
"autoload": {
@@ -13230,7 +13481,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-invoker/issues",
- "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0"
+ "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1"
},
"funding": [
{
@@ -13238,32 +13490,32 @@
"type": "github"
}
],
- "time": "2023-02-03T06:56:09+00:00"
+ "time": "2024-07-03T05:07:44+00:00"
},
{
"name": "phpunit/php-text-template",
- "version": "3.0.1",
+ "version": "4.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
- "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748"
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748",
- "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.0-dev"
+ "dev-main": "4.0-dev"
}
},
"autoload": {
@@ -13290,7 +13542,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-text-template/issues",
"security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
- "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1"
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1"
},
"funding": [
{
@@ -13298,32 +13550,32 @@
"type": "github"
}
],
- "time": "2023-08-31T14:07:24+00:00"
+ "time": "2024-07-03T05:08:43+00:00"
},
{
"name": "phpunit/php-timer",
- "version": "6.0.0",
+ "version": "7.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
- "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d"
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d",
- "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "6.0-dev"
+ "dev-main": "7.0-dev"
}
},
"autoload": {
@@ -13349,7 +13601,8 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-timer/issues",
- "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0"
+ "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1"
},
"funding": [
{
@@ -13357,20 +13610,20 @@
"type": "github"
}
],
- "time": "2023-02-03T06:57:52+00:00"
+ "time": "2024-07-03T05:09:35+00:00"
},
{
"name": "phpunit/phpunit",
- "version": "10.5.17",
+ "version": "11.4.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "c1f736a473d21957ead7e94fcc029f571895abf5"
+ "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c1f736a473d21957ead7e94fcc029f571895abf5",
- "reference": "c1f736a473d21957ead7e94fcc029f571895abf5",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e8e8ed1854de5d36c088ec1833beae40d2dedd76",
+ "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76",
"shasum": ""
},
"require": {
@@ -13380,26 +13633,25 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
- "myclabs/deep-copy": "^1.10.1",
- "phar-io/manifest": "^2.0.3",
- "phar-io/version": "^3.0.2",
- "php": ">=8.1",
- "phpunit/php-code-coverage": "^10.1.5",
- "phpunit/php-file-iterator": "^4.0",
- "phpunit/php-invoker": "^4.0",
- "phpunit/php-text-template": "^3.0",
- "phpunit/php-timer": "^6.0",
- "sebastian/cli-parser": "^2.0",
- "sebastian/code-unit": "^2.0",
- "sebastian/comparator": "^5.0",
- "sebastian/diff": "^5.0",
- "sebastian/environment": "^6.0",
- "sebastian/exporter": "^5.1",
- "sebastian/global-state": "^6.0.1",
- "sebastian/object-enumerator": "^5.0",
- "sebastian/recursion-context": "^5.0",
- "sebastian/type": "^4.0",
- "sebastian/version": "^4.0"
+ "myclabs/deep-copy": "^1.12.0",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
+ "php": ">=8.2",
+ "phpunit/php-code-coverage": "^11.0.7",
+ "phpunit/php-file-iterator": "^5.1.0",
+ "phpunit/php-invoker": "^5.0.1",
+ "phpunit/php-text-template": "^4.0.1",
+ "phpunit/php-timer": "^7.0.1",
+ "sebastian/cli-parser": "^3.0.2",
+ "sebastian/code-unit": "^3.0.1",
+ "sebastian/comparator": "^6.1.1",
+ "sebastian/diff": "^6.0.2",
+ "sebastian/environment": "^7.2.0",
+ "sebastian/exporter": "^6.1.3",
+ "sebastian/global-state": "^7.0.2",
+ "sebastian/object-enumerator": "^6.0.1",
+ "sebastian/type": "^5.1.0",
+ "sebastian/version": "^5.0.2"
},
"suggest": {
"ext-soap": "To be able to generate mocks based on WSDL files"
@@ -13410,7 +13662,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "10.5-dev"
+ "dev-main": "11.4-dev"
}
},
"autoload": {
@@ -13442,7 +13694,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.17"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.3"
},
"funding": [
{
@@ -13458,32 +13710,32 @@
"type": "tidelift"
}
],
- "time": "2024-04-05T04:39:01+00:00"
+ "time": "2024-10-28T13:07:50+00:00"
},
{
"name": "sebastian/cli-parser",
- "version": "2.0.1",
+ "version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git",
- "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084"
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084",
- "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180",
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "2.0-dev"
+ "dev-main": "3.0-dev"
}
},
"autoload": {
@@ -13507,7 +13759,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/cli-parser/issues",
"security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
- "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1"
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2"
},
"funding": [
{
@@ -13515,32 +13767,32 @@
"type": "github"
}
],
- "time": "2024-03-02T07:12:49+00:00"
+ "time": "2024-07-03T04:41:36+00:00"
},
{
"name": "sebastian/code-unit",
- "version": "2.0.0",
+ "version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/code-unit.git",
- "reference": "a81fee9eef0b7a76af11d121767abc44c104e503"
+ "reference": "6bb7d09d6623567178cf54126afa9c2310114268"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503",
- "reference": "a81fee9eef0b7a76af11d121767abc44c104e503",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268",
+ "reference": "6bb7d09d6623567178cf54126afa9c2310114268",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "2.0-dev"
+ "dev-main": "3.0-dev"
}
},
"autoload": {
@@ -13563,7 +13815,8 @@
"homepage": "https://github.com/sebastianbergmann/code-unit",
"support": {
"issues": "https://github.com/sebastianbergmann/code-unit/issues",
- "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0"
+ "security": "https://github.com/sebastianbergmann/code-unit/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1"
},
"funding": [
{
@@ -13571,32 +13824,32 @@
"type": "github"
}
],
- "time": "2023-02-03T06:58:43+00:00"
+ "time": "2024-07-03T04:44:28+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
- "version": "3.0.0",
+ "version": "4.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
- "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d"
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
- "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e",
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.0-dev"
+ "dev-main": "4.0-dev"
}
},
"autoload": {
@@ -13618,7 +13871,8 @@
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"support": {
"issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
- "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0"
+ "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1"
},
"funding": [
{
@@ -13626,36 +13880,36 @@
"type": "github"
}
],
- "time": "2023-02-03T06:59:15+00:00"
+ "time": "2024-07-03T04:45:54+00:00"
},
{
"name": "sebastian/comparator",
- "version": "5.0.2",
+ "version": "6.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53"
+ "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53",
- "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739",
+ "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-mbstring": "*",
- "php": ">=8.1",
- "sebastian/diff": "^5.0",
- "sebastian/exporter": "^5.0"
+ "php": ">=8.2",
+ "sebastian/diff": "^6.0",
+ "sebastian/exporter": "^6.0"
},
"require-dev": {
- "phpunit/phpunit": "^10.4"
+ "phpunit/phpunit": "^11.4"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "5.0-dev"
+ "dev-main": "6.2-dev"
}
},
"autoload": {
@@ -13695,7 +13949,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
- "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.2"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1"
},
"funding": [
{
@@ -13703,33 +13957,33 @@
"type": "github"
}
],
- "time": "2024-08-12T06:03:08+00:00"
+ "time": "2024-10-31T05:30:08+00:00"
},
{
"name": "sebastian/complexity",
- "version": "3.2.0",
+ "version": "4.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git",
- "reference": "68ff824baeae169ec9f2137158ee529584553799"
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799",
- "reference": "68ff824baeae169ec9f2137158ee529584553799",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0",
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0",
"shasum": ""
},
"require": {
- "nikic/php-parser": "^4.18 || ^5.0",
- "php": ">=8.1"
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.2-dev"
+ "dev-main": "4.0-dev"
}
},
"autoload": {
@@ -13753,7 +14007,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues",
"security": "https://github.com/sebastianbergmann/complexity/security/policy",
- "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0"
+ "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1"
},
"funding": [
{
@@ -13761,33 +14015,33 @@
"type": "github"
}
],
- "time": "2023-12-21T08:37:17+00:00"
+ "time": "2024-07-03T04:49:50+00:00"
},
{
"name": "sebastian/diff",
- "version": "5.1.1",
+ "version": "6.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e",
- "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544",
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0",
- "symfony/process": "^6.4"
+ "phpunit/phpunit": "^11.0",
+ "symfony/process": "^4.2 || ^5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "5.1-dev"
+ "dev-main": "6.0-dev"
}
},
"autoload": {
@@ -13820,7 +14074,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"security": "https://github.com/sebastianbergmann/diff/security/policy",
- "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1"
+ "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2"
},
"funding": [
{
@@ -13828,27 +14082,27 @@
"type": "github"
}
],
- "time": "2024-03-02T07:15:17+00:00"
+ "time": "2024-07-03T04:53:05+00:00"
},
{
"name": "sebastian/environment",
- "version": "6.1.0",
+ "version": "7.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "8074dbcd93529b357029f5cc5058fd3e43666984"
+ "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984",
- "reference": "8074dbcd93529b357029f5cc5058fd3e43666984",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5",
+ "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"suggest": {
"ext-posix": "*"
@@ -13856,7 +14110,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "6.1-dev"
+ "dev-main": "7.2-dev"
}
},
"autoload": {
@@ -13884,7 +14138,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"security": "https://github.com/sebastianbergmann/environment/security/policy",
- "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0"
+ "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0"
},
"funding": [
{
@@ -13892,34 +14146,34 @@
"type": "github"
}
],
- "time": "2024-03-23T08:47:14+00:00"
+ "time": "2024-07-03T04:54:44+00:00"
},
{
"name": "sebastian/exporter",
- "version": "5.1.2",
+ "version": "6.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "955288482d97c19a372d3f31006ab3f37da47adf"
+ "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf",
- "reference": "955288482d97c19a372d3f31006ab3f37da47adf",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e",
+ "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
- "php": ">=8.1",
- "sebastian/recursion-context": "^5.0"
+ "php": ">=8.2",
+ "sebastian/recursion-context": "^6.0"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.2"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "5.1-dev"
+ "dev-main": "6.1-dev"
}
},
"autoload": {
@@ -13962,7 +14216,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"security": "https://github.com/sebastianbergmann/exporter/security/policy",
- "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2"
+ "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3"
},
"funding": [
{
@@ -13970,35 +14224,35 @@
"type": "github"
}
],
- "time": "2024-03-02T07:17:12+00:00"
+ "time": "2024-07-03T04:56:19+00:00"
},
{
"name": "sebastian/global-state",
- "version": "6.0.2",
+ "version": "7.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9"
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
- "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7",
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7",
"shasum": ""
},
"require": {
- "php": ">=8.1",
- "sebastian/object-reflector": "^3.0",
- "sebastian/recursion-context": "^5.0"
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
},
"require-dev": {
"ext-dom": "*",
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "6.0-dev"
+ "dev-main": "7.0-dev"
}
},
"autoload": {
@@ -14024,7 +14278,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"security": "https://github.com/sebastianbergmann/global-state/security/policy",
- "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2"
+ "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2"
},
"funding": [
{
@@ -14032,33 +14286,33 @@
"type": "github"
}
],
- "time": "2024-03-02T07:19:19+00:00"
+ "time": "2024-07-03T04:57:36+00:00"
},
{
"name": "sebastian/lines-of-code",
- "version": "2.0.2",
+ "version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
- "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0"
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0",
- "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a",
"shasum": ""
},
"require": {
- "nikic/php-parser": "^4.18 || ^5.0",
- "php": ">=8.1"
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "2.0-dev"
+ "dev-main": "3.0-dev"
}
},
"autoload": {
@@ -14082,7 +14336,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
- "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2"
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1"
},
"funding": [
{
@@ -14090,34 +14344,34 @@
"type": "github"
}
],
- "time": "2023-12-21T08:38:20+00:00"
+ "time": "2024-07-03T04:58:38+00:00"
},
{
"name": "sebastian/object-enumerator",
- "version": "5.0.0",
+ "version": "6.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-enumerator.git",
- "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906"
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906",
- "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa",
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa",
"shasum": ""
},
"require": {
- "php": ">=8.1",
- "sebastian/object-reflector": "^3.0",
- "sebastian/recursion-context": "^5.0"
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "5.0-dev"
+ "dev-main": "6.0-dev"
}
},
"autoload": {
@@ -14139,7 +14393,8 @@
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
- "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0"
+ "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1"
},
"funding": [
{
@@ -14147,32 +14402,32 @@
"type": "github"
}
],
- "time": "2023-02-03T07:08:32+00:00"
+ "time": "2024-07-03T05:00:13+00:00"
},
{
"name": "sebastian/object-reflector",
- "version": "3.0.0",
+ "version": "4.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-reflector.git",
- "reference": "24ed13d98130f0e7122df55d06c5c4942a577957"
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957",
- "reference": "24ed13d98130f0e7122df55d06c5c4942a577957",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.0-dev"
+ "dev-main": "4.0-dev"
}
},
"autoload": {
@@ -14194,7 +14449,8 @@
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-reflector/issues",
- "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0"
+ "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1"
},
"funding": [
{
@@ -14202,32 +14458,32 @@
"type": "github"
}
],
- "time": "2023-02-03T07:06:18+00:00"
+ "time": "2024-07-03T05:01:32+00:00"
},
{
"name": "sebastian/recursion-context",
- "version": "5.0.0",
+ "version": "6.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
- "reference": "05909fb5bc7df4c52992396d0116aed689f93712"
+ "reference": "694d156164372abbd149a4b85ccda2e4670c0e16"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712",
- "reference": "05909fb5bc7df4c52992396d0116aed689f93712",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16",
+ "reference": "694d156164372abbd149a4b85ccda2e4670c0e16",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "5.0-dev"
+ "dev-main": "6.0-dev"
}
},
"autoload": {
@@ -14257,7 +14513,8 @@
"homepage": "https://github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
- "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0"
+ "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2"
},
"funding": [
{
@@ -14265,32 +14522,32 @@
"type": "github"
}
],
- "time": "2023-02-03T07:05:40+00:00"
+ "time": "2024-07-03T05:10:34+00:00"
},
{
"name": "sebastian/type",
- "version": "4.0.0",
+ "version": "5.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
- "reference": "462699a16464c3944eefc02ebdd77882bd3925bf"
+ "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf",
- "reference": "462699a16464c3944eefc02ebdd77882bd3925bf",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac",
+ "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^11.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "4.0-dev"
+ "dev-main": "5.1-dev"
}
},
"autoload": {
@@ -14313,7 +14570,8 @@
"homepage": "https://github.com/sebastianbergmann/type",
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
- "source": "https://github.com/sebastianbergmann/type/tree/4.0.0"
+ "security": "https://github.com/sebastianbergmann/type/security/policy",
+ "source": "https://github.com/sebastianbergmann/type/tree/5.1.0"
},
"funding": [
{
@@ -14321,29 +14579,29 @@
"type": "github"
}
],
- "time": "2023-02-03T07:10:45+00:00"
+ "time": "2024-09-17T13:12:04+00:00"
},
{
"name": "sebastian/version",
- "version": "4.0.1",
+ "version": "5.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/version.git",
- "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17"
+ "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17",
- "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874",
+ "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "4.0-dev"
+ "dev-main": "5.0-dev"
}
},
"autoload": {
@@ -14366,7 +14624,8 @@
"homepage": "https://github.com/sebastianbergmann/version",
"support": {
"issues": "https://github.com/sebastianbergmann/version/issues",
- "source": "https://github.com/sebastianbergmann/version/tree/4.0.1"
+ "security": "https://github.com/sebastianbergmann/version/security/policy",
+ "source": "https://github.com/sebastianbergmann/version/tree/5.0.2"
},
"funding": [
{
@@ -14374,20 +14633,20 @@
"type": "github"
}
],
- "time": "2023-02-07T11:34:05+00:00"
+ "time": "2024-10-09T05:16:32+00:00"
},
{
"name": "serversideup/spin",
- "version": "v1.1.0",
+ "version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/serversideup/spin.git",
- "reference": "03bb69dbdc6d6a68b82b4bb4cfeb7accc4f8758f"
+ "reference": "e7f742dfe54146196da26876670f368c11852df3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/serversideup/spin/zipball/03bb69dbdc6d6a68b82b4bb4cfeb7accc4f8758f",
- "reference": "03bb69dbdc6d6a68b82b4bb4cfeb7accc4f8758f",
+ "url": "https://api.github.com/repos/serversideup/spin/zipball/e7f742dfe54146196da26876670f368c11852df3",
+ "reference": "e7f742dfe54146196da26876670f368c11852df3",
"shasum": ""
},
"bin": [
@@ -14411,7 +14670,7 @@
"description": "Replicate your production environment locally using Docker. Just run \"spin up\". It's really that easy.",
"support": {
"issues": "https://github.com/serversideup/spin/issues",
- "source": "https://github.com/serversideup/spin/tree/v1.1.0"
+ "source": "https://github.com/serversideup/spin/tree/v2.3.0"
},
"funding": [
{
@@ -14419,7 +14678,7 @@
"type": "github"
}
],
- "time": "2022-05-20T15:13:10+00:00"
+ "time": "2024-10-15T15:12:28+00:00"
},
{
"name": "spatie/error-solutions",
@@ -14740,20 +14999,20 @@
},
{
"name": "symfony/http-client",
- "version": "v6.4.11",
+ "version": "v7.1.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
- "reference": "4c92046bb788648ff1098cc66da69aa7eac8cb65"
+ "reference": "c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/4c92046bb788648ff1098cc66da69aa7eac8cb65",
- "reference": "4c92046bb788648ff1098cc66da69aa7eac8cb65",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a",
+ "reference": "c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a",
"shasum": ""
},
"require": {
- "php": ">=8.1",
+ "php": ">=8.2",
"psr/log": "^1|^2|^3",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/http-client-contracts": "^3.4.1",
@@ -14761,7 +15020,7 @@
},
"conflict": {
"php-http/discovery": "<1.15",
- "symfony/http-foundation": "<6.3"
+ "symfony/http-foundation": "<6.4"
},
"provide": {
"php-http/async-client-implementation": "*",
@@ -14778,11 +15037,12 @@
"nyholm/psr7": "^1.0",
"php-http/httplug": "^1.0|^2.0",
"psr/http-client": "^1.0",
- "symfony/dependency-injection": "^5.4|^6.0|^7.0",
- "symfony/http-kernel": "^5.4|^6.0|^7.0",
- "symfony/messenger": "^5.4|^6.0|^7.0",
- "symfony/process": "^5.4|^6.0|^7.0",
- "symfony/stopwatch": "^5.4|^6.0|^7.0"
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/rate-limiter": "^6.4|^7.0",
+ "symfony/stopwatch": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -14813,7 +15073,7 @@
"http"
],
"support": {
- "source": "https://github.com/symfony/http-client/tree/v6.4.11"
+ "source": "https://github.com/symfony/http-client/tree/v7.1.8"
},
"funding": [
{
@@ -14829,7 +15089,7 @@
"type": "tidelift"
}
],
- "time": "2024-08-26T06:30:21+00:00"
+ "time": "2024-11-13T13:40:27+00:00"
},
{
"name": "symfony/http-client-contracts",
diff --git a/config/app.php b/config/app.php
index 34484fe41..371ac44ec 100644
--- a/config/app.php
+++ b/config/app.php
@@ -199,8 +199,6 @@ return [
App\Providers\EventServiceProvider::class,
App\Providers\HorizonServiceProvider::class,
App\Providers\RouteServiceProvider::class,
- App\Providers\TelescopeServiceProvider::class,
-
],
/*
diff --git a/config/clockwork.php b/config/clockwork.php
deleted file mode 100644
index ce880464a..000000000
--- a/config/clockwork.php
+++ /dev/null
@@ -1,424 +0,0 @@
- 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)
-
-];
diff --git a/config/constants.php b/config/constants.php
index 906ef3ba2..b29adfff3 100644
--- a/config/constants.php
+++ b/config/constants.php
@@ -1,32 +1,62 @@
[
- 'base_url' => 'https://coolify.io/docs',
+ 'coolify' => [
+ 'version' => '4.0.0-beta.372',
+ 'self_hosted' => env('SELF_HOSTED', true),
+ 'autoupdate' => env('AUTOUPDATE'),
+ 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
+ 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper'),
+ 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
+ ],
+
+ 'urls' => [
+ 'docs' => 'https://coolify.io/docs',
'contact' => 'https://coolify.io/docs/contact',
],
- 'ssh' => [
- // Using MUX
- 'mux_enabled' => env('MUX_ENABLED', env('SSH_MUX_ENABLED', true), true),
- 'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1h'),
- 'connection_timeout' => 10,
- 'server_interval' => 20,
- 'command_timeout' => 7200,
- ],
- 'waitlist' => [
- 'expiration' => 10,
- ],
- 'invitation' => [
- 'link' => [
- 'base_url' => '/invitations/',
- 'expiration' => 10,
- ],
- ],
+
'services' => [
// Temporary disabled until cache is implemented
// 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json',
],
+
+ 'terminal' => [
+ 'protocol' => env('TERMINAL_PROTOCOL'),
+ 'host' => env('TERMINAL_HOST'),
+ 'port' => env('TERMINAL_PORT'),
+ ],
+
+ 'pusher' => [
+ 'host' => env('PUSHER_HOST'),
+ 'port' => env('PUSHER_PORT'),
+ 'app_key' => env('PUSHER_APP_KEY'),
+ ],
+
+ 'horizon' => [
+ 'is_horizon_enabled' => env('HORIZON_ENABLED', true),
+ 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true),
+ ],
+
+ 'docker' => [
+ 'minimum_required_version' => '26.0',
+ ],
+
+ 'ssh' => [
+ 'mux_enabled' => env('MUX_ENABLED', env('SSH_MUX_ENABLED', true)),
+ 'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', 3600),
+ 'connection_timeout' => 10,
+ 'server_interval' => 20,
+ 'command_timeout' => 7200,
+ ],
+
+ 'invitation' => [
+ 'link' => [
+ 'base_url' => '/invitations/',
+ 'expiration_days' => 3,
+ ],
+ ],
+
'limits' => [
'trial_period' => 0,
'server' => [
@@ -46,4 +76,23 @@ return [
'dynamic' => true,
],
],
+
+ 'waitlist' => [
+ 'enabled' => env('WAITLIST', false),
+ 'expiration' => 10,
+ ],
+
+ 'sentry' => [
+ 'sentry_dsn' => env('SENTRY_DSN'),
+ ],
+
+ 'webhooks' => [
+ 'feedback_discord_webhook' => env('FEEDBACK_DISCORD_WEBHOOK'),
+ 'dev_webhook' => env('SERVEO_URL'),
+ ],
+
+ 'bunny' => [
+ 'storage_api_key' => env('BUNNY_STORAGE_API_KEY'),
+ 'api_key' => env('BUNNY_API_KEY'),
+ ],
];
diff --git a/config/coolify.php b/config/coolify.php
deleted file mode 100644
index f9878fff7..000000000
--- a/config/coolify.php
+++ /dev/null
@@ -1,16 +0,0 @@
- 'https://coolify.io/docs/',
- 'contact' => 'https://coolify.io/docs/contact',
- 'feedback_discord_webhook' => env('FEEDBACK_DISCORD_WEBHOOK'),
- 'self_hosted' => env('SELF_HOSTED', true),
- 'waitlist' => env('WAITLIST', false),
- 'license_url' => 'https://licenses.coollabs.io',
- 'dev_webhook' => env('SERVEO_URL'),
- 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
- 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
- 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper'),
- 'is_horizon_enabled' => env('HORIZON_ENABLED', true),
- 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true),
-];
diff --git a/config/debugbar.php b/config/debugbar.php
new file mode 100644
index 000000000..daeea96b6
--- /dev/null
+++ b/config/debugbar.php
@@ -0,0 +1,326 @@
+ env('DEBUGBAR_ENABLED', null),
+ 'except' => [
+ 'telescope*',
+ 'horizon*',
+ 'api*',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Storage settings
+ |--------------------------------------------------------------------------
+ |
+ | DebugBar stores data for session/ajax requests.
+ | You can disable this, so the debugbar stores data in headers/session,
+ | but this can cause problems with large data collectors.
+ | By default, file storage (in the storage folder) is used. Redis and PDO
+ | can also be used. For PDO, run the package migrations first.
+ |
+ | Warning: Enabling storage.open will allow everyone to access previous
+ | request, do not enable open storage in publicly available environments!
+ | Specify a callback if you want to limit based on IP or authentication.
+ | Leaving it to null will allow localhost only.
+ */
+ 'storage' => [
+ 'enabled' => true,
+ 'open' => env('DEBUGBAR_OPEN_STORAGE'), // bool/callback.
+ 'driver' => 'file', // redis, file, pdo, socket, custom
+ 'path' => storage_path('debugbar'), // For file driver
+ 'connection' => null, // Leave null for default connection (Redis/PDO)
+ 'provider' => '', // Instance of StorageInterface for custom driver
+ 'hostname' => '127.0.0.1', // Hostname to use with the "socket" driver
+ 'port' => 2304, // Port to use with the "socket" driver
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Editor
+ |--------------------------------------------------------------------------
+ |
+ | Choose your preferred editor to use when clicking file name.
+ |
+ | Supported: "phpstorm", "vscode", "vscode-insiders", "vscode-remote",
+ | "vscode-insiders-remote", "vscodium", "textmate", "emacs",
+ | "sublime", "atom", "nova", "macvim", "idea", "netbeans",
+ | "xdebug", "espresso"
+ |
+ */
+
+ 'editor' => env('DEBUGBAR_EDITOR') ?: env('IGNITION_EDITOR', 'phpstorm'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Remote Path Mapping
+ |--------------------------------------------------------------------------
+ |
+ | If you are using a remote dev server, like Laravel Homestead, Docker, or
+ | even a remote VPS, it will be necessary to specify your path mapping.
+ |
+ | Leaving one, or both of these, empty or null will not trigger the remote
+ | URL changes and Debugbar will treat your editor links as local files.
+ |
+ | "remote_sites_path" is an absolute base path for your sites or projects
+ | in Homestead, Vagrant, Docker, or another remote development server.
+ |
+ | Example value: "/home/vagrant/Code"
+ |
+ | "local_sites_path" is an absolute base path for your sites or projects
+ | on your local computer where your IDE or code editor is running on.
+ |
+ | Example values: "/Users/