Merge branch 'next' into docker-network-aliases
This commit is contained in:
@@ -1,16 +1,16 @@
|
|||||||
# Coolify Configuration
|
|
||||||
APP_ID=
|
APP_ID=
|
||||||
APP_NAME=Coolify
|
APP_NAME=Coolify
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
|
|
||||||
# PostgreSQL Database Configuration
|
|
||||||
DB_USERNAME=coolify
|
DB_USERNAME=coolify
|
||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
|
|
||||||
# Redis Configuration
|
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
# Pusher Configuration
|
|
||||||
PUSHER_APP_ID=
|
PUSHER_APP_ID=
|
||||||
PUSHER_APP_KEY=
|
PUSHER_APP_KEY=
|
||||||
PUSHER_APP_SECRET=
|
PUSHER_APP_SECRET=
|
||||||
|
|
||||||
|
ROOT_USERNAME=
|
||||||
|
ROOT_USER_EMAIL=
|
||||||
|
ROOT_USER_PASSWORD=
|
||||||
|
@@ -13,16 +13,16 @@ jobs:
|
|||||||
id: stale
|
id: stale
|
||||||
with:
|
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-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.'
|
stale-pr-message: 'This pull request requires attention. If no changes or response is received within the next few days, it will be automatically closed. Please update your PR or leave a comment with the requested information.'
|
||||||
close-issue-message: 'This issue has been automatically closed due to inactivity.'
|
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.'
|
close-pr-message: 'Thank you for your contribution. Due to inactivity, this PR was automatically closed. If you would like to continue working on this change in the future, feel free to reopen this PR or submit a new one.'
|
||||||
days-before-stale: 14
|
days-before-stale: 14
|
||||||
days-before-close: 7
|
days-before-close: 7
|
||||||
stale-issue-label: '⏱︎ Stale'
|
stale-issue-label: '⏱︎ Stale'
|
||||||
stale-pr-label: '⏱︎ Stale'
|
stale-pr-label: '⏱︎ Stale'
|
||||||
only-labels: '💤 Waiting for feedback'
|
only-labels: '💤 Waiting for feedback, 💤 Waiting for changes'
|
||||||
remove-stale-when-updated: true
|
remove-stale-when-updated: true
|
||||||
operations-per-run: 100
|
operations-per-run: 100
|
||||||
labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback'
|
labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback, 💤 Waiting for changes'
|
||||||
close-issue-reason: 'not_planned'
|
close-issue-reason: 'not_planned'
|
||||||
exempt-all-milestones: false
|
exempt-all-milestones: false
|
||||||
|
@@ -19,8 +19,12 @@ jobs:
|
|||||||
script: |
|
script: |
|
||||||
const { owner, repo } = context.repo;
|
const { owner, repo } = context.repo;
|
||||||
|
|
||||||
async function processIssue(issueNumber) {
|
async function processIssue(issueNumber, isFromPR = false, prBaseBranch = null) {
|
||||||
try {
|
try {
|
||||||
|
if (isFromPR && prBaseBranch !== 'main') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
|
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
@@ -59,19 +63,19 @@ jobs:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.eventName === 'issues' || context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
|
if (context.eventName === 'issues') {
|
||||||
const issue = context.payload.issue || context.payload.pull_request;
|
await processIssue(context.payload.issue.number);
|
||||||
await processIssue(issue.number);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
|
if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
|
||||||
const pr = context.payload.pull_request;
|
const pr = context.payload.pull_request;
|
||||||
if (pr.body) {
|
await processIssue(pr.number);
|
||||||
|
if (pr.merged && pr.base.ref === 'main' && pr.body) {
|
||||||
const issueReferences = pr.body.match(/#(\d+)/g);
|
const issueReferences = pr.body.match(/#(\d+)/g);
|
||||||
if (issueReferences) {
|
if (issueReferences) {
|
||||||
for (const reference of issueReferences) {
|
for (const reference of issueReferences) {
|
||||||
const issueNumber = parseInt(reference.substring(1));
|
const issueNumber = parseInt(reference.substring(1));
|
||||||
await processIssue(issueNumber);
|
await processIssue(issueNumber, true, pr.base.ref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
.github/workflows/coolify-helper-next.yml
vendored
6
.github/workflows/coolify-helper-next.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getHelperVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and Push Image
|
- name: Build and Push Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getHelperVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and Push Image
|
- name: Build and Push Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -119,7 +119,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getHelperVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
||||||
run: |
|
run: |
|
||||||
|
6
.github/workflows/coolify-helper.yml
vendored
6
.github/workflows/coolify-helper.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getHelperVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and Push Image
|
- name: Build and Push Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getHelperVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and Push Image
|
- name: Build and Push Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -119,7 +119,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getHelperVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
||||||
run: |
|
run: |
|
||||||
|
@@ -12,6 +12,7 @@ on:
|
|||||||
- docker/coolify-realtime/Dockerfile
|
- docker/coolify-realtime/Dockerfile
|
||||||
- docker/testing-host/Dockerfile
|
- docker/testing-host/Dockerfile
|
||||||
- templates/**
|
- templates/**
|
||||||
|
- CHANGELOG.md
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_REGISTRY: ghcr.io
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
6
.github/workflows/coolify-realtime-next.yml
vendored
6
.github/workflows/coolify-realtime-next.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getRealtimeVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and Push Image
|
- name: Build and Push Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getRealtimeVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and Push Image
|
- name: Build and Push Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -125,7 +125,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getRealtimeVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
||||||
run: |
|
run: |
|
||||||
|
6
.github/workflows/coolify-realtime.yml
vendored
6
.github/workflows/coolify-realtime.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getRealtimeVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and Push Image
|
- name: Build and Push Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getRealtimeVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and Push Image
|
- name: Build and Push Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -125,7 +125,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getRealtimeVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
|
||||||
run: |
|
run: |
|
||||||
|
1
.github/workflows/coolify-staging-build.yml
vendored
1
.github/workflows/coolify-staging-build.yml
vendored
@@ -12,6 +12,7 @@ on:
|
|||||||
- docker/coolify-realtime/Dockerfile
|
- docker/coolify-realtime/Dockerfile
|
||||||
- docker/testing-host/Dockerfile
|
- docker/testing-host/Dockerfile
|
||||||
- templates/**
|
- templates/**
|
||||||
|
- CHANGELOG.md
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_REGISTRY: ghcr.io
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
36
.github/workflows/generate-changelog.yml
vendored
Normal file
36
.github/workflows/generate-changelog.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Generate Changelog
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
changelog:
|
||||||
|
name: Generate changelog
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Generate changelog
|
||||||
|
uses: orhun/git-cliff-action@v4
|
||||||
|
with:
|
||||||
|
config: cliff.toml
|
||||||
|
args: --verbose
|
||||||
|
env:
|
||||||
|
OUTPUT: CHANGELOG.md
|
||||||
|
GITHUB_REPO: ${{ github.repository }}
|
||||||
|
|
||||||
|
- name: Commit
|
||||||
|
run: |
|
||||||
|
git config user.name 'github-actions[bot]'
|
||||||
|
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||||
|
git add CHANGELOG.md
|
||||||
|
git commit -m "docs: update changelog"
|
||||||
|
git push https://${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git main
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -36,3 +36,4 @@ scripts/load-test/*
|
|||||||
.env.dusk.local
|
.env.dusk.local
|
||||||
docker/coolify-realtime/node_modules
|
docker/coolify-realtime/node_modules
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
Changelog.md
|
||||||
|
6844
CHANGELOG.md
Normal file
6844
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
You can ask for guidance anytime on our [Discord server](https://coollabs.io/discord) in the `#contribute` channel.
|
You can ask for guidance anytime on our [Discord server](https://coollabs.io/discord) in the `#contribute` channel.
|
||||||
|
|
||||||
|
To understand the tech stack, please refer to the [Tech Stack](TECH_STACK.md) document.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
1. [Setup Development Environment](#1-setup-development-environment)
|
1. [Setup Development Environment](#1-setup-development-environment)
|
||||||
|
@@ -53,12 +53,13 @@ Special thanks to our biggest sponsors!
|
|||||||
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
|
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
|
||||||
* [GoldenVM](https://billing.goldenvm.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
* [GoldenVM](https://billing.goldenvm.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
||||||
* [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.
|
* [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.
|
||||||
|
* [Convex](https://convex.link/coolify.io) - Convex is the open-source reactive database for web app developers.
|
||||||
* [Cloudify.ro](https://cloudify.ro/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
* [Cloudify.ro](https://cloudify.ro/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
||||||
* [Syntaxfm](https://syntax.fm/?ref=coolify.io) - Podcast for web developers.
|
* [Syntaxfm](https://syntax.fm/?ref=coolify.io) - Podcast for web developers.
|
||||||
* [PFGlabs](https://pfglabs.com/?ref=coolify.io) - Build real project with Golang.
|
* [PFGlabs](https://pfglabs.com/?ref=coolify.io) - Build real project with Golang.
|
||||||
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets.
|
* [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.
|
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers.
|
||||||
* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities.
|
* [Brand Dev](https://brand.dev/?ref=coolify.io) - The #1 Brand API for B2B software startups - instantly pull logos, fonts, descriptions, social links, slogans, and so much more from any domain via a single api call.
|
||||||
* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries.
|
* [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.
|
* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools.
|
||||||
* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services.
|
* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services.
|
||||||
@@ -66,7 +67,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.
|
* [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.
|
* [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.
|
* [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.
|
* [LiquidWeb](https://liquidweb.com/?utm_source=coolify.io) - A Fast web hosting provider.
|
||||||
|
|
||||||
|
|
||||||
## Github Sponsors ($40+)
|
## Github Sponsors ($40+)
|
||||||
@@ -75,6 +76,7 @@ Special thanks to our biggest sponsors!
|
|||||||
<a href="https://www.runpod.io/?ref=coolify.io">
|
<a href="https://www.runpod.io/?ref=coolify.io">
|
||||||
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
|
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
|
||||||
<a href="https://lightspeed.run/?ref=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
<a href="https://lightspeed.run/?ref=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
||||||
|
<a href="https://dartnode.com/?ref=coolify.io"><img src="https://github.com/DartNode-com.png" width="60px" alt="DartNode"/></a>
|
||||||
<a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
|
<a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
|
||||||
<a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
<a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
||||||
<a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
<a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
||||||
|
29
TECH_STACK.md
Normal file
29
TECH_STACK.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Coolify Technology Stack
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
- Livewire and Alpine.js
|
||||||
|
- Blade (PHP templating engine)
|
||||||
|
- Tailwind CSS
|
||||||
|
- Monaco Editor (Code editor component)
|
||||||
|
- XTerm.js (Terminal component)
|
||||||
|
|
||||||
|
## Backend
|
||||||
|
|
||||||
|
- Laravel 11 (PHP Framework)
|
||||||
|
- PostgreSQL 15 (Database)
|
||||||
|
- Redis 7 (Caching & Real-time features)
|
||||||
|
- Soketi (WebSocket Server)
|
||||||
|
|
||||||
|
## DevOps & Infrastructure
|
||||||
|
|
||||||
|
- Docker & Docker Compose
|
||||||
|
- Nginx (Web Server)
|
||||||
|
- S6 Overlay (Process Supervisor)
|
||||||
|
- GitHub Actions (CI/CD)
|
||||||
|
|
||||||
|
## Languages
|
||||||
|
|
||||||
|
- PHP 8.4
|
||||||
|
- JavaScript
|
||||||
|
- Shell/Bash scripts
|
@@ -91,16 +91,9 @@ class RunRemoteProcess
|
|||||||
} else {
|
} else {
|
||||||
if ($processResult->exitCode() == 0) {
|
if ($processResult->exitCode() == 0) {
|
||||||
$status = ProcessStatus::FINISHED;
|
$status = ProcessStatus::FINISHED;
|
||||||
}
|
} else {
|
||||||
if ($processResult->exitCode() != 0 && ! $this->ignore_errors) {
|
|
||||||
$status = ProcessStatus::ERROR;
|
$status = ProcessStatus::ERROR;
|
||||||
}
|
}
|
||||||
// if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
|
|
||||||
// $status = ProcessStatus::FINISHED;
|
|
||||||
// }
|
|
||||||
// if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
|
|
||||||
// $status = ProcessStatus::ERROR;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->activity->properties = $this->activity->properties->merge([
|
$this->activity->properties = $this->activity->properties->merge([
|
||||||
@@ -110,9 +103,6 @@ class RunRemoteProcess
|
|||||||
'status' => $status->value,
|
'status' => $status->value,
|
||||||
]);
|
]);
|
||||||
$this->activity->save();
|
$this->activity->save();
|
||||||
if ($processResult->exitCode() != 0 && ! $this->ignore_errors) {
|
|
||||||
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
|
|
||||||
}
|
|
||||||
if ($this->call_event_on_finish) {
|
if ($this->call_event_on_finish) {
|
||||||
try {
|
try {
|
||||||
if ($this->call_event_data) {
|
if ($this->call_event_data) {
|
||||||
@@ -128,6 +118,9 @@ class RunRemoteProcess
|
|||||||
Log::error('Error calling event: '.$e->getMessage());
|
Log::error('Error calling event: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($processResult->exitCode() != 0 && ! $this->ignore_errors) {
|
||||||
|
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
|
||||||
|
}
|
||||||
|
|
||||||
return $processResult;
|
return $processResult;
|
||||||
}
|
}
|
||||||
|
@@ -49,11 +49,7 @@ class StartClickhouse
|
|||||||
'hard' => 262144,
|
'hard' => 262144,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => defaultDatabaseLabels($this->database)->toArray(),
|
||||||
'coolify.managed' => 'true',
|
|
||||||
'coolify.type' => 'database',
|
|
||||||
'coolify.databaseId' => $this->database->id,
|
|
||||||
],
|
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => "clickhouse-client --password {$this->database->clickhouse_admin_password} --query 'SELECT 1'",
|
'test' => "clickhouse-client --password {$this->database->clickhouse_admin_password} --query 'SELECT 1'",
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
|
@@ -22,70 +22,27 @@ class StartDatabaseProxy
|
|||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase $database)
|
||||||
{
|
{
|
||||||
$internalPort = null;
|
$databaseType = $database->database_type;
|
||||||
$type = $database->getMorphClass();
|
|
||||||
$network = data_get($database, 'destination.network');
|
$network = data_get($database, 'destination.network');
|
||||||
$server = data_get($database, 'destination.server');
|
$server = data_get($database, 'destination.server');
|
||||||
$containerName = data_get($database, 'uuid');
|
$containerName = data_get($database, 'uuid');
|
||||||
$proxyContainerName = "{$database->uuid}-proxy";
|
$proxyContainerName = "{$database->uuid}-proxy";
|
||||||
if ($database->getMorphClass() === \App\Models\ServiceDatabase::class) {
|
if ($database->getMorphClass() === \App\Models\ServiceDatabase::class) {
|
||||||
$databaseType = $database->databaseType();
|
$databaseType = $database->databaseType();
|
||||||
// $connectPredefined = data_get($database, 'service.connect_to_docker_network');
|
|
||||||
$network = $database->service->uuid;
|
$network = $database->service->uuid;
|
||||||
$server = data_get($database, 'service.destination.server');
|
$server = data_get($database, 'service.destination.server');
|
||||||
$proxyContainerName = "{$database->service->uuid}-proxy";
|
$proxyContainerName = "{$database->service->uuid}-proxy";
|
||||||
switch ($databaseType) {
|
$containerName = "{$database->name}-{$database->service->uuid}";
|
||||||
case 'standalone-mariadb':
|
|
||||||
$type = \App\Models\StandaloneMariadb::class;
|
|
||||||
$containerName = "mariadb-{$database->service->uuid}";
|
|
||||||
break;
|
|
||||||
case 'standalone-mongodb':
|
|
||||||
$type = \App\Models\StandaloneMongodb::class;
|
|
||||||
$containerName = "mongodb-{$database->service->uuid}";
|
|
||||||
break;
|
|
||||||
case 'standalone-mysql':
|
|
||||||
$type = \App\Models\StandaloneMysql::class;
|
|
||||||
$containerName = "mysql-{$database->service->uuid}";
|
|
||||||
break;
|
|
||||||
case 'standalone-postgresql':
|
|
||||||
$type = \App\Models\StandalonePostgresql::class;
|
|
||||||
$containerName = "postgresql-{$database->service->uuid}";
|
|
||||||
break;
|
|
||||||
case 'standalone-redis':
|
|
||||||
$type = \App\Models\StandaloneRedis::class;
|
|
||||||
$containerName = "redis-{$database->service->uuid}";
|
|
||||||
break;
|
|
||||||
case 'standalone-keydb':
|
|
||||||
$type = \App\Models\StandaloneKeydb::class;
|
|
||||||
$containerName = "keydb-{$database->service->uuid}";
|
|
||||||
break;
|
|
||||||
case 'standalone-dragonfly':
|
|
||||||
$type = \App\Models\StandaloneDragonfly::class;
|
|
||||||
$containerName = "dragonfly-{$database->service->uuid}";
|
|
||||||
break;
|
|
||||||
case 'standalone-clickhouse':
|
|
||||||
$type = \App\Models\StandaloneClickhouse::class;
|
|
||||||
$containerName = "clickhouse-{$database->service->uuid}";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($type === \App\Models\StandaloneRedis::class) {
|
|
||||||
$internalPort = 6379;
|
|
||||||
} elseif ($type === \App\Models\StandalonePostgresql::class) {
|
|
||||||
$internalPort = 5432;
|
|
||||||
} elseif ($type === \App\Models\StandaloneMongodb::class) {
|
|
||||||
$internalPort = 27017;
|
|
||||||
} elseif ($type === \App\Models\StandaloneMysql::class) {
|
|
||||||
$internalPort = 3306;
|
|
||||||
} elseif ($type === \App\Models\StandaloneMariadb::class) {
|
|
||||||
$internalPort = 3306;
|
|
||||||
} elseif ($type === \App\Models\StandaloneKeydb::class) {
|
|
||||||
$internalPort = 6379;
|
|
||||||
} elseif ($type === \App\Models\StandaloneDragonfly::class) {
|
|
||||||
$internalPort = 6379;
|
|
||||||
} elseif ($type === \App\Models\StandaloneClickhouse::class) {
|
|
||||||
$internalPort = 9000;
|
|
||||||
}
|
}
|
||||||
|
$internalPort = match ($databaseType) {
|
||||||
|
'standalone-mariadb', 'standalone-mysql' => 3306,
|
||||||
|
'standalone-postgresql', 'standalone-supabase/postgres' => 5432,
|
||||||
|
'standalone-redis', 'standalone-keydb', 'standalone-dragonfly' => 6379,
|
||||||
|
'standalone-clickhouse' => 9000,
|
||||||
|
'standalone-mongodb' => 27017,
|
||||||
|
default => throw new \Exception("Unsupported database type: $databaseType"),
|
||||||
|
};
|
||||||
|
|
||||||
$configuration_dir = database_proxy_dir($database->uuid);
|
$configuration_dir = database_proxy_dir($database->uuid);
|
||||||
$nginxconf = <<<EOF
|
$nginxconf = <<<EOF
|
||||||
user nginx;
|
user nginx;
|
||||||
|
@@ -46,11 +46,7 @@ class StartDragonfly
|
|||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network,
|
$this->database->destination->network,
|
||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => defaultDatabaseLabels($this->database)->toArray(),
|
||||||
'coolify.managed' => 'true',
|
|
||||||
'coolify.type' => 'database',
|
|
||||||
'coolify.databaseId' => $this->database->id,
|
|
||||||
],
|
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => "redis-cli -a {$this->database->dragonfly_password} ping",
|
'test' => "redis-cli -a {$this->database->dragonfly_password} ping",
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
|
@@ -48,11 +48,7 @@ class StartKeydb
|
|||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network,
|
$this->database->destination->network,
|
||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => defaultDatabaseLabels($this->database)->toArray(),
|
||||||
'coolify.managed' => 'true',
|
|
||||||
'coolify.type' => 'database',
|
|
||||||
'coolify.databaseId' => $this->database->id,
|
|
||||||
],
|
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => "keydb-cli --pass {$this->database->keydb_password} ping",
|
'test' => "keydb-cli --pass {$this->database->keydb_password} ping",
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
|
@@ -43,11 +43,7 @@ class StartMariadb
|
|||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network,
|
$this->database->destination->network,
|
||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => defaultDatabaseLabels($this->database)->toArray(),
|
||||||
'coolify.managed' => 'true',
|
|
||||||
'coolify.type' => 'database',
|
|
||||||
'coolify.databaseId' => $this->database->id,
|
|
||||||
],
|
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'],
|
'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'],
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
|
@@ -51,11 +51,7 @@ class StartMongodb
|
|||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network,
|
$this->database->destination->network,
|
||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => defaultDatabaseLabels($this->database)->toArray(),
|
||||||
'coolify.managed' => 'true',
|
|
||||||
'coolify.type' => 'database',
|
|
||||||
'coolify.databaseId' => $this->database->id,
|
|
||||||
],
|
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
'CMD',
|
'CMD',
|
||||||
|
@@ -43,11 +43,7 @@ class StartMysql
|
|||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network,
|
$this->database->destination->network,
|
||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => defaultDatabaseLabels($this->database)->toArray(),
|
||||||
'coolify.managed' => 'true',
|
|
||||||
'coolify.type' => 'database',
|
|
||||||
'coolify.databaseId' => $this->database->id,
|
|
||||||
],
|
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"],
|
'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"],
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
|
@@ -23,6 +23,9 @@ class StartPostgresql
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
if (isDev()) {
|
||||||
|
$this->configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name;
|
||||||
|
}
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting database.'",
|
"echo 'Starting database.'",
|
||||||
@@ -47,11 +50,7 @@ class StartPostgresql
|
|||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network,
|
$this->database->destination->network,
|
||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => defaultDatabaseLabels($this->database)->toArray(),
|
||||||
'coolify.managed' => 'true',
|
|
||||||
'coolify.type' => 'database',
|
|
||||||
'coolify.databaseId' => $this->database->id,
|
|
||||||
],
|
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
'CMD-SHELL',
|
'CMD-SHELL',
|
||||||
@@ -78,7 +77,7 @@ class StartPostgresql
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (filled($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -108,7 +107,7 @@ class StartPostgresql
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (! is_null($this->database->postgres_conf) && ! empty($this->database->postgres_conf)) {
|
if (filled($this->database->postgres_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/custom-postgres.conf',
|
'source' => $this->configuration_dir.'/custom-postgres.conf',
|
||||||
@@ -199,9 +198,12 @@ class StartPostgresql
|
|||||||
|
|
||||||
private function generate_init_scripts()
|
private function generate_init_scripts()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->init_scripts) || count($this->database->init_scripts) === 0) {
|
$this->commands[] = "rm -rf $this->configuration_dir/docker-entrypoint-initdb.d/*";
|
||||||
|
|
||||||
|
if (blank($this->database->init_scripts) || count($this->database->init_scripts) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->database->init_scripts as $init_script) {
|
foreach ($this->database->init_scripts as $init_script) {
|
||||||
$filename = data_get($init_script, 'filename');
|
$filename = data_get($init_script, 'filename');
|
||||||
$content = data_get($init_script, 'content');
|
$content = data_get($init_script, 'content');
|
||||||
@@ -213,10 +215,15 @@ class StartPostgresql
|
|||||||
|
|
||||||
private function add_custom_conf()
|
private function add_custom_conf()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) {
|
$filename = 'custom-postgres.conf';
|
||||||
|
$config_file_path = "$this->configuration_dir/$filename";
|
||||||
|
|
||||||
|
if (blank($this->database->postgres_conf)) {
|
||||||
|
$this->commands[] = "rm -f $config_file_path";
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'custom-postgres.conf';
|
|
||||||
$content = $this->database->postgres_conf;
|
$content = $this->database->postgres_conf;
|
||||||
if (! str($content)->contains('listen_addresses')) {
|
if (! str($content)->contains('listen_addresses')) {
|
||||||
$content .= "\nlisten_addresses = '*'";
|
$content .= "\nlisten_addresses = '*'";
|
||||||
@@ -224,6 +231,6 @@ class StartPostgresql
|
|||||||
$this->database->save();
|
$this->database->save();
|
||||||
}
|
}
|
||||||
$content_base64 = base64_encode($content);
|
$content_base64 = base64_encode($content);
|
||||||
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
|
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $config_file_path > /dev/null";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -48,11 +48,7 @@ class StartRedis
|
|||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network,
|
$this->database->destination->network,
|
||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => defaultDatabaseLabels($this->database)->toArray(),
|
||||||
'coolify.managed' => 'true',
|
|
||||||
'coolify.type' => 'database',
|
|
||||||
'coolify.databaseId' => $this->database->id,
|
|
||||||
],
|
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
'CMD-SHELL',
|
'CMD-SHELL',
|
||||||
|
@@ -30,7 +30,6 @@ class StopDatabaseProxy
|
|||||||
}
|
}
|
||||||
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
||||||
|
|
||||||
$database->is_public = false;
|
|
||||||
$database->save();
|
$database->save();
|
||||||
|
|
||||||
DatabaseProxyStopped::dispatch();
|
DatabaseProxyStopped::dispatch();
|
||||||
|
@@ -208,7 +208,6 @@ class GetContainersStatus
|
|||||||
$foundServices[] = "$service->id-$service->name";
|
$foundServices[] = "$service->id-$service->name";
|
||||||
$statusFromDb = $service->status;
|
$statusFromDb = $service->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
// ray('Updating status: ' . $containerStatus);
|
|
||||||
$service->update(['status' => $containerStatus]);
|
$service->update(['status' => $containerStatus]);
|
||||||
} else {
|
} else {
|
||||||
$service->update(['last_online_at' => now()]);
|
$service->update(['last_online_at' => now()]);
|
||||||
|
@@ -28,13 +28,13 @@ class StartProxy
|
|||||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||||
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
|
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
|
||||||
$server->save();
|
$server->save();
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarmManager()) {
|
||||||
$commands = $commands->merge([
|
$commands = $commands->merge([
|
||||||
"mkdir -p $proxy_path/dynamic",
|
"mkdir -p $proxy_path/dynamic",
|
||||||
"cd $proxy_path",
|
"cd $proxy_path",
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
'docker stack deploy -c docker-compose.yml coolify-proxy',
|
'docker stack deploy --detach=true -c docker-compose.yml coolify-proxy',
|
||||||
"echo 'Successfully started coolify-proxy.'",
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
@@ -57,7 +57,7 @@ class StartProxy
|
|||||||
" echo 'Successfully stopped and removed existing coolify-proxy.'",
|
" echo 'Successfully stopped and removed existing coolify-proxy.'",
|
||||||
'fi',
|
'fi',
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
'docker compose up -d --remove-orphans',
|
'docker compose up -d',
|
||||||
"echo 'Successfully started coolify-proxy.'",
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
]);
|
]);
|
||||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||||
|
@@ -25,17 +25,25 @@ class CleanupDocker
|
|||||||
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
|
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
|
||||||
];
|
];
|
||||||
|
|
||||||
$serverSettings = $server->settings;
|
if ($server->settings->delete_unused_volumes) {
|
||||||
if ($serverSettings->delete_unused_volumes) {
|
|
||||||
$commands[] = 'docker volume prune -af';
|
$commands[] = 'docker volume prune -af';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($serverSettings->delete_unused_networks) {
|
if ($server->settings->delete_unused_networks) {
|
||||||
$commands[] = 'docker network prune -f';
|
$commands[] = 'docker network prune -f';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$cleanupLog = [];
|
||||||
foreach ($commands as $command) {
|
foreach ($commands as $command) {
|
||||||
instant_remote_process([$command], $server, false);
|
$commandOutput = instant_remote_process([$command], $server, false);
|
||||||
|
if ($commandOutput !== null) {
|
||||||
|
$cleanupLog[] = [
|
||||||
|
'command' => $command,
|
||||||
|
'output' => $commandOutput,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $cleanupLog;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,19 +12,24 @@ class StartService
|
|||||||
|
|
||||||
public string $jobQueue = 'high';
|
public string $jobQueue = 'high';
|
||||||
|
|
||||||
public function handle(Service $service)
|
public function handle(Service $service, bool $pullLatestImages = false, bool $stopBeforeStart = false)
|
||||||
{
|
{
|
||||||
|
$service->parse();
|
||||||
|
if ($stopBeforeStart) {
|
||||||
|
StopService::run(service: $service, dockerCleanup: false);
|
||||||
|
}
|
||||||
$service->saveComposeConfigs();
|
$service->saveComposeConfigs();
|
||||||
$commands[] = 'cd '.$service->workdir();
|
$commands[] = 'cd '.$service->workdir();
|
||||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||||
|
if ($pullLatestImages) {
|
||||||
|
$commands[] = "echo 'Pulling images.'";
|
||||||
|
$commands[] = 'docker compose pull';
|
||||||
|
}
|
||||||
if ($service->networks()->count() > 0) {
|
if ($service->networks()->count() > 0) {
|
||||||
$commands[] = "echo 'Creating Docker network.'";
|
$commands[] = "echo 'Creating Docker network.'";
|
||||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||||
}
|
}
|
||||||
$commands[] = 'echo Starting service.';
|
$commands[] = 'echo Starting service.';
|
||||||
$commands[] = "echo 'Pulling images.'";
|
|
||||||
$commands[] = 'docker compose pull';
|
|
||||||
$commands[] = "echo 'Starting containers.'";
|
|
||||||
$commands[] = 'docker compose up -d --remove-orphans --force-recreate --build';
|
$commands[] = 'docker compose up -d --remove-orphans --force-recreate --build';
|
||||||
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
||||||
if (data_get($service, 'connect_to_docker_network')) {
|
if (data_get($service, 'connect_to_docker_network')) {
|
||||||
|
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Actions\Shared;
|
|
||||||
|
|
||||||
use App\Models\Service;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
|
|
||||||
class PullImage
|
|
||||||
{
|
|
||||||
use AsAction;
|
|
||||||
|
|
||||||
public function handle(Service $resource)
|
|
||||||
{
|
|
||||||
$resource->saveComposeConfigs();
|
|
||||||
|
|
||||||
$commands[] = 'cd '.$resource->workdir();
|
|
||||||
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
|
|
||||||
$commands[] = 'docker compose pull';
|
|
||||||
|
|
||||||
$server = data_get($resource, 'server');
|
|
||||||
|
|
||||||
if (! $server) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
instant_remote_process($commands, $resource->server);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -57,6 +57,14 @@ class CleanupDatabase extends Command
|
|||||||
$application_deployment_queues->delete();
|
$application_deployment_queues->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup scheduled_task_executions table
|
||||||
|
$scheduled_task_executions = DB::table('scheduled_task_executions')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc');
|
||||||
|
$count = $scheduled_task_executions->count();
|
||||||
|
echo "Delete $count entries from scheduled_task_executions.\n";
|
||||||
|
if ($this->option('yes')) {
|
||||||
|
$scheduled_task_executions->delete();
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup webhooks table
|
// Cleanup webhooks table
|
||||||
$webhooks = DB::table('webhooks')->where('created_at', '<', now()->subDays($keep_days));
|
$webhooks = DB::table('webhooks')->where('created_at', '<', now()->subDays($keep_days));
|
||||||
$count = $webhooks->count();
|
$count = $webhooks->count();
|
||||||
|
@@ -39,6 +39,11 @@ class CleanupStuckedResources extends Command
|
|||||||
$servers = Server::all()->filter(function ($server) {
|
$servers = Server::all()->filter(function ($server) {
|
||||||
return $server->isFunctional();
|
return $server->isFunctional();
|
||||||
});
|
});
|
||||||
|
if (isCloud()) {
|
||||||
|
$servers = $servers->filter(function ($server) {
|
||||||
|
return data_get($server->team->subscription, 'stripe_invoice_paid', false) === true;
|
||||||
|
});
|
||||||
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
CleanupHelperContainersJob::dispatch($server);
|
CleanupHelperContainersJob::dispatch($server);
|
||||||
}
|
}
|
||||||
|
@@ -50,7 +50,7 @@ class CloudCleanupSubscriptions extends Command
|
|||||||
} else {
|
} else {
|
||||||
$subscription = $stripe->subscriptions->retrieve(data_get($team, 'subscription.stripe_subscription_id'), []);
|
$subscription = $stripe->subscriptions->retrieve(data_get($team, 'subscription.stripe_subscription_id'), []);
|
||||||
$status = data_get($subscription, 'status');
|
$status = data_get($subscription, 'status');
|
||||||
if ($status === 'active' || $status === 'past_due') {
|
if ($status === 'active') {
|
||||||
$team->subscription->update([
|
$team->subscription->update([
|
||||||
'stripe_invoice_paid' => true,
|
'stripe_invoice_paid' => true,
|
||||||
'stripe_trial_already_ended' => false,
|
'stripe_trial_already_ended' => false,
|
||||||
|
178
app/Console/Commands/HorizonManage.php
Normal file
178
app/Console/Commands/HorizonManage.php
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Repositories\CustomJobRepository;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Laravel\Horizon\Contracts\JobRepository;
|
||||||
|
use Laravel\Horizon\Contracts\MetricsRepository;
|
||||||
|
use Laravel\Horizon\Repositories\RedisJobRepository;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\multiselect;
|
||||||
|
use function Laravel\Prompts\select;
|
||||||
|
use function Laravel\Prompts\table;
|
||||||
|
use function Laravel\Prompts\text;
|
||||||
|
|
||||||
|
class HorizonManage extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'horizon:manage {--can-i-restart-this-worker} {--job-status=}';
|
||||||
|
|
||||||
|
protected $description = 'Manage horizon';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if ($this->option('can-i-restart-this-worker')) {
|
||||||
|
return $this->isThereAJobInProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->option('job-status')) {
|
||||||
|
return $this->getJobStatus($this->option('job-status'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = select(
|
||||||
|
label: 'What to do?',
|
||||||
|
options: [
|
||||||
|
'pending' => 'Pending Jobs',
|
||||||
|
'running' => 'Running Jobs',
|
||||||
|
'can-i-restart-this-worker' => 'Can I restart this worker?',
|
||||||
|
'job-status' => 'Job Status',
|
||||||
|
'workers' => 'Workers',
|
||||||
|
'failed' => 'Failed Jobs',
|
||||||
|
'failed-delete' => 'Failed Jobs - Delete',
|
||||||
|
'purge-queues' => 'Purge Queues',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($action === 'can-i-restart-this-worker') {
|
||||||
|
$this->isThereAJobInProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'job-status') {
|
||||||
|
$jobId = text('Which job to check?');
|
||||||
|
$jobStatus = $this->getJobStatus($jobId);
|
||||||
|
$this->info('Job Status: '.$jobStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'pending') {
|
||||||
|
$pendingJobs = app(JobRepository::class)->getPending();
|
||||||
|
$pendingJobsTable = [];
|
||||||
|
if (count($pendingJobs) === 0) {
|
||||||
|
$this->info('No pending jobs found.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($pendingJobs as $pendingJob) {
|
||||||
|
$pendingJobsTable[] = [
|
||||||
|
'id' => $pendingJob->id,
|
||||||
|
'name' => $pendingJob->name,
|
||||||
|
'status' => $pendingJob->status,
|
||||||
|
'reserved_at' => $pendingJob->reserved_at ? now()->parse($pendingJob->reserved_at)->format('Y-m-d H:i:s') : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
table($pendingJobsTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'failed') {
|
||||||
|
$failedJobs = app(JobRepository::class)->getFailed();
|
||||||
|
$failedJobsTable = [];
|
||||||
|
if (count($failedJobs) === 0) {
|
||||||
|
$this->info('No failed jobs found.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($failedJobs as $failedJob) {
|
||||||
|
$failedJobsTable[] = [
|
||||||
|
'id' => $failedJob->id,
|
||||||
|
'name' => $failedJob->name,
|
||||||
|
'failed_at' => $failedJob->failed_at ? now()->parse($failedJob->failed_at)->format('Y-m-d H:i:s') : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
table($failedJobsTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'failed-delete') {
|
||||||
|
$failedJobs = app(JobRepository::class)->getFailed();
|
||||||
|
$failedJobsTable = [];
|
||||||
|
foreach ($failedJobs as $failedJob) {
|
||||||
|
$failedJobsTable[] = [
|
||||||
|
'id' => $failedJob->id,
|
||||||
|
'name' => $failedJob->name,
|
||||||
|
'failed_at' => $failedJob->failed_at ? now()->parse($failedJob->failed_at)->format('Y-m-d H:i:s') : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
app(MetricsRepository::class)->clear();
|
||||||
|
if (count($failedJobsTable) === 0) {
|
||||||
|
$this->info('No failed jobs found.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$jobIds = multiselect(
|
||||||
|
label: 'Which job to delete?',
|
||||||
|
options: collect($failedJobsTable)->mapWithKeys(fn ($job) => [$job['id'] => $job['id'].' - '.$job['name']])->toArray(),
|
||||||
|
);
|
||||||
|
foreach ($jobIds as $jobId) {
|
||||||
|
Artisan::queue('horizon:forget', ['id' => $jobId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'running') {
|
||||||
|
$redisJobRepository = app(CustomJobRepository::class);
|
||||||
|
$runningJobs = $redisJobRepository->getReservedJobs();
|
||||||
|
$runningJobsTable = [];
|
||||||
|
if (count($runningJobs) === 0) {
|
||||||
|
$this->info('No running jobs found.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($runningJobs as $runningJob) {
|
||||||
|
$runningJobsTable[] = [
|
||||||
|
'id' => $runningJob->id,
|
||||||
|
'name' => $runningJob->name,
|
||||||
|
'reserved_at' => $runningJob->reserved_at ? now()->parse($runningJob->reserved_at)->format('Y-m-d H:i:s') : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
table($runningJobsTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'workers') {
|
||||||
|
$redisJobRepository = app(CustomJobRepository::class);
|
||||||
|
$workers = $redisJobRepository->getHorizonWorkers();
|
||||||
|
$workersTable = [];
|
||||||
|
foreach ($workers as $worker) {
|
||||||
|
$workersTable[] = [
|
||||||
|
'name' => $worker->name,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
table($workersTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'purge-queues') {
|
||||||
|
$getQueues = app(CustomJobRepository::class)->getQueues();
|
||||||
|
$queueName = select(
|
||||||
|
label: 'Which queue to purge?',
|
||||||
|
options: $getQueues,
|
||||||
|
);
|
||||||
|
$redisJobRepository = app(RedisJobRepository::class);
|
||||||
|
$redisJobRepository->purge($queueName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isThereAJobInProgress()
|
||||||
|
{
|
||||||
|
$runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)->get();
|
||||||
|
$count = $runningJobs->count();
|
||||||
|
if ($count === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getJobStatus(string $jobId)
|
||||||
|
{
|
||||||
|
return getJobStatus($jobId);
|
||||||
|
}
|
||||||
|
}
|
@@ -35,8 +35,7 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->servers = Server::all();
|
$this->servers = Server::all();
|
||||||
if (isCloud()) {
|
if (! isCloud()) {
|
||||||
} else {
|
|
||||||
$this->send_alive_signal();
|
$this->send_alive_signal();
|
||||||
get_public_ips();
|
get_public_ips();
|
||||||
}
|
}
|
||||||
@@ -88,8 +87,10 @@ class Init extends Command
|
|||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
if (! is_null(config('constants.coolify.autoupdate', null))) {
|
if (! is_null(config('constants.coolify.autoupdate', null))) {
|
||||||
if (config('constants.coolify.autoupdate') == true) {
|
if (config('constants.coolify.autoupdate') == true) {
|
||||||
|
echo "Enabling auto-update\n";
|
||||||
$settings->update(['is_auto_update_enabled' => true]);
|
$settings->update(['is_auto_update_enabled' => true]);
|
||||||
} else {
|
} else {
|
||||||
|
echo "Disabling auto-update\n";
|
||||||
$settings->update(['is_auto_update_enabled' => false]);
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +120,9 @@ class Init extends Command
|
|||||||
private function update_user_emails()
|
private function update_user_emails()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
User::whereRaw('email ~ \'[A-Z]\'')->get()->each(fn (User $user) => $user->update(['email' => strtolower($user->email)]));
|
User::whereRaw('email ~ \'[A-Z]\'')->get()->each(function (User $user) {
|
||||||
|
$user->update(['email' => strtolower($user->email)]);
|
||||||
|
});
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Error in updating user emails: {$e->getMessage()}\n";
|
echo "Error in updating user emails: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
@@ -200,7 +203,6 @@ class Init extends Command
|
|||||||
try {
|
try {
|
||||||
$database = StandalonePostgresql::withTrashed()->find(0);
|
$database = StandalonePostgresql::withTrashed()->find(0);
|
||||||
if ($database && $database->trashed()) {
|
if ($database && $database->trashed()) {
|
||||||
echo "Restoring coolify db backup\n";
|
|
||||||
$database->restore();
|
$database->restore();
|
||||||
$scheduledBackup = ScheduledDatabaseBackup::find(0);
|
$scheduledBackup = ScheduledDatabaseBackup::find(0);
|
||||||
if (! $scheduledBackup) {
|
if (! $scheduledBackup) {
|
||||||
|
@@ -6,13 +6,11 @@ use App\Jobs\CheckAndStartSentinelJob;
|
|||||||
use App\Jobs\CheckForUpdatesJob;
|
use App\Jobs\CheckForUpdatesJob;
|
||||||
use App\Jobs\CheckHelperImageJob;
|
use App\Jobs\CheckHelperImageJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\CleanupStaleMultiplexedConnections;
|
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
use App\Jobs\DockerCleanupJob;
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\PullTemplatesFromCDN;
|
use App\Jobs\PullTemplatesFromCDN;
|
||||||
use App\Jobs\ScheduledTaskJob;
|
use App\Jobs\ScheduledTaskJob;
|
||||||
use App\Jobs\ServerCheckJob;
|
use App\Jobs\ServerCheckJob;
|
||||||
use App\Jobs\ServerCleanupMux;
|
|
||||||
use App\Jobs\ServerStorageCheckJob;
|
use App\Jobs\ServerStorageCheckJob;
|
||||||
use App\Jobs\UpdateCoolifyJob;
|
use App\Jobs\UpdateCoolifyJob;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
@@ -23,6 +21,7 @@ use App\Models\Team;
|
|||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class Kernel extends ConsoleKernel
|
class Kernel extends ConsoleKernel
|
||||||
{
|
{
|
||||||
@@ -91,13 +90,23 @@ class Kernel extends ConsoleKernel
|
|||||||
|
|
||||||
private function pullImages(): void
|
private function pullImages(): void
|
||||||
{
|
{
|
||||||
|
if (isCloud()) {
|
||||||
|
$servers = $this->allServers->whereRelation('team.subscription', 'stripe_invoice_paid', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
|
||||||
|
$own = Team::find(0)->servers;
|
||||||
|
$servers = $servers->merge($own);
|
||||||
|
} else {
|
||||||
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
|
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
|
||||||
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
|
try {
|
||||||
if ($server->isSentinelEnabled()) {
|
if ($server->isSentinelEnabled()) {
|
||||||
$this->scheduleInstance->job(function () use ($server) {
|
$this->scheduleInstance->job(function () use ($server) {
|
||||||
CheckAndStartSentinelJob::dispatch($server);
|
CheckAndStartSentinelJob::dispatch($server);
|
||||||
})->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
})->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error pulling images: '.$e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->scheduleInstance->job(new CheckHelperImageJob)
|
$this->scheduleInstance->job(new CheckHelperImageJob)
|
||||||
->cron($this->updateCheckFrequency)
|
->cron($this->updateCheckFrequency)
|
||||||
@@ -124,7 +133,7 @@ class Kernel extends ConsoleKernel
|
|||||||
private function checkResources(): void
|
private function checkResources(): void
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
$servers = $this->allServers->whereHas('team.subscription')->get();
|
$servers = $this->allServers->whereRelation('team.subscription', 'stripe_invoice_paid', true)->get();
|
||||||
$own = Team::find(0)->servers;
|
$own = Team::find(0)->servers;
|
||||||
$servers = $servers->merge($own);
|
$servers = $servers->merge($own);
|
||||||
} else {
|
} else {
|
||||||
@@ -132,15 +141,16 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
|
try {
|
||||||
$serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone);
|
$serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone);
|
||||||
|
if (validate_timezone($serverTimezone) === false) {
|
||||||
|
$serverTimezone = config('app.timezone');
|
||||||
|
}
|
||||||
|
|
||||||
// Sentinel check
|
// Sentinel check
|
||||||
$lastSentinelUpdate = $server->sentinel_updated_at;
|
$lastSentinelUpdate = $server->sentinel_updated_at;
|
||||||
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
||||||
// Check container status every minute if Sentinel does not activated
|
// Check container status every minute if Sentinel does not activated
|
||||||
if (validate_timezone($serverTimezone) === false) {
|
|
||||||
$serverTimezone = config('app.timezone');
|
|
||||||
}
|
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
$this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyFiveMinutes()->onOneServer();
|
$this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyFiveMinutes()->onOneServer();
|
||||||
} else {
|
} else {
|
||||||
@@ -148,15 +158,19 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
// $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyFiveMinutes()->onOneServer();
|
// $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyFiveMinutes()->onOneServer();
|
||||||
|
|
||||||
// Check storage usage every 10 minutes if Sentinel does not activated
|
$serverDiskUsageCheckFrequency = data_get($server->settings, 'server_disk_usage_check_frequency', '0 * * * *');
|
||||||
$this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
if (isset(VALID_CRON_STRINGS[$serverDiskUsageCheckFrequency])) {
|
||||||
|
$serverDiskUsageCheckFrequency = VALID_CRON_STRINGS[$serverDiskUsageCheckFrequency];
|
||||||
}
|
}
|
||||||
if ($server->settings->force_docker_cleanup) {
|
$this->scheduleInstance->job(new ServerStorageCheckJob($server))->cron($serverDiskUsageCheckFrequency)->timezone($serverTimezone)->onOneServer();
|
||||||
$this->scheduleInstance->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
|
||||||
} else {
|
|
||||||
$this->scheduleInstance->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$dockerCleanupFrequency = data_get($server->settings, 'docker_cleanup_frequency', '0 * * * *');
|
||||||
|
if (isset(VALID_CRON_STRINGS[$dockerCleanupFrequency])) {
|
||||||
|
$dockerCleanupFrequency = VALID_CRON_STRINGS[$dockerCleanupFrequency];
|
||||||
|
}
|
||||||
|
$this->scheduleInstance->job(new DockerCleanupJob($server))->cron($dockerCleanupFrequency)->timezone($serverTimezone)->onOneServer();
|
||||||
|
|
||||||
// Cleanup multiplexed connections every hour
|
// Cleanup multiplexed connections every hour
|
||||||
// $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
// $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
||||||
|
|
||||||
@@ -166,6 +180,9 @@ class Kernel extends ConsoleKernel
|
|||||||
$server->restartContainer('coolify-sentinel');
|
$server->restartContainer('coolify-sentinel');
|
||||||
})->daily()->onOneServer();
|
})->daily()->onOneServer();
|
||||||
}
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error checking resources: '.$e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,25 +192,51 @@ class Kernel extends ConsoleKernel
|
|||||||
if ($scheduled_backups->isEmpty()) {
|
if ($scheduled_backups->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$finalScheduledBackups = collect();
|
||||||
foreach ($scheduled_backups as $scheduled_backup) {
|
foreach ($scheduled_backups as $scheduled_backup) {
|
||||||
if (is_null(data_get($scheduled_backup, 'database'))) {
|
if (blank(data_get($scheduled_backup, 'database'))) {
|
||||||
$scheduled_backup->delete();
|
$scheduled_backup->delete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$server = $scheduled_backup->server();
|
$server = $scheduled_backup->server();
|
||||||
|
if (blank($server)) {
|
||||||
|
$scheduled_backup->delete();
|
||||||
|
|
||||||
if (is_null($server)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if ($server->isFunctional() === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isCloud() && data_get($server->team->subscription, 'stripe_invoice_paid', false) === false && $server->team->id !== 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$finalScheduledBackups->push($scheduled_backup);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($finalScheduledBackups as $scheduled_backup) {
|
||||||
|
try {
|
||||||
|
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||||
|
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||||
|
}
|
||||||
|
$server = $scheduled_backup->server();
|
||||||
|
$serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone);
|
||||||
|
|
||||||
|
if (validate_timezone($serverTimezone) === false) {
|
||||||
|
$serverTimezone = config('app.timezone');
|
||||||
|
}
|
||||||
|
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||||
}
|
}
|
||||||
|
$serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone);
|
||||||
$this->scheduleInstance->job(new DatabaseBackupJob(
|
$this->scheduleInstance->job(new DatabaseBackupJob(
|
||||||
backup: $scheduled_backup
|
backup: $scheduled_backup
|
||||||
))->cron($scheduled_backup->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error scheduling backup: '.$e->getMessage());
|
||||||
|
Log::error($e->getTraceAsString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,37 +246,60 @@ class Kernel extends ConsoleKernel
|
|||||||
if ($scheduled_tasks->isEmpty()) {
|
if ($scheduled_tasks->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$finalScheduledTasks = collect();
|
||||||
foreach ($scheduled_tasks as $scheduled_task) {
|
foreach ($scheduled_tasks as $scheduled_task) {
|
||||||
$service = $scheduled_task->service;
|
$service = $scheduled_task->service;
|
||||||
$application = $scheduled_task->application;
|
$application = $scheduled_task->application;
|
||||||
|
|
||||||
if (! $application && ! $service) {
|
$server = $scheduled_task->server();
|
||||||
|
if (blank($server)) {
|
||||||
$scheduled_task->delete();
|
$scheduled_task->delete();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($application) {
|
|
||||||
if (str($application->status)->contains('running') === false) {
|
if ($server->isFunctional() === false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if ($service) {
|
|
||||||
if (str($service->status)->contains('running') === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (isCloud() && data_get($server->team->subscription, 'stripe_invoice_paid', false) === false && $server->team->id !== 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $service && ! $application) {
|
||||||
|
$scheduled_task->delete();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($application && str($application->status)->contains('running') === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($service && str($service->status)->contains('running') === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$finalScheduledTasks->push($scheduled_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($finalScheduledTasks as $scheduled_task) {
|
||||||
|
try {
|
||||||
$server = $scheduled_task->server();
|
$server = $scheduled_task->server();
|
||||||
if (! $server) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
||||||
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
||||||
}
|
}
|
||||||
|
$serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone);
|
||||||
|
|
||||||
|
if (validate_timezone($serverTimezone) === false) {
|
||||||
|
$serverTimezone = config('app.timezone');
|
||||||
|
}
|
||||||
$this->scheduleInstance->job(new ScheduledTaskJob(
|
$this->scheduleInstance->job(new ScheduledTaskJob(
|
||||||
task: $scheduled_task
|
task: $scheduled_task
|
||||||
))->cron($scheduled_task->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error scheduling task: '.$e->getMessage());
|
||||||
|
Log::error($e->getTraceAsString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
app/Contracts/CustomJobRepositoryInterface.php
Normal file
19
app/Contracts/CustomJobRepositoryInterface.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Laravel\Horizon\Contracts\JobRepository;
|
||||||
|
|
||||||
|
interface CustomJobRepositoryInterface extends JobRepository
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get all jobs with a specific status.
|
||||||
|
*/
|
||||||
|
public function getJobsByStatus(string $status): Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the count of jobs with a specific status.
|
||||||
|
*/
|
||||||
|
public function countJobsByStatus(string $status): int;
|
||||||
|
}
|
24
app/Events/DockerCleanupDone.php
Normal file
24
app/Events/DockerCleanupDone.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use App\Models\DockerCleanupExecution;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class DockerCleanupDone implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(public DockerCleanupExecution $execution) {}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel('team.'.$this->execution->server->team->id),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
34
app/Events/RestoreJobFinished.php
Normal file
34
app/Events/RestoreJobFinished.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class RestoreJobFinished
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public function __construct($data)
|
||||||
|
{
|
||||||
|
$scriptPath = data_get($data, 'scriptPath');
|
||||||
|
$tmpPath = data_get($data, 'tmpPath');
|
||||||
|
$container = data_get($data, 'container');
|
||||||
|
$serverId = data_get($data, 'serverId');
|
||||||
|
if (filled($scriptPath) && filled($tmpPath) && filled($container) && filled($serverId)) {
|
||||||
|
if (str($tmpPath)->startsWith('/tmp/')
|
||||||
|
&& str($scriptPath)->startsWith('/tmp/')
|
||||||
|
&& ! str($tmpPath)->contains('..')
|
||||||
|
&& ! str($scriptPath)->contains('..')
|
||||||
|
&& strlen($tmpPath) > 5 // longer than just "/tmp/"
|
||||||
|
&& strlen($scriptPath) > 5
|
||||||
|
) {
|
||||||
|
$commands[] = "docker exec {$container} sh -c 'rm {$scriptPath}'";
|
||||||
|
$commands[] = "docker exec {$container} sh -c 'rm {$tmpPath}'";
|
||||||
|
instant_remote_process($commands, Server::find($serverId), throwError: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -18,6 +18,7 @@ use App\Models\Service;
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
|
use Spatie\Url\Url;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
@@ -27,6 +28,9 @@ class ApplicationsController extends Controller
|
|||||||
{
|
{
|
||||||
$application->makeHidden([
|
$application->makeHidden([
|
||||||
'id',
|
'id',
|
||||||
|
'resourceable',
|
||||||
|
'resourceable_id',
|
||||||
|
'resourceable_type',
|
||||||
]);
|
]);
|
||||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
$application->makeHidden([
|
$application->makeHidden([
|
||||||
@@ -114,11 +118,12 @@ class ApplicationsController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
|
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
|
||||||
properties: [
|
properties: [
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
|
'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'],
|
'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'],
|
||||||
'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
|
'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
|
||||||
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
|
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
|
||||||
@@ -185,8 +190,17 @@ class ApplicationsController extends Controller
|
|||||||
),
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 201,
|
||||||
description: 'Application created successfully.',
|
description: 'Application created successfully.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
@@ -220,11 +234,12 @@ class ApplicationsController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'github_app_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
|
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'github_app_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
|
||||||
properties: [
|
properties: [
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
|
'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'github_app_uuid' => ['type' => 'string', 'description' => 'The Github App UUID.'],
|
'github_app_uuid' => ['type' => 'string', 'description' => 'The Github App UUID.'],
|
||||||
'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'],
|
'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'],
|
||||||
'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
|
'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
|
||||||
@@ -291,8 +306,17 @@ class ApplicationsController extends Controller
|
|||||||
),
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 201,
|
||||||
description: 'Application created successfully.',
|
description: 'Application created successfully.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
@@ -326,11 +350,12 @@ class ApplicationsController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'private_key_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
|
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'private_key_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
|
||||||
properties: [
|
properties: [
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
|
'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'private_key_uuid' => ['type' => 'string', 'description' => 'The private key UUID.'],
|
'private_key_uuid' => ['type' => 'string', 'description' => 'The private key UUID.'],
|
||||||
'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'],
|
'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'],
|
||||||
'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
|
'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
|
||||||
@@ -397,8 +422,17 @@ class ApplicationsController extends Controller
|
|||||||
),
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 201,
|
||||||
description: 'Application created successfully.',
|
description: 'Application created successfully.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
@@ -432,11 +466,12 @@ class ApplicationsController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'dockerfile'],
|
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'dockerfile'],
|
||||||
properties: [
|
properties: [
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
|
'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
|
'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
|
||||||
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
|
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
|
||||||
'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
|
'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
|
||||||
@@ -487,8 +522,17 @@ class ApplicationsController extends Controller
|
|||||||
),
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 201,
|
||||||
description: 'Application created successfully.',
|
description: 'Application created successfully.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
@@ -522,11 +566,12 @@ class ApplicationsController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'docker_registry_image_name', 'ports_exposes'],
|
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'docker_registry_image_name', 'ports_exposes'],
|
||||||
properties: [
|
properties: [
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
|
'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
||||||
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
||||||
'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
|
'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
|
||||||
@@ -574,8 +619,17 @@ class ApplicationsController extends Controller
|
|||||||
),
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 201,
|
||||||
description: 'Application created successfully.',
|
description: 'Application created successfully.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
@@ -609,11 +663,12 @@ class ApplicationsController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'docker_compose_raw'],
|
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'docker_compose_raw'],
|
||||||
properties: [
|
properties: [
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
|
'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'],
|
'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'],
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID if the server has more than one destinations.'],
|
'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID if the server has more than one destinations.'],
|
||||||
'name' => ['type' => 'string', 'description' => 'The application name.'],
|
'name' => ['type' => 'string', 'description' => 'The application name.'],
|
||||||
@@ -627,8 +682,17 @@ class ApplicationsController extends Controller
|
|||||||
),
|
),
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 201,
|
||||||
description: 'Application created successfully.',
|
description: 'Application created successfully.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
@@ -647,7 +711,7 @@ class ApplicationsController extends Controller
|
|||||||
|
|
||||||
private function create_application(Request $request, $type)
|
private function create_application(Request $request, $type)
|
||||||
{
|
{
|
||||||
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration'];
|
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration'];
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
@@ -661,7 +725,8 @@ class ApplicationsController extends Controller
|
|||||||
'name' => 'string|max:255',
|
'name' => 'string|max:255',
|
||||||
'description' => 'string|nullable',
|
'description' => 'string|nullable',
|
||||||
'project_uuid' => 'string|required',
|
'project_uuid' => 'string|required',
|
||||||
'environment_name' => 'string|required',
|
'environment_name' => 'string|nullable',
|
||||||
|
'environment_uuid' => 'string|nullable',
|
||||||
'server_uuid' => 'string|required',
|
'server_uuid' => 'string|required',
|
||||||
'destination_uuid' => 'string',
|
'destination_uuid' => 'string',
|
||||||
]);
|
]);
|
||||||
@@ -681,6 +746,11 @@ class ApplicationsController extends Controller
|
|||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$environmentUuid = $request->environment_uuid;
|
||||||
|
$environmentName = $request->environment_name;
|
||||||
|
if (blank($environmentUuid) && blank($environmentName)) {
|
||||||
|
return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422);
|
||||||
|
}
|
||||||
$serverUuid = $request->server_uuid;
|
$serverUuid = $request->server_uuid;
|
||||||
$fqdn = $request->domains;
|
$fqdn = $request->domains;
|
||||||
$instantDeploy = $request->instant_deploy;
|
$instantDeploy = $request->instant_deploy;
|
||||||
@@ -713,7 +783,10 @@ class ApplicationsController extends Controller
|
|||||||
if (! $project) {
|
if (! $project) {
|
||||||
return response()->json(['message' => 'Project not found.'], 404);
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
}
|
}
|
||||||
$environment = $project->environments()->where('name', $request->environment_name)->first();
|
$environment = $project->environments()->where('name', $environmentName)->first();
|
||||||
|
if (! $environment) {
|
||||||
|
$environment = $project->environments()->where('uuid', $environmentUuid)->first();
|
||||||
|
}
|
||||||
if (! $environment) {
|
if (! $environment) {
|
||||||
return response()->json(['message' => 'Environment not found.'], 404);
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
}
|
}
|
||||||
@@ -730,12 +803,6 @@ class ApplicationsController extends Controller
|
|||||||
}
|
}
|
||||||
$destination = $destinations->first();
|
$destination = $destinations->first();
|
||||||
if ($type === 'public') {
|
if ($type === 'public') {
|
||||||
if (! $request->has('name')) {
|
|
||||||
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
|
||||||
}
|
|
||||||
if ($request->build_pack === 'dockercompose') {
|
|
||||||
$request->offsetSet('ports_exposes', '80');
|
|
||||||
}
|
|
||||||
$validationRules = [
|
$validationRules = [
|
||||||
'git_repository' => 'string|required',
|
'git_repository' => 'string|required',
|
||||||
'git_branch' => 'string|required',
|
'git_branch' => 'string|required',
|
||||||
@@ -745,7 +812,12 @@ class ApplicationsController extends Controller
|
|||||||
'docker_compose_raw' => 'string|nullable',
|
'docker_compose_raw' => 'string|nullable',
|
||||||
'docker_compose_domains' => 'array|nullable',
|
'docker_compose_domains' => 'array|nullable',
|
||||||
];
|
];
|
||||||
$validationRules = array_merge($validationRules, sharedDataApplications());
|
// ports_exposes is not required for dockercompose
|
||||||
|
if ($request->build_pack === 'dockercompose') {
|
||||||
|
$validationRules['ports_exposes'] = 'string';
|
||||||
|
$request->offsetSet('ports_exposes', '80');
|
||||||
|
}
|
||||||
|
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||||
$validator = customApiValidator($request->all(), $validationRules);
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -753,7 +825,9 @@ class ApplicationsController extends Controller
|
|||||||
'errors' => $validator->errors(),
|
'errors' => $validator->errors(),
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
if (! $request->has('name')) {
|
||||||
|
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
||||||
|
}
|
||||||
$return = $this->validateDataApplications($request, $server);
|
$return = $this->validateDataApplications($request, $server);
|
||||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
return $return;
|
return $return;
|
||||||
@@ -776,7 +850,13 @@ class ApplicationsController extends Controller
|
|||||||
if ($dockerComposeDomainsJson->count() > 0) {
|
if ($dockerComposeDomainsJson->count() > 0) {
|
||||||
$application->docker_compose_domains = $dockerComposeDomainsJson;
|
$application->docker_compose_domains = $dockerComposeDomainsJson;
|
||||||
}
|
}
|
||||||
|
$repository_url_parsed = Url::fromString($request->git_repository);
|
||||||
|
$git_host = $repository_url_parsed->getHost();
|
||||||
|
if ($git_host === 'github.com') {
|
||||||
|
$application->source_type = GithubApp::class;
|
||||||
|
$application->source_id = GithubApp::find(0)->id;
|
||||||
|
}
|
||||||
|
$application->git_repository = $repository_url_parsed->getSegment(1).'/'.$repository_url_parsed->getSegment(2);
|
||||||
$application->fqdn = $fqdn;
|
$application->fqdn = $fqdn;
|
||||||
$application->destination_id = $destination->id;
|
$application->destination_id = $destination->id;
|
||||||
$application->destination_type = $destination->getMorphClass();
|
$application->destination_type = $destination->getMorphClass();
|
||||||
@@ -791,7 +871,7 @@ class ApplicationsController extends Controller
|
|||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
}
|
}
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
if ($application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
@@ -815,14 +895,8 @@ class ApplicationsController extends Controller
|
|||||||
return response()->json(serializeApiResponse([
|
return response()->json(serializeApiResponse([
|
||||||
'uuid' => data_get($application, 'uuid'),
|
'uuid' => data_get($application, 'uuid'),
|
||||||
'domains' => data_get($application, 'domains'),
|
'domains' => data_get($application, 'domains'),
|
||||||
]));
|
]))->setStatusCode(201);
|
||||||
} elseif ($type === 'private-gh-app') {
|
} elseif ($type === 'private-gh-app') {
|
||||||
if (! $request->has('name')) {
|
|
||||||
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
|
||||||
}
|
|
||||||
if ($request->build_pack === 'dockercompose') {
|
|
||||||
$request->offsetSet('ports_exposes', '80');
|
|
||||||
}
|
|
||||||
$validationRules = [
|
$validationRules = [
|
||||||
'git_repository' => 'string|required',
|
'git_repository' => 'string|required',
|
||||||
'git_branch' => 'string|required',
|
'git_branch' => 'string|required',
|
||||||
@@ -833,7 +907,7 @@ class ApplicationsController extends Controller
|
|||||||
'docker_compose_location' => 'string',
|
'docker_compose_location' => 'string',
|
||||||
'docker_compose_raw' => 'string|nullable',
|
'docker_compose_raw' => 'string|nullable',
|
||||||
];
|
];
|
||||||
$validationRules = array_merge($validationRules, sharedDataApplications());
|
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||||
|
|
||||||
$validator = customApiValidator($request->all(), $validationRules);
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
@@ -842,6 +916,14 @@ class ApplicationsController extends Controller
|
|||||||
'errors' => $validator->errors(),
|
'errors' => $validator->errors(),
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $request->has('name')) {
|
||||||
|
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
||||||
|
}
|
||||||
|
if ($request->build_pack === 'dockercompose') {
|
||||||
|
$request->offsetSet('ports_exposes', '80');
|
||||||
|
}
|
||||||
|
|
||||||
$return = $this->validateDataApplications($request, $server);
|
$return = $this->validateDataApplications($request, $server);
|
||||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
return $return;
|
return $return;
|
||||||
@@ -884,13 +966,13 @@ class ApplicationsController extends Controller
|
|||||||
$application->environment_id = $environment->id;
|
$application->environment_id = $environment->id;
|
||||||
$application->source_type = $githubApp->getMorphClass();
|
$application->source_type = $githubApp->getMorphClass();
|
||||||
$application->source_id = $githubApp->id;
|
$application->source_id = $githubApp->id;
|
||||||
|
$application->save();
|
||||||
|
$application->refresh();
|
||||||
if (isset($useBuildServer)) {
|
if (isset($useBuildServer)) {
|
||||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
}
|
}
|
||||||
$application->save();
|
if ($application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->refresh();
|
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
@@ -914,14 +996,8 @@ class ApplicationsController extends Controller
|
|||||||
return response()->json(serializeApiResponse([
|
return response()->json(serializeApiResponse([
|
||||||
'uuid' => data_get($application, 'uuid'),
|
'uuid' => data_get($application, 'uuid'),
|
||||||
'domains' => data_get($application, 'domains'),
|
'domains' => data_get($application, 'domains'),
|
||||||
]));
|
]))->setStatusCode(201);
|
||||||
} elseif ($type === 'private-deploy-key') {
|
} elseif ($type === 'private-deploy-key') {
|
||||||
if (! $request->has('name')) {
|
|
||||||
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
|
||||||
}
|
|
||||||
if ($request->build_pack === 'dockercompose') {
|
|
||||||
$request->offsetSet('ports_exposes', '80');
|
|
||||||
}
|
|
||||||
|
|
||||||
$validationRules = [
|
$validationRules = [
|
||||||
'git_repository' => 'string|required',
|
'git_repository' => 'string|required',
|
||||||
@@ -934,7 +1010,7 @@ class ApplicationsController extends Controller
|
|||||||
'docker_compose_raw' => 'string|nullable',
|
'docker_compose_raw' => 'string|nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
$validationRules = array_merge($validationRules, sharedDataApplications());
|
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||||
$validator = customApiValidator($request->all(), $validationRules);
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
@@ -943,6 +1019,13 @@ class ApplicationsController extends Controller
|
|||||||
'errors' => $validator->errors(),
|
'errors' => $validator->errors(),
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
if (! $request->has('name')) {
|
||||||
|
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
||||||
|
}
|
||||||
|
if ($request->build_pack === 'dockercompose') {
|
||||||
|
$request->offsetSet('ports_exposes', '80');
|
||||||
|
}
|
||||||
|
|
||||||
$return = $this->validateDataApplications($request, $server);
|
$return = $this->validateDataApplications($request, $server);
|
||||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
return $return;
|
return $return;
|
||||||
@@ -980,13 +1063,13 @@ class ApplicationsController extends Controller
|
|||||||
$application->destination_id = $destination->id;
|
$application->destination_id = $destination->id;
|
||||||
$application->destination_type = $destination->getMorphClass();
|
$application->destination_type = $destination->getMorphClass();
|
||||||
$application->environment_id = $environment->id;
|
$application->environment_id = $environment->id;
|
||||||
|
$application->save();
|
||||||
|
$application->refresh();
|
||||||
if (isset($useBuildServer)) {
|
if (isset($useBuildServer)) {
|
||||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
}
|
}
|
||||||
$application->save();
|
if ($application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->refresh();
|
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
@@ -1010,16 +1093,12 @@ class ApplicationsController extends Controller
|
|||||||
return response()->json(serializeApiResponse([
|
return response()->json(serializeApiResponse([
|
||||||
'uuid' => data_get($application, 'uuid'),
|
'uuid' => data_get($application, 'uuid'),
|
||||||
'domains' => data_get($application, 'domains'),
|
'domains' => data_get($application, 'domains'),
|
||||||
]));
|
]))->setStatusCode(201);
|
||||||
} elseif ($type === 'dockerfile') {
|
} elseif ($type === 'dockerfile') {
|
||||||
if (! $request->has('name')) {
|
|
||||||
$request->offsetSet('name', 'dockerfile-'.new Cuid2);
|
|
||||||
}
|
|
||||||
|
|
||||||
$validationRules = [
|
$validationRules = [
|
||||||
'dockerfile' => 'string|required',
|
'dockerfile' => 'string|required',
|
||||||
];
|
];
|
||||||
$validationRules = array_merge($validationRules, sharedDataApplications());
|
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||||
$validator = customApiValidator($request->all(), $validationRules);
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
@@ -1028,6 +1107,10 @@ class ApplicationsController extends Controller
|
|||||||
'errors' => $validator->errors(),
|
'errors' => $validator->errors(),
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
if (! $request->has('name')) {
|
||||||
|
$request->offsetSet('name', 'dockerfile-'.new Cuid2);
|
||||||
|
}
|
||||||
|
|
||||||
$return = $this->validateDataApplications($request, $server);
|
$return = $this->validateDataApplications($request, $server);
|
||||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
return $return;
|
return $return;
|
||||||
@@ -1066,16 +1149,16 @@ class ApplicationsController extends Controller
|
|||||||
$application->destination_id = $destination->id;
|
$application->destination_id = $destination->id;
|
||||||
$application->destination_type = $destination->getMorphClass();
|
$application->destination_type = $destination->getMorphClass();
|
||||||
$application->environment_id = $environment->id;
|
$application->environment_id = $environment->id;
|
||||||
if (isset($useBuildServer)) {
|
|
||||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
|
||||||
$application->settings->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
$application->git_repository = 'coollabsio/coolify';
|
$application->git_repository = 'coollabsio/coolify';
|
||||||
$application->git_branch = 'main';
|
$application->git_branch = 'main';
|
||||||
$application->save();
|
$application->save();
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
if (isset($useBuildServer)) {
|
||||||
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
|
$application->settings->save();
|
||||||
|
}
|
||||||
|
if ($application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
@@ -1095,17 +1178,14 @@ class ApplicationsController extends Controller
|
|||||||
return response()->json(serializeApiResponse([
|
return response()->json(serializeApiResponse([
|
||||||
'uuid' => data_get($application, 'uuid'),
|
'uuid' => data_get($application, 'uuid'),
|
||||||
'domains' => data_get($application, 'domains'),
|
'domains' => data_get($application, 'domains'),
|
||||||
]));
|
]))->setStatusCode(201);
|
||||||
} elseif ($type === 'dockerimage') {
|
} elseif ($type === 'dockerimage') {
|
||||||
if (! $request->has('name')) {
|
|
||||||
$request->offsetSet('name', 'docker-image-'.new Cuid2);
|
|
||||||
}
|
|
||||||
$validationRules = [
|
$validationRules = [
|
||||||
'docker_registry_image_name' => 'string|required',
|
'docker_registry_image_name' => 'string|required',
|
||||||
'docker_registry_image_tag' => 'string',
|
'docker_registry_image_tag' => 'string',
|
||||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
|
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
|
||||||
];
|
];
|
||||||
$validationRules = array_merge($validationRules, sharedDataApplications());
|
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||||
$validator = customApiValidator($request->all(), $validationRules);
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
@@ -1114,6 +1194,9 @@ class ApplicationsController extends Controller
|
|||||||
'errors' => $validator->errors(),
|
'errors' => $validator->errors(),
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
if (! $request->has('name')) {
|
||||||
|
$request->offsetSet('name', 'docker-image-'.new Cuid2);
|
||||||
|
}
|
||||||
$return = $this->validateDataApplications($request, $server);
|
$return = $this->validateDataApplications($request, $server);
|
||||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
return $return;
|
return $return;
|
||||||
@@ -1130,16 +1213,16 @@ class ApplicationsController extends Controller
|
|||||||
$application->destination_id = $destination->id;
|
$application->destination_id = $destination->id;
|
||||||
$application->destination_type = $destination->getMorphClass();
|
$application->destination_type = $destination->getMorphClass();
|
||||||
$application->environment_id = $environment->id;
|
$application->environment_id = $environment->id;
|
||||||
if (isset($useBuildServer)) {
|
|
||||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
|
||||||
$application->settings->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
$application->git_repository = 'coollabsio/coolify';
|
$application->git_repository = 'coollabsio/coolify';
|
||||||
$application->git_branch = 'main';
|
$application->git_branch = 'main';
|
||||||
$application->save();
|
$application->save();
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
if (! $application->settings->is_container_label_readonly_enabled) {
|
if (isset($useBuildServer)) {
|
||||||
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
|
$application->settings->save();
|
||||||
|
}
|
||||||
|
if ($application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
$application->save();
|
$application->save();
|
||||||
}
|
}
|
||||||
@@ -1159,9 +1242,9 @@ class ApplicationsController extends Controller
|
|||||||
return response()->json(serializeApiResponse([
|
return response()->json(serializeApiResponse([
|
||||||
'uuid' => data_get($application, 'uuid'),
|
'uuid' => data_get($application, 'uuid'),
|
||||||
'domains' => data_get($application, 'domains'),
|
'domains' => data_get($application, 'domains'),
|
||||||
]));
|
]))->setStatusCode(201);
|
||||||
} elseif ($type === 'dockercompose') {
|
} elseif ($type === 'dockercompose') {
|
||||||
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'instant_deploy', 'docker_compose_raw'];
|
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'instant_deploy', 'docker_compose_raw'];
|
||||||
|
|
||||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
if ($validator->fails() || ! empty($extraFields)) {
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
@@ -1183,7 +1266,7 @@ class ApplicationsController extends Controller
|
|||||||
$validationRules = [
|
$validationRules = [
|
||||||
'docker_compose_raw' => 'string|required',
|
'docker_compose_raw' => 'string|required',
|
||||||
];
|
];
|
||||||
$validationRules = array_merge($validationRules, sharedDataApplications());
|
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||||
$validator = customApiValidator($request->all(), $validationRules);
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
@@ -1216,11 +1299,6 @@ class ApplicationsController extends Controller
|
|||||||
$dockerCompose = base64_decode($request->docker_compose_raw);
|
$dockerCompose = base64_decode($request->docker_compose_raw);
|
||||||
$dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
|
$dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
|
||||||
|
|
||||||
// $isValid = validateComposeFile($dockerComposeRaw, $server_id);
|
|
||||||
// if ($isValid !== 'OK') {
|
|
||||||
// return $this->dispatch('error', "Invalid docker-compose file.\n$isValid");
|
|
||||||
// }
|
|
||||||
|
|
||||||
$service = new Service;
|
$service = new Service;
|
||||||
removeUnnecessaryFieldsFromRequest($request);
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
$service->fill($request->all());
|
$service->fill($request->all());
|
||||||
@@ -1241,7 +1319,7 @@ class ApplicationsController extends Controller
|
|||||||
return response()->json(serializeApiResponse([
|
return response()->json(serializeApiResponse([
|
||||||
'uuid' => data_get($service, 'uuid'),
|
'uuid' => data_get($service, 'uuid'),
|
||||||
'domains' => data_get($service, 'domains'),
|
'domains' => data_get($service, 'domains'),
|
||||||
]));
|
]))->setStatusCode(201);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(['message' => 'Invalid type.'], 400);
|
return response()->json(['message' => 'Invalid type.'], 400);
|
||||||
@@ -1313,6 +1391,108 @@ class ApplicationsController extends Controller
|
|||||||
return response()->json($this->removeSensitiveData($application));
|
return response()->json($this->removeSensitiveData($application));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get application logs.',
|
||||||
|
description: 'Get application logs by UUID.',
|
||||||
|
path: '/applications/{uuid}/logs',
|
||||||
|
operationId: 'get-application-logs-by-uuid',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Applications'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the application.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'lines',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Number of lines to show from the end of the logs.',
|
||||||
|
required: false,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'integer',
|
||||||
|
format: 'int32',
|
||||||
|
default: 100,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get application logs by UUID.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'logs' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function logs_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
if (! $application) {
|
||||||
|
return response()->json(['message' => 'Application not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$containers = getCurrentApplicationContainerStatus($application->destination->server, $application->id);
|
||||||
|
|
||||||
|
if ($containers->count() == 0) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Application is not running.',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$container = $containers->first();
|
||||||
|
|
||||||
|
$status = getContainerStatus($application->destination->server, $container['Names']);
|
||||||
|
if ($status !== 'running') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Application is not running.',
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = $request->query->get('lines', 100) ?: 100;
|
||||||
|
$logs = getContainerLogs($application->destination->server, $container['ID'], $lines);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'logs' => $logs,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
#[OA\Delete(
|
#[OA\Delete(
|
||||||
summary: 'Delete',
|
summary: 'Delete',
|
||||||
description: 'Delete application by UUID.',
|
description: 'Delete application by UUID.',
|
||||||
@@ -1551,7 +1731,7 @@ class ApplicationsController extends Controller
|
|||||||
'docker_compose_custom_build_command' => 'string|nullable',
|
'docker_compose_custom_build_command' => 'string|nullable',
|
||||||
'custom_nginx_configuration' => 'string|nullable',
|
'custom_nginx_configuration' => 'string|nullable',
|
||||||
];
|
];
|
||||||
$validationRules = array_merge($validationRules, sharedDataApplications());
|
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||||
$validator = customApiValidator($request->all(), $validationRules);
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
|
|
||||||
// Validate ports_exposes
|
// Validate ports_exposes
|
||||||
@@ -1606,7 +1786,8 @@ class ApplicationsController extends Controller
|
|||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
$domains = $request->domains;
|
$domains = $request->domains;
|
||||||
if ($request->has('domains') && $server->isProxyShouldRun()) {
|
$requestHasDomains = $request->has('domains');
|
||||||
|
if ($requestHasDomains && $server->isProxyShouldRun()) {
|
||||||
$uuid = $request->uuid;
|
$uuid = $request->uuid;
|
||||||
$fqdn = $request->domains;
|
$fqdn = $request->domains;
|
||||||
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
||||||
@@ -1668,7 +1849,10 @@ class ApplicationsController extends Controller
|
|||||||
removeUnnecessaryFieldsFromRequest($request);
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
|
|
||||||
$data = $request->all();
|
$data = $request->all();
|
||||||
|
if ($requestHasDomains && $server->isProxyShouldRun()) {
|
||||||
data_set($data, 'fqdn', $domains);
|
data_set($data, 'fqdn', $domains);
|
||||||
|
}
|
||||||
|
|
||||||
if ($dockerComposeDomainsJson->count() > 0) {
|
if ($dockerComposeDomainsJson->count() > 0) {
|
||||||
data_set($data, 'docker_compose_domains', json_encode($dockerComposeDomainsJson));
|
data_set($data, 'docker_compose_domains', json_encode($dockerComposeDomainsJson));
|
||||||
}
|
}
|
||||||
@@ -1893,8 +2077,9 @@ class ApplicationsController extends Controller
|
|||||||
$is_preview = $request->is_preview ?? false;
|
$is_preview = $request->is_preview ?? false;
|
||||||
$is_build_time = $request->is_build_time ?? false;
|
$is_build_time = $request->is_build_time ?? false;
|
||||||
$is_literal = $request->is_literal ?? false;
|
$is_literal = $request->is_literal ?? false;
|
||||||
|
$key = str($request->key)->trim()->replace(' ', '_')->value;
|
||||||
if ($is_preview) {
|
if ($is_preview) {
|
||||||
$env = $application->environment_variables_preview->where('key', $request->key)->first();
|
$env = $application->environment_variables_preview->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
$env->value = $request->value;
|
$env->value = $request->value;
|
||||||
if ($env->is_build_time != $is_build_time) {
|
if ($env->is_build_time != $is_build_time) {
|
||||||
@@ -1921,7 +2106,7 @@ class ApplicationsController extends Controller
|
|||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$env = $application->environment_variables->where('key', $request->key)->first();
|
$env = $application->environment_variables->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
$env->value = $request->value;
|
$env->value = $request->value;
|
||||||
if ($env->is_build_time != $is_build_time) {
|
if ($env->is_build_time != $is_build_time) {
|
||||||
@@ -2064,6 +2249,7 @@ class ApplicationsController extends Controller
|
|||||||
$bulk_data = collect($bulk_data)->map(function ($item) {
|
$bulk_data = collect($bulk_data)->map(function ($item) {
|
||||||
return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']);
|
return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']);
|
||||||
});
|
});
|
||||||
|
$returnedEnvs = collect();
|
||||||
foreach ($bulk_data as $item) {
|
foreach ($bulk_data as $item) {
|
||||||
$validator = customApiValidator($item, [
|
$validator = customApiValidator($item, [
|
||||||
'key' => 'string|required',
|
'key' => 'string|required',
|
||||||
@@ -2085,8 +2271,9 @@ class ApplicationsController extends Controller
|
|||||||
$is_literal = $item->get('is_literal') ?? false;
|
$is_literal = $item->get('is_literal') ?? false;
|
||||||
$is_multi_line = $item->get('is_multiline') ?? false;
|
$is_multi_line = $item->get('is_multiline') ?? false;
|
||||||
$is_shown_once = $item->get('is_shown_once') ?? false;
|
$is_shown_once = $item->get('is_shown_once') ?? false;
|
||||||
|
$key = str($item->get('key'))->trim()->replace(' ', '_')->value;
|
||||||
if ($is_preview) {
|
if ($is_preview) {
|
||||||
$env = $application->environment_variables_preview->where('key', $item->get('key'))->first();
|
$env = $application->environment_variables_preview->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
$env->value = $item->get('value');
|
$env->value = $item->get('value');
|
||||||
if ($env->is_build_time != $is_build_time) {
|
if ($env->is_build_time != $is_build_time) {
|
||||||
@@ -2111,10 +2298,12 @@ class ApplicationsController extends Controller
|
|||||||
'is_literal' => $is_literal,
|
'is_literal' => $is_literal,
|
||||||
'is_multiline' => $is_multi_line,
|
'is_multiline' => $is_multi_line,
|
||||||
'is_shown_once' => $is_shown_once,
|
'is_shown_once' => $is_shown_once,
|
||||||
|
'resourceable_type' => get_class($application),
|
||||||
|
'resourceable_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$env = $application->environment_variables->where('key', $item->get('key'))->first();
|
$env = $application->environment_variables->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
$env->value = $item->get('value');
|
$env->value = $item->get('value');
|
||||||
if ($env->is_build_time != $is_build_time) {
|
if ($env->is_build_time != $is_build_time) {
|
||||||
@@ -2139,12 +2328,15 @@ class ApplicationsController extends Controller
|
|||||||
'is_literal' => $is_literal,
|
'is_literal' => $is_literal,
|
||||||
'is_multiline' => $is_multi_line,
|
'is_multiline' => $is_multi_line,
|
||||||
'is_shown_once' => $is_shown_once,
|
'is_shown_once' => $is_shown_once,
|
||||||
|
'resourceable_type' => get_class($application),
|
||||||
|
'resourceable_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$returnedEnvs->push($this->removeSensitiveData($env));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
|
return response()->json($returnedEnvs)->setStatusCode(201);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OA\Post(
|
#[OA\Post(
|
||||||
@@ -2257,8 +2449,10 @@ class ApplicationsController extends Controller
|
|||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
$is_preview = $request->is_preview ?? false;
|
$is_preview = $request->is_preview ?? false;
|
||||||
|
$key = str($request->key)->trim()->replace(' ', '_')->value;
|
||||||
|
|
||||||
if ($is_preview) {
|
if ($is_preview) {
|
||||||
$env = $application->environment_variables_preview->where('key', $request->key)->first();
|
$env = $application->environment_variables_preview->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
||||||
@@ -2272,6 +2466,8 @@ class ApplicationsController extends Controller
|
|||||||
'is_literal' => $request->is_literal ?? false,
|
'is_literal' => $request->is_literal ?? false,
|
||||||
'is_multiline' => $request->is_multiline ?? false,
|
'is_multiline' => $request->is_multiline ?? false,
|
||||||
'is_shown_once' => $request->is_shown_once ?? false,
|
'is_shown_once' => $request->is_shown_once ?? false,
|
||||||
|
'resourceable_type' => get_class($application),
|
||||||
|
'resourceable_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -2279,7 +2475,7 @@ class ApplicationsController extends Controller
|
|||||||
])->setStatusCode(201);
|
])->setStatusCode(201);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$env = $application->environment_variables->where('key', $request->key)->first();
|
$env = $application->environment_variables->where('key', $key)->first();
|
||||||
if ($env) {
|
if ($env) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
||||||
@@ -2293,6 +2489,8 @@ class ApplicationsController extends Controller
|
|||||||
'is_literal' => $request->is_literal ?? false,
|
'is_literal' => $request->is_literal ?? false,
|
||||||
'is_multiline' => $request->is_multiline ?? false,
|
'is_multiline' => $request->is_multiline ?? false,
|
||||||
'is_shown_once' => $request->is_shown_once ?? false,
|
'is_shown_once' => $request->is_shown_once ?? false,
|
||||||
|
'resourceable_type' => get_class($application),
|
||||||
|
'resourceable_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -2380,7 +2578,10 @@ class ApplicationsController extends Controller
|
|||||||
'message' => 'Application not found.',
|
'message' => 'Application not found.',
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
$found_env = EnvironmentVariable::where('uuid', $request->env_uuid)->where('application_id', $application->id)->first();
|
$found_env = EnvironmentVariable::where('uuid', $request->env_uuid)
|
||||||
|
->where('resourceable_type', Application::class)
|
||||||
|
->where('resourceable_id', $application->id)
|
||||||
|
->first();
|
||||||
if (! $found_env) {
|
if (! $found_env) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Environment variable not found.',
|
'message' => 'Environment variable not found.',
|
||||||
|
@@ -523,11 +523,12 @@ class DatabasesController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['server_uuid', 'project_uuid', 'environment_name'],
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'],
|
||||||
properties: [
|
properties: [
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'postgres_user' => ['type' => 'string', 'description' => 'PostgreSQL user'],
|
'postgres_user' => ['type' => 'string', 'description' => 'PostgreSQL user'],
|
||||||
'postgres_password' => ['type' => 'string', 'description' => 'PostgreSQL password'],
|
'postgres_password' => ['type' => 'string', 'description' => 'PostgreSQL password'],
|
||||||
'postgres_db' => ['type' => 'string', 'description' => 'PostgreSQL database'],
|
'postgres_db' => ['type' => 'string', 'description' => 'PostgreSQL database'],
|
||||||
@@ -589,11 +590,12 @@ class DatabasesController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['server_uuid', 'project_uuid', 'environment_name'],
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'],
|
||||||
properties: [
|
properties: [
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
||||||
'clickhouse_admin_user' => ['type' => 'string', 'description' => 'Clickhouse admin user'],
|
'clickhouse_admin_user' => ['type' => 'string', 'description' => 'Clickhouse admin user'],
|
||||||
'clickhouse_admin_password' => ['type' => 'string', 'description' => 'Clickhouse admin password'],
|
'clickhouse_admin_password' => ['type' => 'string', 'description' => 'Clickhouse admin password'],
|
||||||
@@ -651,11 +653,12 @@ class DatabasesController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['server_uuid', 'project_uuid', 'environment_name'],
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'],
|
||||||
properties: [
|
properties: [
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
||||||
'dragonfly_password' => ['type' => 'string', 'description' => 'DragonFly password'],
|
'dragonfly_password' => ['type' => 'string', 'description' => 'DragonFly password'],
|
||||||
'name' => ['type' => 'string', 'description' => 'Name of the database'],
|
'name' => ['type' => 'string', 'description' => 'Name of the database'],
|
||||||
@@ -712,11 +715,12 @@ class DatabasesController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['server_uuid', 'project_uuid', 'environment_name'],
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'],
|
||||||
properties: [
|
properties: [
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
||||||
'redis_password' => ['type' => 'string', 'description' => 'Redis password'],
|
'redis_password' => ['type' => 'string', 'description' => 'Redis password'],
|
||||||
'redis_conf' => ['type' => 'string', 'description' => 'Redis conf'],
|
'redis_conf' => ['type' => 'string', 'description' => 'Redis conf'],
|
||||||
@@ -774,11 +778,12 @@ class DatabasesController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['server_uuid', 'project_uuid', 'environment_name'],
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'],
|
||||||
properties: [
|
properties: [
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
||||||
'keydb_password' => ['type' => 'string', 'description' => 'KeyDB password'],
|
'keydb_password' => ['type' => 'string', 'description' => 'KeyDB password'],
|
||||||
'keydb_conf' => ['type' => 'string', 'description' => 'KeyDB conf'],
|
'keydb_conf' => ['type' => 'string', 'description' => 'KeyDB conf'],
|
||||||
@@ -836,11 +841,12 @@ class DatabasesController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['server_uuid', 'project_uuid', 'environment_name'],
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'],
|
||||||
properties: [
|
properties: [
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
||||||
'mariadb_conf' => ['type' => 'string', 'description' => 'MariaDB conf'],
|
'mariadb_conf' => ['type' => 'string', 'description' => 'MariaDB conf'],
|
||||||
'mariadb_root_password' => ['type' => 'string', 'description' => 'MariaDB root password'],
|
'mariadb_root_password' => ['type' => 'string', 'description' => 'MariaDB root password'],
|
||||||
@@ -901,11 +907,12 @@ class DatabasesController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['server_uuid', 'project_uuid', 'environment_name'],
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'],
|
||||||
properties: [
|
properties: [
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
||||||
'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
|
||||||
'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'],
|
||||||
@@ -966,11 +973,12 @@ class DatabasesController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['server_uuid', 'project_uuid', 'environment_name'],
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'],
|
||||||
properties: [
|
properties: [
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
|
'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
|
||||||
'mongo_conf' => ['type' => 'string', 'description' => 'MongoDB conf'],
|
'mongo_conf' => ['type' => 'string', 'description' => 'MongoDB conf'],
|
||||||
'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'MongoDB initdb root username'],
|
'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'MongoDB initdb root username'],
|
||||||
@@ -1013,7 +1021,7 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
public function create_database(Request $request, NewDatabaseTypes $type)
|
public function create_database(Request $request, NewDatabaseTypes $type)
|
||||||
{
|
{
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||||
|
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
@@ -1039,6 +1047,11 @@ class DatabasesController extends Controller
|
|||||||
'errors' => $errors,
|
'errors' => $errors,
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
$environmentUuid = $request->environment_uuid;
|
||||||
|
$environmentName = $request->environment_name;
|
||||||
|
if (blank($environmentUuid) && blank($environmentName)) {
|
||||||
|
return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422);
|
||||||
|
}
|
||||||
$serverUuid = $request->server_uuid;
|
$serverUuid = $request->server_uuid;
|
||||||
$instantDeploy = $request->instant_deploy ?? false;
|
$instantDeploy = $request->instant_deploy ?? false;
|
||||||
if ($request->is_public && ! $request->public_port) {
|
if ($request->is_public && ! $request->public_port) {
|
||||||
@@ -1048,9 +1061,12 @@ class DatabasesController extends Controller
|
|||||||
if (! $project) {
|
if (! $project) {
|
||||||
return response()->json(['message' => 'Project not found.'], 404);
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
}
|
}
|
||||||
$environment = $project->environments()->where('name', $request->environment_name)->first();
|
$environment = $project->environments()->where('name', $environmentName)->first();
|
||||||
if (! $environment) {
|
if (! $environment) {
|
||||||
return response()->json(['message' => 'Environment not found.'], 404);
|
$environment = $project->environments()->where('uuid', $environmentUuid)->first();
|
||||||
|
}
|
||||||
|
if (! $environment) {
|
||||||
|
return response()->json(['message' => 'You need to provide a valid environment_name or environment_uuid.'], 422);
|
||||||
}
|
}
|
||||||
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
|
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
|
||||||
if (! $server) {
|
if (! $server) {
|
||||||
@@ -1074,7 +1090,8 @@ class DatabasesController extends Controller
|
|||||||
'description' => 'string|nullable',
|
'description' => 'string|nullable',
|
||||||
'image' => 'string',
|
'image' => 'string',
|
||||||
'project_uuid' => 'string|required',
|
'project_uuid' => 'string|required',
|
||||||
'environment_name' => 'string|required',
|
'environment_name' => 'string|nullable',
|
||||||
|
'environment_uuid' => 'string|nullable',
|
||||||
'server_uuid' => 'string|required',
|
'server_uuid' => 'string|required',
|
||||||
'destination_uuid' => 'string',
|
'destination_uuid' => 'string',
|
||||||
'is_public' => 'boolean',
|
'is_public' => 'boolean',
|
||||||
@@ -1105,7 +1122,7 @@ class DatabasesController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($type === NewDatabaseTypes::POSTGRESQL) {
|
if ($type === NewDatabaseTypes::POSTGRESQL) {
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'postgres_user' => 'string',
|
'postgres_user' => 'string',
|
||||||
'postgres_password' => 'string',
|
'postgres_password' => 'string',
|
||||||
@@ -1164,7 +1181,7 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||||
} elseif ($type === NewDatabaseTypes::MARIADB) {
|
} elseif ($type === NewDatabaseTypes::MARIADB) {
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'clickhouse_admin_user' => 'string',
|
'clickhouse_admin_user' => 'string',
|
||||||
'clickhouse_admin_password' => 'string',
|
'clickhouse_admin_password' => 'string',
|
||||||
@@ -1220,7 +1237,7 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||||
} elseif ($type === NewDatabaseTypes::MYSQL) {
|
} elseif ($type === NewDatabaseTypes::MYSQL) {
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'mysql_root_password' => 'string',
|
'mysql_root_password' => 'string',
|
||||||
'mysql_password' => 'string',
|
'mysql_password' => 'string',
|
||||||
@@ -1279,7 +1296,7 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||||
} elseif ($type === NewDatabaseTypes::REDIS) {
|
} elseif ($type === NewDatabaseTypes::REDIS) {
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'redis_password', 'redis_conf'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'redis_password', 'redis_conf'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'redis_password' => 'string',
|
'redis_password' => 'string',
|
||||||
'redis_conf' => 'string',
|
'redis_conf' => 'string',
|
||||||
@@ -1335,7 +1352,7 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||||
} elseif ($type === NewDatabaseTypes::DRAGONFLY) {
|
} elseif ($type === NewDatabaseTypes::DRAGONFLY) {
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'dragonfly_password'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'dragonfly_password'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'dragonfly_password' => 'string',
|
'dragonfly_password' => 'string',
|
||||||
]);
|
]);
|
||||||
@@ -1365,7 +1382,7 @@ class DatabasesController extends Controller
|
|||||||
'uuid' => $database->uuid,
|
'uuid' => $database->uuid,
|
||||||
]))->setStatusCode(201);
|
]))->setStatusCode(201);
|
||||||
} elseif ($type === NewDatabaseTypes::KEYDB) {
|
} elseif ($type === NewDatabaseTypes::KEYDB) {
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'keydb_password', 'keydb_conf'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'keydb_password', 'keydb_conf'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'keydb_password' => 'string',
|
'keydb_password' => 'string',
|
||||||
'keydb_conf' => 'string',
|
'keydb_conf' => 'string',
|
||||||
@@ -1421,7 +1438,7 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||||
} elseif ($type === NewDatabaseTypes::CLICKHOUSE) {
|
} elseif ($type === NewDatabaseTypes::CLICKHOUSE) {
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'clickhouse_admin_user', 'clickhouse_admin_password'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'clickhouse_admin_user', 'clickhouse_admin_password'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'clickhouse_admin_user' => 'string',
|
'clickhouse_admin_user' => 'string',
|
||||||
'clickhouse_admin_password' => 'string',
|
'clickhouse_admin_password' => 'string',
|
||||||
@@ -1457,7 +1474,7 @@ class DatabasesController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
return response()->json(serializeApiResponse($payload))->setStatusCode(201);
|
||||||
} elseif ($type === NewDatabaseTypes::MONGODB) {
|
} elseif ($type === NewDatabaseTypes::MONGODB) {
|
||||||
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database'];
|
$allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database'];
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'mongo_conf' => 'string',
|
'mongo_conf' => 'string',
|
||||||
'mongo_initdb_root_username' => 'string',
|
'mongo_initdb_root_username' => 'string',
|
||||||
|
@@ -90,11 +90,13 @@ class ProjectController extends Controller
|
|||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||||
if (! $project) {
|
if (! $project) {
|
||||||
return response()->json(['message' => 'Project not found.'], 404);
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$project->load(['environments']);
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
serializeApiResponse($project),
|
serializeApiResponse($project),
|
||||||
);
|
);
|
||||||
@@ -102,16 +104,16 @@ class ProjectController extends Controller
|
|||||||
|
|
||||||
#[OA\Get(
|
#[OA\Get(
|
||||||
summary: 'Environment',
|
summary: 'Environment',
|
||||||
description: 'Get environment by name.',
|
description: 'Get environment by name or UUID.',
|
||||||
path: '/projects/{uuid}/{environment_name}',
|
path: '/projects/{uuid}/{environment_name_or_uuid}',
|
||||||
operationId: 'get-environment-by-name',
|
operationId: 'get-environment-by-name-or-uuid',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Projects'],
|
tags: ['Projects'],
|
||||||
parameters: [
|
parameters: [
|
||||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')),
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')),
|
||||||
new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')),
|
new OA\Parameter(name: 'environment_name_or_uuid', in: 'path', required: true, description: 'Environment name or UUID', schema: new OA\Schema(type: 'string')),
|
||||||
],
|
],
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
@@ -141,14 +143,17 @@ class ProjectController extends Controller
|
|||||||
if (! $request->uuid) {
|
if (! $request->uuid) {
|
||||||
return response()->json(['message' => 'UUID is required.'], 422);
|
return response()->json(['message' => 'UUID is required.'], 422);
|
||||||
}
|
}
|
||||||
if (! $request->environment_name) {
|
if (! $request->environment_name_or_uuid) {
|
||||||
return response()->json(['message' => 'Environment name is required.'], 422);
|
return response()->json(['message' => 'Environment name or UUID is required.'], 422);
|
||||||
}
|
}
|
||||||
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
if (! $project) {
|
if (! $project) {
|
||||||
return response()->json(['message' => 'Project not found.'], 404);
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
}
|
}
|
||||||
$environment = $project->environments()->whereName($request->environment_name)->first();
|
$environment = $project->environments()->whereName($request->environment_name_or_uuid)->first();
|
||||||
|
if (! $environment) {
|
||||||
|
$environment = $project->environments()->whereUuid($request->environment_name_or_uuid)->first();
|
||||||
|
}
|
||||||
if (! $environment) {
|
if (! $environment) {
|
||||||
return response()->json(['message' => 'Environment not found.'], 404);
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
}
|
}
|
||||||
|
@@ -195,6 +195,31 @@ class SecurityController extends Controller
|
|||||||
if (! $request->description) {
|
if (! $request->description) {
|
||||||
$request->offsetSet('description', 'Created by Coolify via API');
|
$request->offsetSet('description', 'Created by Coolify via API');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$isPrivateKeyString = str_starts_with($request->private_key, '-----BEGIN');
|
||||||
|
if (! $isPrivateKeyString) {
|
||||||
|
try {
|
||||||
|
$base64PrivateKey = base64_decode($request->private_key);
|
||||||
|
$request->offsetSet('private_key', $base64PrivateKey);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Invalid private key.',
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$isPrivateKeyValid = PrivateKey::validatePrivateKey($request->private_key);
|
||||||
|
if (! $isPrivateKeyValid) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Invalid private key.',
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$fingerPrint = PrivateKey::generateFingerprint($request->private_key);
|
||||||
|
$isFingerPrintExists = PrivateKey::fingerprintExists($fingerPrint);
|
||||||
|
if ($isFingerPrintExists) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Private key already exists.',
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
$key = PrivateKey::create([
|
$key = PrivateKey::create([
|
||||||
'team_id' => $teamId,
|
'team_id' => $teamId,
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
|
@@ -530,11 +530,11 @@ class ServersController extends Controller
|
|||||||
'user' => $request->user,
|
'user' => $request->user,
|
||||||
'private_key_id' => $privateKey->id,
|
'private_key_id' => $privateKey->id,
|
||||||
'team_id' => $teamId,
|
'team_id' => $teamId,
|
||||||
'proxy' => [
|
|
||||||
'type' => $proxyType,
|
|
||||||
'status' => ProxyStatus::EXITED->value,
|
|
||||||
],
|
|
||||||
]);
|
]);
|
||||||
|
$server->proxy->set('type', $proxyType);
|
||||||
|
$server->proxy->set('status', ProxyStatus::EXITED->value);
|
||||||
|
$server->save();
|
||||||
|
|
||||||
$server->settings()->update([
|
$server->settings()->update([
|
||||||
'is_build_server' => $request->is_build_server,
|
'is_build_server' => $request->is_build_server,
|
||||||
]);
|
]);
|
||||||
@@ -742,6 +742,9 @@ class ServersController extends Controller
|
|||||||
if ($server->definedResources()->count() > 0) {
|
if ($server->definedResources()->count() > 0) {
|
||||||
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
|
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
|
||||||
}
|
}
|
||||||
|
if ($server->isLocalhost()) {
|
||||||
|
return response()->json(['message' => 'Local server cannot be deleted.'], 400);
|
||||||
|
}
|
||||||
$server->delete();
|
$server->delete();
|
||||||
DeleteServer::dispatch($server);
|
DeleteServer::dispatch($server);
|
||||||
|
|
||||||
|
@@ -20,6 +20,9 @@ class ServicesController extends Controller
|
|||||||
{
|
{
|
||||||
$service->makeHidden([
|
$service->makeHidden([
|
||||||
'id',
|
'id',
|
||||||
|
'resourceable',
|
||||||
|
'resourceable_id',
|
||||||
|
'resourceable_type',
|
||||||
]);
|
]);
|
||||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||||
$service->makeHidden([
|
$service->makeHidden([
|
||||||
@@ -99,7 +102,7 @@ class ServicesController extends Controller
|
|||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
schema: new OA\Schema(
|
schema: new OA\Schema(
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['server_uuid', 'project_uuid', 'environment_name', 'type'],
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid', 'type'],
|
||||||
properties: [
|
properties: [
|
||||||
'type' => [
|
'type' => [
|
||||||
'description' => 'The one-click service type',
|
'description' => 'The one-click service type',
|
||||||
@@ -196,7 +199,8 @@ class ServicesController extends Controller
|
|||||||
'name' => ['type' => 'string', 'maxLength' => 255, 'description' => 'Name of the service.'],
|
'name' => ['type' => 'string', 'maxLength' => 255, 'description' => 'Name of the service.'],
|
||||||
'description' => ['type' => 'string', 'nullable' => true, 'description' => 'Description of the service.'],
|
'description' => ['type' => 'string', 'nullable' => true, 'description' => 'Description of the service.'],
|
||||||
'project_uuid' => ['type' => 'string', 'description' => 'Project UUID.'],
|
'project_uuid' => ['type' => 'string', 'description' => 'Project UUID.'],
|
||||||
'environment_name' => ['type' => 'string', 'description' => 'Environment name.'],
|
'environment_name' => ['type' => 'string', 'description' => 'Environment name. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
|
'environment_uuid' => ['type' => 'string', 'description' => 'Environment UUID. You need to provide at least one of environment_name or environment_uuid.'],
|
||||||
'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'],
|
'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'],
|
||||||
'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'],
|
'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'],
|
||||||
'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'],
|
'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'],
|
||||||
@@ -233,7 +237,7 @@ class ServicesController extends Controller
|
|||||||
)]
|
)]
|
||||||
public function create_service(Request $request)
|
public function create_service(Request $request)
|
||||||
{
|
{
|
||||||
$allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy'];
|
$allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy'];
|
||||||
|
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
@@ -247,7 +251,8 @@ class ServicesController extends Controller
|
|||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
'type' => 'string|required',
|
'type' => 'string|required',
|
||||||
'project_uuid' => 'string|required',
|
'project_uuid' => 'string|required',
|
||||||
'environment_name' => 'string|required',
|
'environment_name' => 'string|nullable',
|
||||||
|
'environment_uuid' => 'string|nullable',
|
||||||
'server_uuid' => 'string|required',
|
'server_uuid' => 'string|required',
|
||||||
'destination_uuid' => 'string',
|
'destination_uuid' => 'string',
|
||||||
'name' => 'string|max:255',
|
'name' => 'string|max:255',
|
||||||
@@ -269,6 +274,11 @@ class ServicesController extends Controller
|
|||||||
'errors' => $errors,
|
'errors' => $errors,
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
$environmentUuid = $request->environment_uuid;
|
||||||
|
$environmentName = $request->environment_name;
|
||||||
|
if (blank($environmentUuid) && blank($environmentName)) {
|
||||||
|
return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422);
|
||||||
|
}
|
||||||
$serverUuid = $request->server_uuid;
|
$serverUuid = $request->server_uuid;
|
||||||
$instantDeploy = $request->instant_deploy ?? false;
|
$instantDeploy = $request->instant_deploy ?? false;
|
||||||
if ($request->is_public && ! $request->public_port) {
|
if ($request->is_public && ! $request->public_port) {
|
||||||
@@ -278,7 +288,10 @@ class ServicesController extends Controller
|
|||||||
if (! $project) {
|
if (! $project) {
|
||||||
return response()->json(['message' => 'Project not found.'], 404);
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
}
|
}
|
||||||
$environment = $project->environments()->where('name', $request->environment_name)->first();
|
$environment = $project->environments()->where('name', $environmentName)->first();
|
||||||
|
if (! $environment) {
|
||||||
|
$environment = $project->environments()->where('uuid', $environmentUuid)->first();
|
||||||
|
}
|
||||||
if (! $environment) {
|
if (! $environment) {
|
||||||
return response()->json(['message' => 'Environment not found.'], 404);
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
}
|
}
|
||||||
@@ -333,7 +346,8 @@ class ServicesController extends Controller
|
|||||||
EnvironmentVariable::create([
|
EnvironmentVariable::create([
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
'value' => $generatedValue,
|
'value' => $generatedValue,
|
||||||
'service_id' => $service->id,
|
'resourceable_id' => $service->id,
|
||||||
|
'resourceable_type' => $service->getMorphClass(),
|
||||||
'is_build_time' => false,
|
'is_build_time' => false,
|
||||||
'is_preview' => false,
|
'is_preview' => false,
|
||||||
]);
|
]);
|
||||||
@@ -345,7 +359,11 @@ class ServicesController extends Controller
|
|||||||
}
|
}
|
||||||
$domains = $service->applications()->get()->pluck('fqdn')->sort();
|
$domains = $service->applications()->get()->pluck('fqdn')->sort();
|
||||||
$domains = $domains->map(function ($domain) {
|
$domains = $domains->map(function ($domain) {
|
||||||
|
if (count(explode(':', $domain)) > 2) {
|
||||||
return str($domain)->beforeLast(':')->value();
|
return str($domain)->beforeLast(':')->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $domain;
|
||||||
});
|
});
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -673,7 +691,8 @@ class ServicesController extends Controller
|
|||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
$env = $service->environment_variables()->where('key', $request->key)->first();
|
$key = str($request->key)->trim()->replace(' ', '_')->value;
|
||||||
|
$env = $service->environment_variables()->where('key', $key)->first();
|
||||||
if (! $env) {
|
if (! $env) {
|
||||||
return response()->json(['message' => 'Environment variable not found.'], 404);
|
return response()->json(['message' => 'Environment variable not found.'], 404);
|
||||||
}
|
}
|
||||||
@@ -799,9 +818,9 @@ class ServicesController extends Controller
|
|||||||
'errors' => $validator->errors(),
|
'errors' => $validator->errors(),
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
$key = str($item['key'])->trim()->replace(' ', '_')->value;
|
||||||
$env = $service->environment_variables()->updateOrCreate(
|
$env = $service->environment_variables()->updateOrCreate(
|
||||||
['key' => $item['key']],
|
['key' => $key],
|
||||||
$item
|
$item
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -909,7 +928,8 @@ class ServicesController extends Controller
|
|||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
$existingEnv = $service->environment_variables()->where('key', $request->key)->first();
|
$key = str($request->key)->trim()->replace(' ', '_')->value;
|
||||||
|
$existingEnv = $service->environment_variables()->where('key', $key)->first();
|
||||||
if ($existingEnv) {
|
if ($existingEnv) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
'message' => 'Environment variable already exists. Use PATCH request to update it.',
|
||||||
@@ -995,7 +1015,8 @@ class ServicesController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$env = EnvironmentVariable::where('uuid', $request->env_uuid)
|
$env = EnvironmentVariable::where('uuid', $request->env_uuid)
|
||||||
->where('service_id', $service->id)
|
->where('resourceable_type', Service::class)
|
||||||
|
->where('resourceable_id', $service->id)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (! $env) {
|
if (! $env) {
|
||||||
|
@@ -54,7 +54,7 @@ class Controller extends BaseController
|
|||||||
'email' => Str::lower($arrayOfRequest['email']),
|
'email' => Str::lower($arrayOfRequest['email']),
|
||||||
]);
|
]);
|
||||||
$type = set_transanctional_email_settings();
|
$type = set_transanctional_email_settings();
|
||||||
if (! $type) {
|
if (blank($type)) {
|
||||||
return response()->json(['message' => 'Transactional emails are not active'], 400);
|
return response()->json(['message' => 'Transactional emails are not active'], 400);
|
||||||
}
|
}
|
||||||
$request->validate([Fortify::email() => 'required|email']);
|
$request->validate([Fortify::email() => 'required|email']);
|
||||||
|
@@ -37,7 +37,7 @@ class Bitbucket extends Controller
|
|||||||
$headers = $request->headers->all();
|
$headers = $request->headers->all();
|
||||||
$x_bitbucket_token = data_get($headers, 'x-hub-signature.0', '');
|
$x_bitbucket_token = data_get($headers, 'x-hub-signature.0', '');
|
||||||
$x_bitbucket_event = data_get($headers, 'x-event-key.0', '');
|
$x_bitbucket_event = data_get($headers, 'x-event-key.0', '');
|
||||||
$handled_events = collect(['repo:push', 'pullrequest:created', 'pullrequest:rejected', 'pullrequest:fulfilled']);
|
$handled_events = collect(['repo:push', 'pullrequest:updated', 'pullrequest:created', 'pullrequest:rejected', 'pullrequest:fulfilled']);
|
||||||
if (! $handled_events->contains($x_bitbucket_event)) {
|
if (! $handled_events->contains($x_bitbucket_event)) {
|
||||||
return response([
|
return response([
|
||||||
'status' => 'failed',
|
'status' => 'failed',
|
||||||
@@ -48,6 +48,7 @@ class Bitbucket extends Controller
|
|||||||
$branch = data_get($payload, 'push.changes.0.new.name');
|
$branch = data_get($payload, 'push.changes.0.new.name');
|
||||||
$full_name = data_get($payload, 'repository.full_name');
|
$full_name = data_get($payload, 'repository.full_name');
|
||||||
$commit = data_get($payload, 'push.changes.0.new.target.hash');
|
$commit = data_get($payload, 'push.changes.0.new.target.hash');
|
||||||
|
|
||||||
if (! $branch) {
|
if (! $branch) {
|
||||||
return response([
|
return response([
|
||||||
'status' => 'failed',
|
'status' => 'failed',
|
||||||
@@ -55,7 +56,7 @@ class Bitbucket extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') {
|
if ($x_bitbucket_event === 'pullrequest:updated' || $x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') {
|
||||||
$branch = data_get($payload, 'pullrequest.destination.branch.name');
|
$branch = data_get($payload, 'pullrequest.destination.branch.name');
|
||||||
$base_branch = data_get($payload, 'pullrequest.source.branch.name');
|
$base_branch = data_get($payload, 'pullrequest.source.branch.name');
|
||||||
$full_name = data_get($payload, 'repository.full_name');
|
$full_name = data_get($payload, 'repository.full_name');
|
||||||
@@ -119,7 +120,7 @@ class Bitbucket extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($x_bitbucket_event === 'pullrequest:created') {
|
if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:updated') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
|
@@ -152,7 +152,7 @@ class Gitea extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($x_gitea_event === 'pull_request') {
|
if ($x_gitea_event === 'pull_request') {
|
||||||
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
if ($action === 'opened' || $action === 'synchronized' || $action === 'reopened') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
@@ -202,7 +202,6 @@ class Gitea extends Controller
|
|||||||
if ($found) {
|
if ($found) {
|
||||||
$found->delete();
|
$found->delete();
|
||||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||||
// ray('Stopping container: ' . $container_name);
|
|
||||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
|
@@ -208,7 +208,6 @@ class Github extends Controller
|
|||||||
if ($found) {
|
if ($found) {
|
||||||
$found->delete();
|
$found->delete();
|
||||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||||
// ray('Stopping container: ' . $container_name);
|
|
||||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
|
@@ -227,7 +227,6 @@ class Gitlab extends Controller
|
|||||||
if ($found) {
|
if ($found) {
|
||||||
$found->delete();
|
$found->delete();
|
||||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||||
// ray('Stopping container: ' . $container_name);
|
|
||||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
|
@@ -18,6 +18,7 @@ use App\Models\SwarmDocker;
|
|||||||
use App\Notifications\Application\DeploymentFailed;
|
use App\Notifications\Application\DeploymentFailed;
|
||||||
use App\Notifications\Application\DeploymentSuccess;
|
use App\Notifications\Application\DeploymentSuccess;
|
||||||
use App\Traits\ExecuteRemoteCommand;
|
use App\Traits\ExecuteRemoteCommand;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
@@ -39,12 +40,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, ExecuteRemoteCommand, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, ExecuteRemoteCommand, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $tries = 1;
|
||||||
|
|
||||||
public $timeout = 3600;
|
public $timeout = 3600;
|
||||||
|
|
||||||
public static int $batch_counter = 0;
|
public static int $batch_counter = 0;
|
||||||
|
|
||||||
private int $application_deployment_queue_id;
|
|
||||||
|
|
||||||
private bool $newVersionIsHealthy = false;
|
private bool $newVersionIsHealthy = false;
|
||||||
|
|
||||||
private ApplicationDeploymentQueue $application_deployment_queue;
|
private ApplicationDeploymentQueue $application_deployment_queue;
|
||||||
@@ -126,6 +127,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private ?string $nixpacks_plan = null;
|
private ?string $nixpacks_plan = null;
|
||||||
|
|
||||||
|
private Collection $nixpacks_plan_json;
|
||||||
|
|
||||||
private ?string $nixpacks_type = null;
|
private ?string $nixpacks_type = null;
|
||||||
|
|
||||||
private string $dockerfile_location = '/Dockerfile';
|
private string $dockerfile_location = '/Dockerfile';
|
||||||
@@ -164,18 +167,23 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private bool $preserveRepository = false;
|
private bool $preserveRepository = false;
|
||||||
|
|
||||||
public $tries = 1;
|
public function tags()
|
||||||
|
{
|
||||||
|
// Do not remove this one, it needs to properly identify which worker is running the job
|
||||||
|
return ['App\Models\ApplicationDeploymentQueue:'.$this->application_deployment_queue_id];
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(int $application_deployment_queue_id)
|
public function __construct(public int $application_deployment_queue_id)
|
||||||
{
|
{
|
||||||
$this->onQueue('high');
|
$this->onQueue('high');
|
||||||
|
|
||||||
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
|
$this->application_deployment_queue = ApplicationDeploymentQueue::find($this->application_deployment_queue_id);
|
||||||
|
$this->nixpacks_plan_json = collect([]);
|
||||||
|
|
||||||
$this->application = Application::find($this->application_deployment_queue->application_id);
|
$this->application = Application::find($this->application_deployment_queue->application_id);
|
||||||
$this->build_pack = data_get($this->application, 'build_pack');
|
$this->build_pack = data_get($this->application, 'build_pack');
|
||||||
$this->build_args = collect([]);
|
$this->build_args = collect([]);
|
||||||
|
|
||||||
$this->application_deployment_queue_id = $application_deployment_queue_id;
|
|
||||||
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
|
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
|
||||||
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
|
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
|
||||||
$this->commit = $this->application_deployment_queue->commit;
|
$this->commit = $this->application_deployment_queue->commit;
|
||||||
@@ -233,15 +241,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tags(): array
|
|
||||||
{
|
|
||||||
return ['server:'.gethostname()];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
|
'horizon_job_worker' => gethostname(),
|
||||||
]);
|
]);
|
||||||
if ($this->server->isFunctional() === false) {
|
if ($this->server->isFunctional() === false) {
|
||||||
$this->application_deployment_queue->addLogEntry('Server is not functional.');
|
$this->application_deployment_queue->addLogEntry('Server is not functional.');
|
||||||
@@ -250,6 +254,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
// Make sure the private key is stored in the filesystem
|
||||||
|
$this->server->privateKey->storeInFileSystem();
|
||||||
|
|
||||||
// Generate custom host<->ip mapping
|
// Generate custom host<->ip mapping
|
||||||
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
||||||
|
|
||||||
@@ -313,6 +320,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->fail($e);
|
$this->fail($e);
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
|
$this->application_deployment_queue->update([
|
||||||
|
'finished_at' => Carbon::now()->toImmutable(),
|
||||||
|
]);
|
||||||
|
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->server = $this->build_server;
|
$this->server = $this->build_server;
|
||||||
} else {
|
} else {
|
||||||
@@ -916,8 +927,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
||||||
}
|
}
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_RESOURCE_UUID={$this->application->uuid}");
|
||||||
|
}
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
|
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,8 +989,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
||||||
}
|
}
|
||||||
|
if ($this->application->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_RESOURCE_UUID={$this->application->uuid}");
|
||||||
|
}
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
|
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1115,7 +1132,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
|
$nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
|
||||||
$nixpacks_php_fallback_path->value = '/index.php';
|
$nixpacks_php_fallback_path->value = '/index.php';
|
||||||
$nixpacks_php_fallback_path->is_build_time = false;
|
$nixpacks_php_fallback_path->is_build_time = false;
|
||||||
$nixpacks_php_fallback_path->application_id = $this->application->id;
|
$nixpacks_php_fallback_path->resourceable_id = $this->application->id;
|
||||||
|
$nixpacks_php_fallback_path->resourceable_type = 'App\Models\Application';
|
||||||
$nixpacks_php_fallback_path->save();
|
$nixpacks_php_fallback_path->save();
|
||||||
}
|
}
|
||||||
if (! $nixpacks_php_root_dir) {
|
if (! $nixpacks_php_root_dir) {
|
||||||
@@ -1123,7 +1141,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR';
|
$nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR';
|
||||||
$nixpacks_php_root_dir->value = '/app/public';
|
$nixpacks_php_root_dir->value = '/app/public';
|
||||||
$nixpacks_php_root_dir->is_build_time = false;
|
$nixpacks_php_root_dir->is_build_time = false;
|
||||||
$nixpacks_php_root_dir->application_id = $this->application->id;
|
$nixpacks_php_root_dir->resourceable_id = $this->application->id;
|
||||||
|
$nixpacks_php_root_dir->resourceable_type = 'App\Models\Application';
|
||||||
$nixpacks_php_root_dir->save();
|
$nixpacks_php_root_dir->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1136,7 +1155,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->application_deployment_queue->addLogEntry('Rolling update started.');
|
$this->application_deployment_queue->addLogEntry('Rolling update started.');
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, "docker stack deploy --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}"),
|
executeInDocker($this->deployment_uuid, "docker stack deploy --detach=true --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}"),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->application_deployment_queue->addLogEntry('Rolling update completed.');
|
$this->application_deployment_queue->addLogEntry('Rolling update completed.');
|
||||||
@@ -1189,7 +1208,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->application->custom_healthcheck_found) {
|
if ($this->application->custom_healthcheck_found) {
|
||||||
$this->application_deployment_queue->addLogEntry('Custom healthcheck found, skipping default healthcheck.');
|
$this->application_deployment_queue->addLogEntry('Custom healthcheck found, skipping default healthcheck.');
|
||||||
}
|
}
|
||||||
// ray('New container name: ', $this->container_name);
|
|
||||||
if ($this->container_name) {
|
if ($this->container_name) {
|
||||||
$counter = 1;
|
$counter = 1;
|
||||||
$this->application_deployment_queue->addLogEntry('Waiting for healthcheck to pass on the new container.');
|
$this->application_deployment_queue->addLogEntry('Waiting for healthcheck to pass on the new container.');
|
||||||
@@ -1392,7 +1410,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// ray('Deploying to additional destination: ', $server->name);
|
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
@@ -1405,7 +1422,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
'project_uuid' => data_get($this->application, 'environment.project.uuid'),
|
'project_uuid' => data_get($this->application, 'environment.project.uuid'),
|
||||||
'application_uuid' => data_get($this->application, 'uuid'),
|
'application_uuid' => data_get($this->application, 'uuid'),
|
||||||
'deployment_uuid' => $deployment_uuid,
|
'deployment_uuid' => $deployment_uuid,
|
||||||
'environment_name' => data_get($this->application, 'environment.name'),
|
'environment_uuid' => data_get($this->application, 'environment.uuid'),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1494,7 +1511,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
if ($this->saved_outputs->get('commit_message')) {
|
if ($this->saved_outputs->get('commit_message')) {
|
||||||
$commit_message = str($this->saved_outputs->get('commit_message'))->limit(47);
|
$commit_message = str($this->saved_outputs->get('commit_message'));
|
||||||
$this->application_deployment_queue->commit_message = $commit_message->value();
|
$this->application_deployment_queue->commit_message = $commit_message->value();
|
||||||
ApplicationDeploymentQueue::whereCommit($this->commit)->whereApplicationId($this->application->id)->update(
|
ApplicationDeploymentQueue::whereCommit($this->commit)->whereApplicationId($this->application->id)->update(
|
||||||
['commit_message' => $commit_message->value()]
|
['commit_message' => $commit_message->value()]
|
||||||
@@ -1545,7 +1562,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
// Do any modifications here
|
// Do any modifications here
|
||||||
$this->generate_env_variables();
|
$this->generate_env_variables();
|
||||||
$merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', [])));
|
$merged_envs = collect(data_get($parsed, 'variables', []))->merge($this->env_args);
|
||||||
$aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []);
|
$aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []);
|
||||||
if (count($aptPkgs) === 0) {
|
if (count($aptPkgs) === 0) {
|
||||||
$aptPkgs = ['curl', 'wget'];
|
$aptPkgs = ['curl', 'wget'];
|
||||||
@@ -1570,6 +1587,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->elixir_finetunes();
|
$this->elixir_finetunes();
|
||||||
}
|
}
|
||||||
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
|
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
|
||||||
|
$this->nixpacks_plan_json = collect($parsed);
|
||||||
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
|
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
|
||||||
if ($this->nixpacks_type === 'rust') {
|
if ($this->nixpacks_type === 'rust') {
|
||||||
// temporary: disable healthcheck for rust because the start phase does not have curl/wget
|
// temporary: disable healthcheck for rust because the start phase does not have curl/wget
|
||||||
@@ -1678,7 +1696,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
} else {
|
} else {
|
||||||
if (! $this->application->settings->is_container_label_readonly_enabled) {
|
if ($this->application->settings->is_container_label_readonly_enabled) {
|
||||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1690,7 +1708,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return escapeDollarSign($value);
|
return escapeDollarSign($value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
|
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->application->project()->name, $this->application->name, $this->application->environment->name, $this->pull_request_id))->toArray();
|
||||||
|
|
||||||
// Check for custom HEALTHCHECK
|
// Check for custom HEALTHCHECK
|
||||||
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
|
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
|
||||||
@@ -2005,6 +2023,8 @@ LABEL coolify.deploymentId={$this->deployment_uuid}
|
|||||||
COPY . .
|
COPY . .
|
||||||
RUN rm -f /usr/share/nginx/html/nginx.conf
|
RUN rm -f /usr/share/nginx/html/nginx.conf
|
||||||
RUN rm -f /usr/share/nginx/html/Dockerfile
|
RUN rm -f /usr/share/nginx/html/Dockerfile
|
||||||
|
RUN rm -f /usr/share/nginx/html/docker-compose.yaml
|
||||||
|
RUN rm -f /usr/share/nginx/html/.env
|
||||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||||
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
|
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
|
||||||
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
|
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
|
||||||
@@ -2266,7 +2286,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
} else {
|
} else {
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", 'hidden' => true],
|
["{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --pull always --build -d", 'hidden' => true],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
@@ -2279,18 +2299,18 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
|
|
||||||
private function generate_build_env_variables()
|
private function generate_build_env_variables()
|
||||||
{
|
{
|
||||||
$this->build_args = collect(["--build-arg SOURCE_COMMIT=\"{$this->commit}\""]);
|
if ($this->application->build_pack === 'nixpacks') {
|
||||||
if ($this->pull_request_id === 0) {
|
$variables = collect($this->nixpacks_plan_json->get('variables'));
|
||||||
foreach ($this->application->build_environment_variables as $env) {
|
|
||||||
$value = escapeshellarg($env->real_value);
|
|
||||||
$this->build_args->push("--build-arg {$env->key}={$value}");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
$this->generate_env_variables();
|
||||||
$value = escapeshellarg($env->real_value);
|
$variables = collect([])->merge($this->env_args);
|
||||||
$this->build_args->push("--build-arg {$env->key}={$value}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->build_args = $variables->map(function ($value, $key) {
|
||||||
|
$value = escapeshellarg($value);
|
||||||
|
|
||||||
|
return "--build-arg {$key}={$value}";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function add_build_env_variables_to_dockerfile()
|
private function add_build_env_variables_to_dockerfile()
|
||||||
@@ -2395,7 +2415,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
queue_next_deployment($this->application);
|
queue_next_deployment($this->application);
|
||||||
// If the deployment is cancelled by the user, don't update the status
|
// If the deployment is cancelled by the user, don't update the status
|
||||||
if (
|
if (
|
||||||
$this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value
|
$this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value &&
|
||||||
|
$this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value
|
||||||
) {
|
) {
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
|
@@ -24,7 +24,7 @@ class CheckAndStartSentinelJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$latestVersion = get_latest_sentinel_version();
|
$latestVersion = get_latest_sentinel_version();
|
||||||
|
|
||||||
// Check if sentinel is running
|
// Check if sentinel is running
|
||||||
$sentinelFound = instant_remote_process(['docker inspect coolify-sentinel'], $this->server, false);
|
$sentinelFound = instant_remote_process_with_timeout(['docker inspect coolify-sentinel'], $this->server, false, 10);
|
||||||
$sentinelFoundJson = json_decode($sentinelFound, true);
|
$sentinelFoundJson = json_decode($sentinelFound, true);
|
||||||
$sentinelStatus = data_get($sentinelFoundJson, '0.State.Status', 'exited');
|
$sentinelStatus = data_get($sentinelFoundJson, '0.State.Status', 'exited');
|
||||||
if ($sentinelStatus !== 'running') {
|
if ($sentinelStatus !== 'running') {
|
||||||
@@ -33,7 +33,7 @@ class CheckAndStartSentinelJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If sentinel is running, check if it needs an update
|
// If sentinel is running, check if it needs an update
|
||||||
$runningVersion = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false);
|
$runningVersion = instant_remote_process_with_timeout(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false);
|
||||||
if (empty($runningVersion)) {
|
if (empty($runningVersion)) {
|
||||||
$runningVersion = '0.0.0';
|
$runningVersion = '0.0.0';
|
||||||
}
|
}
|
||||||
|
@@ -20,11 +20,11 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$containers = instant_remote_process(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("ghcr.io/coollabsio/coolify-helper")))\''], $this->server, false);
|
$containers = instant_remote_process_with_timeout(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("ghcr.io/coollabsio/coolify-helper")))\''], $this->server, false);
|
||||||
$containerIds = collect(json_decode($containers))->pluck('ID');
|
$containerIds = collect(json_decode($containers))->pluck('ID');
|
||||||
if ($containerIds->count() > 0) {
|
if ($containerIds->count() > 0) {
|
||||||
foreach ($containerIds as $containerId) {
|
foreach ($containerIds as $containerId) {
|
||||||
instant_remote_process(['docker container rm -f '.$containerId], $this->server, false);
|
instant_remote_process_with_timeout(['docker container rm -f '.$containerId], $this->server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
@@ -32,8 +32,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
public ScheduledDatabaseBackup $backup;
|
|
||||||
|
|
||||||
public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database;
|
public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database;
|
||||||
|
|
||||||
public ?string $container_name = null;
|
public ?string $container_name = null;
|
||||||
@@ -58,10 +56,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public ?S3Storage $s3 = null;
|
public ?S3Storage $s3 = null;
|
||||||
|
|
||||||
public function __construct($backup)
|
public function __construct(public ScheduledDatabaseBackup $backup)
|
||||||
{
|
{
|
||||||
$this->onQueue('high');
|
$this->onQueue('high');
|
||||||
$this->backup = $backup;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
@@ -302,7 +299,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
throw new \Exception('Unsupported database type');
|
throw new \Exception('Unsupported database type');
|
||||||
}
|
}
|
||||||
$size = $this->calculate_size();
|
$size = $this->calculate_size();
|
||||||
$this->remove_old_backups();
|
|
||||||
if ($this->backup->save_s3) {
|
if ($this->backup->save_s3) {
|
||||||
$this->upload_to_s3();
|
$this->upload_to_s3();
|
||||||
}
|
}
|
||||||
@@ -326,12 +322,20 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database));
|
$this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($this->backup_log && $this->backup_log->status === 'success') {
|
||||||
|
removeOldBackups($this->backup);
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
if ($this->team) {
|
if ($this->team) {
|
||||||
BackupCreated::dispatch($this->team->id);
|
BackupCreated::dispatch($this->team->id);
|
||||||
}
|
}
|
||||||
|
if ($this->backup_log) {
|
||||||
|
$this->backup_log->update([
|
||||||
|
'finished_at' => Carbon::now()->toImmutable(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,9 +346,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($databaseWithCollections === 'all') {
|
if ($databaseWithCollections === 'all') {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if (str($this->database->image)->startsWith('mongo:4')) {
|
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --uri=\"$url\" --gzip --archive > $this->backup_location";
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=\"$url\" --gzip --archive > $this->backup_location";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (str($databaseWithCollections)->contains(':')) {
|
if (str($databaseWithCollections)->contains(':')) {
|
||||||
@@ -357,15 +361,15 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if ($collectionsToExclude->count() === 0) {
|
if ($collectionsToExclude->count() === 0) {
|
||||||
if (str($this->database->image)->startsWith('mongo:4')) {
|
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --uri=\"$url\" --gzip --archive > $this->backup_location";
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=\"$url\" --db $databaseName --gzip --archive > $this->backup_location";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (str($this->database->image)->startsWith('mongo:4')) {
|
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=\"$url\" --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,9 +415,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
try {
|
try {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if ($this->backup->dump_all) {
|
if ($this->backup->dump_all) {
|
||||||
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress | gzip > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mysqldump -u root -p\"{$this->database->mysql_root_password}\" --all-databases --single-transaction --quick --lock-tables=false --compress | gzip > $this->backup_location";
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mysqldump -u root -p\"{$this->database->mysql_root_password}\" $database > $this->backup_location";
|
||||||
}
|
}
|
||||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
$this->backup_output = trim($this->backup_output);
|
$this->backup_output = trim($this->backup_output);
|
||||||
@@ -431,9 +435,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
try {
|
try {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if ($this->backup->dump_all) {
|
if ($this->backup->dump_all) {
|
||||||
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p\"{$this->database->mariadb_root_password}\" --all-databases --single-transaction --quick --lock-tables=false --compress > $this->backup_location";
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p\"{$this->database->mariadb_root_password}\" $database > $this->backup_location";
|
||||||
}
|
}
|
||||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
$this->backup_output = trim($this->backup_output);
|
$this->backup_output = trim($this->backup_output);
|
||||||
@@ -460,19 +464,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server, false);
|
return instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function remove_old_backups(): void
|
|
||||||
{
|
|
||||||
if ($this->backup->number_of_backups_locally === 0) {
|
|
||||||
$deletable = $this->backup->executions()->where('status', 'success');
|
|
||||||
} else {
|
|
||||||
$deletable = $this->backup->executions()->where('status', 'success')->skip($this->backup->number_of_backups_locally - 1);
|
|
||||||
}
|
|
||||||
foreach ($deletable->get() as $execution) {
|
|
||||||
delete_backup_locally($execution->filename, $this->server);
|
|
||||||
$execution->delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function upload_to_s3(): void
|
private function upload_to_s3(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -504,12 +495,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
} else {
|
} else {
|
||||||
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
|
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
|
||||||
}
|
}
|
||||||
if ($this->s3->isHetzner()) {
|
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key \"$secret\"";
|
||||||
$endpointWithoutBucket = 'https://'.str($endpoint)->after('https://')->after('.')->value();
|
|
||||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc alias set --path=off --api=S3v4 temporary {$endpointWithoutBucket} $key $secret";
|
|
||||||
} else {
|
|
||||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
|
|
||||||
}
|
|
||||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
||||||
instant_remote_process($commands, $this->server);
|
instant_remote_process($commands, $this->server);
|
||||||
|
|
||||||
|
@@ -3,9 +3,12 @@
|
|||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Actions\Server\CleanupDocker;
|
use App\Actions\Server\CleanupDocker;
|
||||||
|
use App\Events\DockerCleanupDone;
|
||||||
|
use App\Models\DockerCleanupExecution;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Server\DockerCleanupFailed;
|
use App\Notifications\Server\DockerCleanupFailed;
|
||||||
use App\Notifications\Server\DockerCleanupSuccess;
|
use App\Notifications\Server\DockerCleanupSuccess;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@@ -24,6 +27,8 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public ?string $usageBefore = null;
|
public ?string $usageBefore = null;
|
||||||
|
|
||||||
|
public ?DockerCleanupExecution $execution_log = null;
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||||
@@ -38,37 +43,89 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->execution_log = DockerCleanupExecution::create([
|
||||||
|
'server_id' => $this->server->id,
|
||||||
|
]);
|
||||||
|
|
||||||
$this->usageBefore = $this->server->getDiskUsage();
|
$this->usageBefore = $this->server->getDiskUsage();
|
||||||
|
|
||||||
if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) {
|
if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) {
|
||||||
CleanupDocker::run(server: $this->server);
|
$cleanup_log = CleanupDocker::run(server: $this->server);
|
||||||
$usageAfter = $this->server->getDiskUsage();
|
$usageAfter = $this->server->getDiskUsage();
|
||||||
$this->server->team?->notify(new DockerCleanupSuccess($this->server, ($this->manualCleanup ? 'Manual' : 'Forced').' Docker cleanup job executed successfully. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.'));
|
$message = ($this->manualCleanup ? 'Manual' : 'Forced').' Docker cleanup job executed successfully. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.';
|
||||||
|
|
||||||
|
$this->execution_log->update([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $message,
|
||||||
|
'cleanup_log' => $cleanup_log,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->server->team?->notify(new DockerCleanupSuccess($this->server, $message));
|
||||||
|
event(new DockerCleanupDone($this->execution_log));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
||||||
CleanupDocker::run(server: $this->server);
|
$cleanup_log = CleanupDocker::run(server: $this->server);
|
||||||
$this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Docker cleanup job executed successfully, but no disk usage could be determined.'));
|
$message = 'Docker cleanup job executed successfully, but no disk usage could be determined.';
|
||||||
|
|
||||||
|
$this->execution_log->update([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $message,
|
||||||
|
'cleanup_log' => $cleanup_log,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->server->team?->notify(new DockerCleanupSuccess($this->server, $message));
|
||||||
|
event(new DockerCleanupDone($this->execution_log));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
|
if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
|
||||||
CleanupDocker::run(server: $this->server);
|
$cleanup_log = CleanupDocker::run(server: $this->server);
|
||||||
$usageAfter = $this->server->getDiskUsage();
|
$usageAfter = $this->server->getDiskUsage();
|
||||||
$diskSaved = $this->usageBefore - $usageAfter;
|
$diskSaved = $this->usageBefore - $usageAfter;
|
||||||
|
|
||||||
if ($diskSaved > 0) {
|
if ($diskSaved > 0) {
|
||||||
$this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Saved '.$diskSaved.'% disk space. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.'));
|
$message = 'Saved '.$diskSaved.'% disk space. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.';
|
||||||
} else {
|
} else {
|
||||||
$this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Docker cleanup job executed successfully, but no disk space was saved. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.'));
|
$message = 'Docker cleanup job executed successfully, but no disk space was saved. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->execution_log->update([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $message,
|
||||||
|
'cleanup_log' => $cleanup_log,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->server->team?->notify(new DockerCleanupSuccess($this->server, $message));
|
||||||
|
event(new DockerCleanupDone($this->execution_log));
|
||||||
} else {
|
} else {
|
||||||
$this->server->team?->notify(new DockerCleanupSuccess($this->server, 'No cleanup needed for '.$this->server->name));
|
$message = 'No cleanup needed for '.$this->server->name;
|
||||||
|
|
||||||
|
$this->execution_log->update([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $message,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->server->team?->notify(new DockerCleanupSuccess($this->server, $message));
|
||||||
|
event(new DockerCleanupDone($this->execution_log));
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
if ($this->execution_log) {
|
||||||
|
$this->execution_log->update([
|
||||||
|
'status' => 'failed',
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
event(new DockerCleanupDone($this->execution_log));
|
||||||
|
}
|
||||||
$this->server->team?->notify(new DockerCleanupFailed($this->server, 'Docker cleanup job failed with the following error: '.$e->getMessage()));
|
$this->server->team?->notify(new DockerCleanupFailed($this->server, 'Docker cleanup job failed with the following error: '.$e->getMessage()));
|
||||||
throw $e;
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
if ($this->execution_log) {
|
||||||
|
$this->execution_log->update([
|
||||||
|
'finished_at' => Carbon::now()->toImmutable(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,19 +27,28 @@ class GithubAppPermissionJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$github_access_token = generate_github_jwt_token($this->github_app);
|
$github_access_token = generateGithubJwt($this->github_app);
|
||||||
|
|
||||||
$response = Http::withHeaders([
|
$response = Http::withHeaders([
|
||||||
'Authorization' => "Bearer $github_access_token",
|
'Authorization' => "Bearer $github_access_token",
|
||||||
'Accept' => 'application/vnd.github+json',
|
'Accept' => 'application/vnd.github+json',
|
||||||
])->get("{$this->github_app->api_url}/app");
|
])->get("{$this->github_app->api_url}/app");
|
||||||
|
|
||||||
|
if (! $response->successful()) {
|
||||||
|
throw new \RuntimeException('Failed to fetch GitHub app permissions: '.$response->body());
|
||||||
|
}
|
||||||
|
|
||||||
$response = $response->json();
|
$response = $response->json();
|
||||||
$permissions = data_get($response, 'permissions');
|
$permissions = data_get($response, 'permissions');
|
||||||
|
|
||||||
$this->github_app->contents = data_get($permissions, 'contents');
|
$this->github_app->contents = data_get($permissions, 'contents');
|
||||||
$this->github_app->metadata = data_get($permissions, 'metadata');
|
$this->github_app->metadata = data_get($permissions, 'metadata');
|
||||||
$this->github_app->pull_requests = data_get($permissions, 'pull_requests');
|
$this->github_app->pull_requests = data_get($permissions, 'pull_requests');
|
||||||
$this->github_app->administration = data_get($permissions, 'administration');
|
$this->github_app->administration = data_get($permissions, 'administration');
|
||||||
|
|
||||||
$this->github_app->save();
|
$this->github_app->save();
|
||||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('GithubAppPermissionJob failed with: '.$e->getMessage());
|
send_internal_notification('GithubAppPermissionJob failed with: '.$e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
|
@@ -25,7 +25,7 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (isDev() || isCloud()) {
|
if (isDev()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||||
|
@@ -19,6 +19,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
@@ -68,6 +69,11 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public bool $foundLogDrainContainer = false;
|
public bool $foundLogDrainContainer = false;
|
||||||
|
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
public function backoff(): int
|
public function backoff(): int
|
||||||
{
|
{
|
||||||
return isDev() ? 1 : 3;
|
return isDev() ? 1 : 3;
|
||||||
|
@@ -11,6 +11,7 @@ use App\Models\Service;
|
|||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\ScheduledTask\TaskFailed;
|
use App\Notifications\ScheduledTask\TaskFailed;
|
||||||
use App\Notifications\ScheduledTask\TaskSuccess;
|
use App\Notifications\ScheduledTask\TaskSuccess;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
@@ -131,6 +132,11 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
ScheduledTaskDone::dispatch($this->team->id);
|
ScheduledTaskDone::dispatch($this->team->id);
|
||||||
|
if ($this->task_log) {
|
||||||
|
$this->task_log->update([
|
||||||
|
'finished_at' => Carbon::now()->toImmutable(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ class SendMessageToSlackJob implements ShouldQueue
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
Http::post($this->webhookUrl, [
|
Http::post($this->webhookUrl, [
|
||||||
|
'text' => $this->message->title,
|
||||||
'blocks' => [
|
'blocks' => [
|
||||||
[
|
[
|
||||||
'type' => 'section',
|
'type' => 'section',
|
||||||
|
@@ -73,19 +73,21 @@ class StripeProcessJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
$subscription = Subscription::where('team_id', $teamId)->first();
|
$subscription = Subscription::where('team_id', $teamId)->first();
|
||||||
if ($subscription) {
|
if ($subscription) {
|
||||||
send_internal_notification('Old subscription activated for team: '.$teamId);
|
// send_internal_notification('Old subscription activated for team: '.$teamId);
|
||||||
$subscription->update([
|
$subscription->update([
|
||||||
'stripe_subscription_id' => $subscriptionId,
|
'stripe_subscription_id' => $subscriptionId,
|
||||||
'stripe_customer_id' => $customerId,
|
'stripe_customer_id' => $customerId,
|
||||||
'stripe_invoice_paid' => true,
|
'stripe_invoice_paid' => true,
|
||||||
|
'stripe_past_due' => false,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
send_internal_notification('New subscription for team: '.$teamId);
|
// send_internal_notification('New subscription for team: '.$teamId);
|
||||||
Subscription::create([
|
Subscription::create([
|
||||||
'team_id' => $teamId,
|
'team_id' => $teamId,
|
||||||
'stripe_subscription_id' => $subscriptionId,
|
'stripe_subscription_id' => $subscriptionId,
|
||||||
'stripe_customer_id' => $customerId,
|
'stripe_customer_id' => $customerId,
|
||||||
'stripe_invoice_paid' => true,
|
'stripe_invoice_paid' => true,
|
||||||
|
'stripe_past_due' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -100,6 +102,7 @@ class StripeProcessJob implements ShouldQueue
|
|||||||
if ($subscription) {
|
if ($subscription) {
|
||||||
$subscription->update([
|
$subscription->update([
|
||||||
'stripe_invoice_paid' => true,
|
'stripe_invoice_paid' => true,
|
||||||
|
'stripe_past_due' => false,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
throw new \RuntimeException("No subscription found for customer: {$customerId}");
|
throw new \RuntimeException("No subscription found for customer: {$customerId}");
|
||||||
@@ -119,9 +122,7 @@ class StripeProcessJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
if (! $subscription->stripe_invoice_paid) {
|
if (! $subscription->stripe_invoice_paid) {
|
||||||
SubscriptionInvoiceFailedJob::dispatch($team);
|
SubscriptionInvoiceFailedJob::dispatch($team);
|
||||||
send_internal_notification('Invoice payment failed: '.$customerId);
|
// send_internal_notification('Invoice payment failed: '.$customerId);
|
||||||
} else {
|
|
||||||
send_internal_notification('Invoice payment failed but already paid: '.$customerId);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'payment_intent.payment_failed':
|
case 'payment_intent.payment_failed':
|
||||||
@@ -136,7 +137,7 @@ class StripeProcessJob implements ShouldQueue
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
send_internal_notification('Subscription payment failed for customer: '.$customerId);
|
// send_internal_notification('Subscription payment failed for customer: '.$customerId);
|
||||||
break;
|
break;
|
||||||
case 'customer.subscription.created':
|
case 'customer.subscription.created':
|
||||||
$customerId = data_get($data, 'customer');
|
$customerId = data_get($data, 'customer');
|
||||||
@@ -158,7 +159,7 @@ class StripeProcessJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
$subscription = Subscription::where('team_id', $teamId)->first();
|
$subscription = Subscription::where('team_id', $teamId)->first();
|
||||||
if ($subscription) {
|
if ($subscription) {
|
||||||
send_internal_notification("Subscription already exists for team: {$teamId}");
|
// send_internal_notification("Subscription already exists for team: {$teamId}");
|
||||||
throw new \RuntimeException("Subscription already exists for team: {$teamId}");
|
throw new \RuntimeException("Subscription already exists for team: {$teamId}");
|
||||||
} else {
|
} else {
|
||||||
Subscription::create([
|
Subscription::create([
|
||||||
@@ -182,7 +183,7 @@ class StripeProcessJob implements ShouldQueue
|
|||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
if (! $subscription) {
|
if (! $subscription) {
|
||||||
if ($status === 'incomplete_expired') {
|
if ($status === 'incomplete_expired') {
|
||||||
send_internal_notification('Subscription incomplete expired');
|
// send_internal_notification('Subscription incomplete expired');
|
||||||
throw new \RuntimeException('Subscription incomplete expired');
|
throw new \RuntimeException('Subscription incomplete expired');
|
||||||
}
|
}
|
||||||
if ($teamId) {
|
if ($teamId) {
|
||||||
@@ -224,9 +225,33 @@ class StripeProcessJob implements ShouldQueue
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($status === 'past_due') {
|
||||||
|
if ($subscription->stripe_subscription_id === $subscriptionId) {
|
||||||
|
$subscription->update([
|
||||||
|
'stripe_past_due' => true,
|
||||||
|
]);
|
||||||
|
send_internal_notification('Past Due: '.$customerId.'Subscription ID: '.$subscriptionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($status === 'unpaid') {
|
||||||
|
if ($subscription->stripe_subscription_id === $subscriptionId) {
|
||||||
|
$subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
]);
|
||||||
|
send_internal_notification('Unpaid: '.$customerId.'Subscription ID: '.$subscriptionId);
|
||||||
|
}
|
||||||
|
$team = data_get($subscription, 'team');
|
||||||
|
if ($team) {
|
||||||
|
$team->subscriptionEnded();
|
||||||
|
} else {
|
||||||
|
send_internal_notification('Subscription unpaid but no team found in Coolify for customer: '.$customerId);
|
||||||
|
throw new \RuntimeException("No team found in Coolify for customer: {$customerId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($status === 'active') {
|
if ($status === 'active') {
|
||||||
if ($subscription->stripe_subscription_id === $subscriptionId) {
|
if ($subscription->stripe_subscription_id === $subscriptionId) {
|
||||||
$subscription->update([
|
$subscription->update([
|
||||||
|
'stripe_past_due' => false,
|
||||||
'stripe_invoice_paid' => true,
|
'stripe_invoice_paid' => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
104
app/Jobs/VolumeCloneJob.php
Normal file
104
app/Jobs/VolumeCloneJob.php
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\LocalPersistentVolume;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class VolumeCloneJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected string $cloneDir = '/data/coolify/clone';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected string $sourceVolume,
|
||||||
|
protected string $targetVolume,
|
||||||
|
protected Server $sourceServer,
|
||||||
|
protected ?Server $targetServer,
|
||||||
|
protected LocalPersistentVolume $persistentVolume
|
||||||
|
) {
|
||||||
|
$this->onQueue('high');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (! $this->targetServer || $this->targetServer->id === $this->sourceServer->id) {
|
||||||
|
$this->cloneLocalVolume();
|
||||||
|
} else {
|
||||||
|
$this->cloneRemoteVolume();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error("Failed to copy volume data for {$this->sourceVolume}: ".$e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cloneLocalVolume()
|
||||||
|
{
|
||||||
|
instant_remote_process([
|
||||||
|
"docker volume create $this->targetVolume",
|
||||||
|
"docker run --rm -v $this->sourceVolume:/source -v $this->targetVolume:/target alpine sh -c 'cp -a /source/. /target/ && chown -R 1000:1000 /target'",
|
||||||
|
], $this->sourceServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cloneRemoteVolume()
|
||||||
|
{
|
||||||
|
$sourceCloneDir = "{$this->cloneDir}/{$this->sourceVolume}";
|
||||||
|
$targetCloneDir = "{$this->cloneDir}/{$this->targetVolume}";
|
||||||
|
|
||||||
|
try {
|
||||||
|
instant_remote_process([
|
||||||
|
"mkdir -p $sourceCloneDir",
|
||||||
|
"chmod 777 $sourceCloneDir",
|
||||||
|
"docker run --rm -v $this->sourceVolume:/source -v $sourceCloneDir:/clone alpine sh -c 'cd /source && tar czf /clone/volume-data.tar.gz .'",
|
||||||
|
], $this->sourceServer);
|
||||||
|
|
||||||
|
instant_remote_process([
|
||||||
|
"mkdir -p $targetCloneDir",
|
||||||
|
"chmod 777 $targetCloneDir",
|
||||||
|
], $this->targetServer);
|
||||||
|
|
||||||
|
instant_scp(
|
||||||
|
"$sourceCloneDir/volume-data.tar.gz",
|
||||||
|
"$targetCloneDir/volume-data.tar.gz",
|
||||||
|
$this->sourceServer,
|
||||||
|
$this->targetServer
|
||||||
|
);
|
||||||
|
|
||||||
|
instant_remote_process([
|
||||||
|
"docker volume create $this->targetVolume",
|
||||||
|
"docker run --rm -v $this->targetVolume:/target -v $targetCloneDir:/clone alpine sh -c 'cd /target && tar xzf /clone/volume-data.tar.gz && chown -R 1000:1000 /target'",
|
||||||
|
], $this->targetServer);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error("Failed to clone volume {$this->sourceVolume} to {$this->targetVolume}: ".$e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
instant_remote_process([
|
||||||
|
"rm -rf $sourceCloneDir",
|
||||||
|
], $this->sourceServer, false);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::warning('Failed to clean up source server clone directory: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($this->targetServer) {
|
||||||
|
instant_remote_process([
|
||||||
|
"rm -rf $targetCloneDir",
|
||||||
|
], $this->targetServer, false);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::warning('Failed to clean up target server clone directory: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -42,14 +42,8 @@ class ActivityMonitor extends Component
|
|||||||
public function polling()
|
public function polling()
|
||||||
{
|
{
|
||||||
$this->hydrateActivity();
|
$this->hydrateActivity();
|
||||||
// $this->setStatus(ProcessStatus::IN_PROGRESS);
|
|
||||||
$exit_code = data_get($this->activity, 'properties.exitCode');
|
$exit_code = data_get($this->activity, 'properties.exitCode');
|
||||||
if ($exit_code !== null) {
|
if ($exit_code !== null) {
|
||||||
// if ($exit_code === 0) {
|
|
||||||
// // $this->setStatus(ProcessStatus::FINISHED);
|
|
||||||
// } else {
|
|
||||||
// // $this->setStatus(ProcessStatus::ERROR);
|
|
||||||
// }
|
|
||||||
$this->isPollingActive = false;
|
$this->isPollingActive = false;
|
||||||
if ($exit_code === 0) {
|
if ($exit_code === 0) {
|
||||||
if ($this->eventToDispatch !== null) {
|
if ($this->eventToDispatch !== null) {
|
||||||
@@ -70,12 +64,4 @@ class ActivityMonitor extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// protected function setStatus($status)
|
|
||||||
// {
|
|
||||||
// $this->activity->properties = $this->activity->properties->merge([
|
|
||||||
// 'status' => $status,
|
|
||||||
// ]);
|
|
||||||
// $this->activity->save();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@@ -21,16 +21,28 @@ class Index extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (! isCloud()) {
|
if (! isCloud() && ! isDev()) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
if (Auth::id() !== 0 && ! session('impersonating')) {
|
||||||
if (Auth::id() !== 0) {
|
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$this->getSubscribers();
|
$this->getSubscribers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function back()
|
||||||
|
{
|
||||||
|
if (session('impersonating')) {
|
||||||
|
session()->forget('impersonating');
|
||||||
|
$user = User::find(0);
|
||||||
|
$team_to_switch_to = $user->teams->first();
|
||||||
|
Auth::login($user);
|
||||||
|
refreshSession($team_to_switch_to);
|
||||||
|
|
||||||
|
return redirect(request()->header('Referer'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function submitSearch()
|
public function submitSearch()
|
||||||
{
|
{
|
||||||
if ($this->search !== '') {
|
if ($this->search !== '') {
|
||||||
@@ -52,9 +64,10 @@ class Index extends Component
|
|||||||
if (Auth::id() !== 0) {
|
if (Auth::id() !== 0) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
session(['impersonating' => true]);
|
||||||
$user = User::find($user_id);
|
$user = User::find($user_id);
|
||||||
$team_to_switch_to = $user->teams->first();
|
$team_to_switch_to = $user->teams->first();
|
||||||
Cache::forget("team:{$user->id}");
|
// Cache::forget("team:{$user->id}");
|
||||||
Auth::login($user);
|
Auth::login($user);
|
||||||
refreshSession($team_to_switch_to);
|
refreshSession($team_to_switch_to);
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ use App\Models\Server;
|
|||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class Index extends Component
|
class Index extends Component
|
||||||
{
|
{
|
||||||
@@ -334,6 +335,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$this->createdProject = Project::create([
|
$this->createdProject = Project::create([
|
||||||
'name' => 'My first project',
|
'name' => 'My first project',
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
|
'uuid' => (string) new Cuid2,
|
||||||
]);
|
]);
|
||||||
$this->currentState = 'create-resource';
|
$this->currentState = 'create-resource';
|
||||||
}
|
}
|
||||||
@@ -346,7 +348,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
'project.resource.create',
|
'project.resource.create',
|
||||||
[
|
[
|
||||||
'project_uuid' => $this->createdProject->uuid,
|
'project_uuid' => $this->createdProject->uuid,
|
||||||
'environment_name' => 'production',
|
'environment_uuid' => $this->createdProject->environments->first()->uuid,
|
||||||
'server' => $this->createdServer->id,
|
'server' => $this->createdServer->id,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@@ -49,6 +49,11 @@ class Dashboard extends Component
|
|||||||
])->sortBy('id')->groupBy('server_name')->toArray();
|
])->sortBy('id')->groupBy('server_name')->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function navigateToProject($projectUuid)
|
||||||
|
{
|
||||||
|
return $this->redirect(collect($this->projects)->firstWhere('uuid', $projectUuid)->navigateTo(), true);
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.dashboard');
|
return view('livewire.dashboard');
|
||||||
|
@@ -35,10 +35,18 @@ class Docker extends Component
|
|||||||
$this->network = new Cuid2;
|
$this->network = new Cuid2;
|
||||||
$this->servers = Server::isUsable()->get();
|
$this->servers = Server::isUsable()->get();
|
||||||
if ($server_id) {
|
if ($server_id) {
|
||||||
$this->selectedServer = $this->servers->find($server_id) ?: $this->servers->first();
|
$foundServer = $this->servers->find($server_id) ?: $this->servers->first();
|
||||||
|
if (! $foundServer) {
|
||||||
|
throw new \Exception('Server not found.');
|
||||||
|
}
|
||||||
|
$this->selectedServer = $foundServer;
|
||||||
$this->serverId = $this->selectedServer->id;
|
$this->serverId = $this->selectedServer->id;
|
||||||
} else {
|
} else {
|
||||||
$this->selectedServer = $this->servers->first();
|
$foundServer = $this->servers->first();
|
||||||
|
if (! $foundServer) {
|
||||||
|
throw new \Exception('Server not found.');
|
||||||
|
}
|
||||||
|
$this->selectedServer = $foundServer;
|
||||||
$this->serverId = $this->selectedServer->id;
|
$this->serverId = $this->selectedServer->id;
|
||||||
}
|
}
|
||||||
$this->generateName();
|
$this->generateName();
|
||||||
@@ -83,9 +91,7 @@ class Docker extends Component
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->selectedServer);
|
$this->redirect(route('destination.show', $docker->uuid));
|
||||||
instant_remote_process($connectProxyToDockerNetworks, $this->selectedServer, false);
|
|
||||||
$this->dispatch('reloadWindow');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -36,7 +36,7 @@ class Help extends Component
|
|||||||
$type = set_transanctional_email_settings($settings);
|
$type = set_transanctional_email_settings($settings);
|
||||||
|
|
||||||
// Sending feedback through Cloud API
|
// Sending feedback through Cloud API
|
||||||
if ($type === false) {
|
if (blank($type)) {
|
||||||
$url = 'https://app.coolify.io/api/feedback';
|
$url = 'https://app.coolify.io/api/feedback';
|
||||||
Http::post($url, [
|
Http::post($url, [
|
||||||
'content' => 'User: `'.auth()->user()?->email.'` with subject: `'.$this->subject.'` has the following problem: `'.$this->description.'`',
|
'content' => 'User: `'.auth()->user()?->email.'` with subject: `'.$this->subject.'` has the following problem: `'.$this->description.'`',
|
||||||
|
@@ -5,6 +5,7 @@ namespace App\Livewire\Project;
|
|||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use Livewire\Attributes\Validate;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class AddEmpty extends Component
|
class AddEmpty extends Component
|
||||||
{
|
{
|
||||||
@@ -22,6 +23,7 @@ class AddEmpty extends Component
|
|||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'description' => $this->description,
|
'description' => $this->description,
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
|
'uuid' => (string) new Cuid2,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return redirect()->route('project.show', $project->uuid);
|
return redirect()->route('project.show', $project->uuid);
|
||||||
|
@@ -124,9 +124,20 @@ class Advanced extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resetDefaultLabels()
|
||||||
|
{
|
||||||
|
if ($this->application->settings->is_container_label_readonly_enabled === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||||
|
$this->application->custom_labels = base64_encode($customLabels);
|
||||||
|
$this->application->save();
|
||||||
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$reset = false;
|
||||||
if ($this->isLogDrainEnabled) {
|
if ($this->isLogDrainEnabled) {
|
||||||
if (! $this->application->destination->server->isLogDrainEnabled()) {
|
if (! $this->application->destination->server->isLogDrainEnabled()) {
|
||||||
$this->isLogDrainEnabled = false;
|
$this->isLogDrainEnabled = false;
|
||||||
@@ -140,7 +151,7 @@ class Advanced extends Component
|
|||||||
$this->application->isGzipEnabled() !== $this->isGzipEnabled ||
|
$this->application->isGzipEnabled() !== $this->isGzipEnabled ||
|
||||||
$this->application->isStripprefixEnabled() !== $this->isStripprefixEnabled
|
$this->application->isStripprefixEnabled() !== $this->isStripprefixEnabled
|
||||||
) {
|
) {
|
||||||
$this->dispatch('resetDefaultLabels', false);
|
$reset = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
@@ -149,6 +160,11 @@ class Advanced extends Component
|
|||||||
$this->application->parse();
|
$this->application->parse();
|
||||||
}
|
}
|
||||||
$this->syncData(true);
|
$this->syncData(true);
|
||||||
|
|
||||||
|
if ($reset) {
|
||||||
|
$this->resetDefaultLabels();
|
||||||
|
}
|
||||||
|
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
$this->dispatch('configurationChanged');
|
$this->dispatch('configurationChanged');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
@@ -3,43 +3,42 @@
|
|||||||
namespace App\Livewire\Project\Application;
|
namespace App\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Server;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Configuration extends Component
|
class Configuration extends Component
|
||||||
{
|
{
|
||||||
|
public $currentRoute;
|
||||||
|
|
||||||
public Application $application;
|
public Application $application;
|
||||||
|
|
||||||
|
public $project;
|
||||||
|
|
||||||
|
public $environment;
|
||||||
|
|
||||||
public $servers;
|
public $servers;
|
||||||
|
|
||||||
protected $listeners = ['buildPackUpdated' => '$refresh'];
|
protected $listeners = ['buildPackUpdated' => '$refresh'];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->currentRoute = request()->route()->getName();
|
||||||
$project = currentTeam()
|
$project = currentTeam()
|
||||||
->projects()
|
->projects()
|
||||||
->select('id', 'uuid', 'team_id')
|
->select('id', 'uuid', 'team_id')
|
||||||
->where('uuid', request()->route('project_uuid'))
|
->where('uuid', request()->route('project_uuid'))
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
$environment = $project->environments()
|
$environment = $project->environments()
|
||||||
->select('id', 'name', 'project_id')
|
->select('id', 'uuid', 'name', 'project_id')
|
||||||
->where('name', request()->route('environment_name'))
|
->where('uuid', request()->route('environment_uuid'))
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
$application = $environment->applications()
|
$application = $environment->applications()
|
||||||
->with(['destination'])
|
->with(['destination'])
|
||||||
->where('uuid', request()->route('application_uuid'))
|
->where('uuid', request()->route('application_uuid'))
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
|
$this->project = $project;
|
||||||
|
$this->environment = $environment;
|
||||||
$this->application = $application;
|
$this->application = $application;
|
||||||
if ($application->destination && $application->destination->server) {
|
|
||||||
$mainServer = $application->destination->server;
|
|
||||||
$this->servers = Server::ownedByCurrentTeam()
|
|
||||||
->select('id', 'name')
|
|
||||||
->where('id', '!=', $mainServer->id)
|
|
||||||
->get();
|
|
||||||
} else {
|
|
||||||
$this->servers = collect();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
@@ -18,7 +18,7 @@ class Index extends Component
|
|||||||
|
|
||||||
public int $skip = 0;
|
public int $skip = 0;
|
||||||
|
|
||||||
public int $default_take = 40;
|
public int $default_take = 10;
|
||||||
|
|
||||||
public bool $show_next = false;
|
public bool $show_next = false;
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ class Index extends Component
|
|||||||
if (! $project) {
|
if (! $project) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
$environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']);
|
||||||
if (! $environment) {
|
if (! $environment) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ class Index extends Component
|
|||||||
if (! $application) {
|
if (! $application) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 40);
|
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, $this->default_take);
|
||||||
$this->application = $application;
|
$this->application = $application;
|
||||||
$this->deployments = $deployments;
|
$this->deployments = $deployments;
|
||||||
$this->deployments_count = $count;
|
$this->deployments_count = $count;
|
||||||
|
@@ -14,6 +14,8 @@ class Show extends Component
|
|||||||
|
|
||||||
public string $deployment_uuid;
|
public string $deployment_uuid;
|
||||||
|
|
||||||
|
public string $horizon_job_status;
|
||||||
|
|
||||||
public $isKeepAliveOn = true;
|
public $isKeepAliveOn = true;
|
||||||
|
|
||||||
protected $listeners = ['refreshQueue'];
|
protected $listeners = ['refreshQueue'];
|
||||||
@@ -26,7 +28,7 @@ class Show extends Component
|
|||||||
if (! $project) {
|
if (! $project) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
$environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']);
|
||||||
if (! $environment) {
|
if (! $environment) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
@@ -34,25 +36,19 @@ class Show extends Component
|
|||||||
if (! $application) {
|
if (! $application) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
// $activity = Activity::where('properties->type_uuid', '=', $deploymentUuid)->first();
|
|
||||||
// if (!$activity) {
|
|
||||||
// return redirect()->route('project.application.deployment.index', [
|
|
||||||
// 'project_uuid' => $project->uuid,
|
|
||||||
// 'environment_name' => $environment->name,
|
|
||||||
// 'application_uuid' => $application->uuid,
|
|
||||||
// ]);
|
|
||||||
// }
|
|
||||||
$application_deployment_queue = ApplicationDeploymentQueue::where('deployment_uuid', $deploymentUuid)->first();
|
$application_deployment_queue = ApplicationDeploymentQueue::where('deployment_uuid', $deploymentUuid)->first();
|
||||||
if (! $application_deployment_queue) {
|
if (! $application_deployment_queue) {
|
||||||
return redirect()->route('project.application.deployment.index', [
|
return redirect()->route('project.application.deployment.index', [
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_uuid' => $environment->uuid,
|
||||||
'application_uuid' => $application->uuid,
|
'application_uuid' => $application->uuid,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$this->application = $application;
|
$this->application = $application;
|
||||||
$this->application_deployment_queue = $application_deployment_queue;
|
$this->application_deployment_queue = $application_deployment_queue;
|
||||||
|
$this->horizon_job_status = $this->application_deployment_queue->getHorizonJobStatus();
|
||||||
$this->deployment_uuid = $deploymentUuid;
|
$this->deployment_uuid = $deploymentUuid;
|
||||||
|
$this->isKeepAliveOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function refreshQueue()
|
public function refreshQueue()
|
||||||
@@ -60,13 +56,21 @@ class Show extends Component
|
|||||||
$this->application_deployment_queue->refresh();
|
$this->application_deployment_queue->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isKeepAliveOn()
|
||||||
|
{
|
||||||
|
if (data_get($this->application_deployment_queue, 'status') === 'finished' || data_get($this->application_deployment_queue, 'status') === 'failed') {
|
||||||
|
$this->isKeepAliveOn = false;
|
||||||
|
} else {
|
||||||
|
$this->isKeepAliveOn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function polling()
|
public function polling()
|
||||||
{
|
{
|
||||||
$this->dispatch('deploymentFinished');
|
$this->dispatch('deploymentFinished');
|
||||||
$this->application_deployment_queue->refresh();
|
$this->application_deployment_queue->refresh();
|
||||||
if (data_get($this->application_deployment_queue, 'status') === 'finished' || data_get($this->application_deployment_queue, 'status') === 'failed') {
|
$this->horizon_job_status = $this->application_deployment_queue->getHorizonJobStatus();
|
||||||
$this->isKeepAliveOn = false;
|
$this->isKeepAliveOn();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLogLinesProperty()
|
public function getLogLinesProperty()
|
||||||
|
@@ -23,7 +23,7 @@ class DeploymentNavbar extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->application = Application::find($this->application_deployment_queue->application_id);
|
$this->application = Application::ownedByCurrentTeam()->find($this->application_deployment_queue->application_id);
|
||||||
$this->server = $this->application->destination->server;
|
$this->server = $this->application->destination->server;
|
||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||||
}
|
}
|
||||||
@@ -53,13 +53,13 @@ class DeploymentNavbar extends Component
|
|||||||
public function cancel()
|
public function cancel()
|
||||||
{
|
{
|
||||||
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
|
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
|
||||||
$build_server_id = $this->application_deployment_queue->build_server_id;
|
$build_server_id = $this->application_deployment_queue->build_server_id ?? $this->application->destination->server_id;
|
||||||
$server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id;
|
$server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id;
|
||||||
try {
|
try {
|
||||||
if ($this->application->settings->is_build_server_enabled) {
|
if ($this->application->settings->is_build_server_enabled) {
|
||||||
$server = Server::find($build_server_id);
|
$server = Server::ownedByCurrentTeam()->find($build_server_id);
|
||||||
} else {
|
} else {
|
||||||
$server = Server::find($server_id);
|
$server = Server::ownedByCurrentTeam()->find($server_id);
|
||||||
}
|
}
|
||||||
if ($this->application_deployment_queue->logs) {
|
if ($this->application_deployment_queue->logs) {
|
||||||
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||||
|
@@ -155,7 +155,7 @@ class General extends Component
|
|||||||
$this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled;
|
$this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled;
|
||||||
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
||||||
$this->customLabels = $this->application->parseContainerLabels();
|
$this->customLabels = $this->application->parseContainerLabels();
|
||||||
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
|
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && $this->application->settings->is_container_label_readonly_enabled === true) {
|
||||||
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
@@ -189,6 +189,9 @@ class General extends Component
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($this->application->settings->is_container_label_readonly_enabled) {
|
||||||
|
$this->resetDefaultLabels(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadComposeFile($isInit = false)
|
public function loadComposeFile($isInit = false)
|
||||||
@@ -296,7 +299,7 @@ class General extends Component
|
|||||||
public function resetDefaultLabels($manualReset = false)
|
public function resetDefaultLabels($manualReset = false)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->application->settings->is_container_label_readonly_enabled && ! $manualReset) {
|
if (! $this->application->settings->is_container_label_readonly_enabled && ! $manualReset) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||||
@@ -326,10 +329,11 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
check_domain_usage(resource: $this->application);
|
check_domain_usage(resource: $this->application);
|
||||||
$this->application->fqdn = $domains->implode(',');
|
$this->application->fqdn = $domains->implode(',');
|
||||||
|
$this->resetDefaultLabels(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_redirect()
|
public function setRedirect()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
|
$has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
|
||||||
@@ -362,10 +366,10 @@ class General extends Component
|
|||||||
if ($warning) {
|
if ($warning) {
|
||||||
$this->dispatch('warning', __('warning.sslipdomain'));
|
$this->dispatch('warning', __('warning.sslipdomain'));
|
||||||
}
|
}
|
||||||
$this->resetDefaultLabels();
|
// $this->resetDefaultLabels();
|
||||||
|
|
||||||
if ($this->application->isDirty('redirect')) {
|
if ($this->application->isDirty('redirect')) {
|
||||||
$this->set_redirect();
|
$this->setRedirect();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->checkFqdns();
|
$this->checkFqdns();
|
||||||
@@ -444,6 +448,7 @@ class General extends Component
|
|||||||
{
|
{
|
||||||
$config = GenerateConfig::run($this->application, true);
|
$config = GenerateConfig::run($this->application, true);
|
||||||
$fileName = str($this->application->name)->slug()->append('_config.json');
|
$fileName = str($this->application->name)->slug()->append('_config.json');
|
||||||
|
dd($config);
|
||||||
|
|
||||||
return response()->streamDownload(function () use ($config) {
|
return response()->streamDownload(function () use ($config) {
|
||||||
echo $config;
|
echo $config;
|
||||||
|
@@ -38,7 +38,7 @@ class Heading extends Component
|
|||||||
{
|
{
|
||||||
$this->parameters = [
|
$this->parameters = [
|
||||||
'project_uuid' => $this->application->project()->uuid,
|
'project_uuid' => $this->application->project()->uuid,
|
||||||
'environment_name' => $this->application->environment->name,
|
'environment_uuid' => $this->application->environment->uuid,
|
||||||
'application_uuid' => $this->application->uuid,
|
'application_uuid' => $this->application->uuid,
|
||||||
];
|
];
|
||||||
$lastDeployment = $this->application->get_last_successful_deployment();
|
$lastDeployment = $this->application->get_last_successful_deployment();
|
||||||
@@ -90,12 +90,12 @@ class Heading extends Component
|
|||||||
force_rebuild: $force_rebuild,
|
force_rebuild: $force_rebuild,
|
||||||
);
|
);
|
||||||
|
|
||||||
return redirect()->route('project.application.deployment.show', [
|
return $this->redirectRoute('project.application.deployment.show', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
'application_uuid' => $this->parameters['application_uuid'],
|
'application_uuid' => $this->parameters['application_uuid'],
|
||||||
'deployment_uuid' => $this->deploymentUuid,
|
'deployment_uuid' => $this->deploymentUuid,
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
'environment_uuid' => $this->parameters['environment_uuid'],
|
||||||
]);
|
], navigate: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function setDeploymentUuid()
|
protected function setDeploymentUuid()
|
||||||
@@ -132,12 +132,12 @@ class Heading extends Component
|
|||||||
restart_only: true,
|
restart_only: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
return redirect()->route('project.application.deployment.show', [
|
return $this->redirectRoute('project.application.deployment.show', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
'application_uuid' => $this->parameters['application_uuid'],
|
'application_uuid' => $this->parameters['application_uuid'],
|
||||||
'deployment_uuid' => $this->deploymentUuid,
|
'deployment_uuid' => $this->deploymentUuid,
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
'environment_uuid' => $this->parameters['environment_uuid'],
|
||||||
]);
|
], navigate: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
@@ -171,7 +171,7 @@ class Previews extends Component
|
|||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
'application_uuid' => $this->parameters['application_uuid'],
|
'application_uuid' => $this->parameters['application_uuid'],
|
||||||
'deployment_uuid' => $this->deployment_uuid,
|
'deployment_uuid' => $this->deployment_uuid,
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
'environment_uuid' => $this->parameters['environment_uuid'],
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
@@ -37,7 +37,7 @@ class Rollback extends Component
|
|||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
'application_uuid' => $this->parameters['application_uuid'],
|
'application_uuid' => $this->parameters['application_uuid'],
|
||||||
'deployment_uuid' => $deployment_uuid,
|
'deployment_uuid' => $deployment_uuid,
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
'environment_uuid' => $this->parameters['environment_uuid'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project;
|
namespace App\Livewire\Project;
|
||||||
|
|
||||||
|
use App\Actions\Application\StopApplication;
|
||||||
|
use App\Actions\Database\StartDatabase;
|
||||||
|
use App\Actions\Database\StopDatabase;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Actions\Service\StopService;
|
||||||
|
use App\Jobs\VolumeCloneJob;
|
||||||
use App\Models\Environment;
|
use App\Models\Environment;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@@ -12,7 +18,7 @@ class CloneMe extends Component
|
|||||||
{
|
{
|
||||||
public string $project_uuid;
|
public string $project_uuid;
|
||||||
|
|
||||||
public string $environment_name;
|
public string $environment_uuid;
|
||||||
|
|
||||||
public int $project_id;
|
public int $project_id;
|
||||||
|
|
||||||
@@ -34,6 +40,8 @@ class CloneMe extends Component
|
|||||||
|
|
||||||
public string $newName = '';
|
public string $newName = '';
|
||||||
|
|
||||||
|
public bool $cloneVolumeData = false;
|
||||||
|
|
||||||
protected $messages = [
|
protected $messages = [
|
||||||
'selectedServer' => 'Please select a server.',
|
'selectedServer' => 'Please select a server.',
|
||||||
'selectedDestination' => 'Please select a server & destination.',
|
'selectedDestination' => 'Please select a server & destination.',
|
||||||
@@ -44,12 +52,17 @@ class CloneMe extends Component
|
|||||||
{
|
{
|
||||||
$this->project_uuid = $project_uuid;
|
$this->project_uuid = $project_uuid;
|
||||||
$this->project = Project::where('uuid', $project_uuid)->firstOrFail();
|
$this->project = Project::where('uuid', $project_uuid)->firstOrFail();
|
||||||
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
|
$this->environment = $this->project->environments->where('uuid', $this->environment_uuid)->first();
|
||||||
$this->project_id = $this->project->id;
|
$this->project_id = $this->project->id;
|
||||||
$this->servers = currentTeam()->servers;
|
$this->servers = currentTeam()->servers;
|
||||||
$this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug();
|
$this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toggleVolumeCloning(bool $value)
|
||||||
|
{
|
||||||
|
$this->cloneVolumeData = $value;
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.clone-me');
|
return view('livewire.project.clone-me');
|
||||||
@@ -89,6 +102,7 @@ class CloneMe extends Component
|
|||||||
if ($this->environment->name !== 'production') {
|
if ($this->environment->name !== 'production') {
|
||||||
$project->environments()->create([
|
$project->environments()->create([
|
||||||
'name' => $this->environment->name,
|
'name' => $this->environment->name,
|
||||||
|
'uuid' => (string) new Cuid2,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$environment = $project->environments->where('name', $this->environment->name)->first();
|
$environment = $project->environments->where('name', $this->environment->name)->first();
|
||||||
@@ -100,41 +114,160 @@ class CloneMe extends Component
|
|||||||
$project = $this->project;
|
$project = $this->project;
|
||||||
$environment = $this->project->environments()->create([
|
$environment = $this->project->environments()->create([
|
||||||
'name' => $this->newName,
|
'name' => $this->newName,
|
||||||
|
'uuid' => (string) new Cuid2,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$applications = $this->environment->applications;
|
$applications = $this->environment->applications;
|
||||||
$databases = $this->environment->databases();
|
$databases = $this->environment->databases();
|
||||||
$services = $this->environment->services;
|
$services = $this->environment->services;
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
|
$applicationSettings = $application->settings;
|
||||||
|
|
||||||
$uuid = (string) new Cuid2;
|
$uuid = (string) new Cuid2;
|
||||||
$newApplication = $application->replicate()->fill([
|
$url = $application->fqdn;
|
||||||
|
if ($this->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) {
|
||||||
|
$url = generateFqdn($this->server, $uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newApplication = $application->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
'additional_servers_count',
|
||||||
|
'additional_networks_count',
|
||||||
|
])->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'fqdn' => generateFqdn($this->server, $uuid),
|
'fqdn' => $url,
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
// This is not correct, but we need to set it to something
|
|
||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
$newApplication->save();
|
$newApplication->save();
|
||||||
$environmentVaribles = $application->environment_variables()->get();
|
|
||||||
foreach ($environmentVaribles as $environmentVarible) {
|
if ($newApplication->destination->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) {
|
||||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
$customLabels = str(implode('|coolify|', generateLabelsApplication($newApplication)))->replace('|coolify|', "\n");
|
||||||
|
$newApplication->custom_labels = base64_encode($customLabels);
|
||||||
|
$newApplication->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$newApplication->settings()->delete();
|
||||||
|
if ($applicationSettings) {
|
||||||
|
$newApplicationSettings = $applicationSettings->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
'application_id' => $newApplication->id,
|
'application_id' => $newApplication->id,
|
||||||
]);
|
]);
|
||||||
$newEnvironmentVariable->save();
|
$newApplicationSettings->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$tags = $application->tags;
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$newApplication->tags()->attach($tag->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledTasks = $application->scheduled_tasks()->get();
|
||||||
|
foreach ($scheduledTasks as $task) {
|
||||||
|
$newTask = $task->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'uuid' => (string) new Cuid2,
|
||||||
|
'application_id' => $newApplication->id,
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
$newTask->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$applicationPreviews = $application->previews()->get();
|
||||||
|
foreach ($applicationPreviews as $preview) {
|
||||||
|
$newPreview = $preview->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'application_id' => $newApplication->id,
|
||||||
|
'status' => 'exited',
|
||||||
|
]);
|
||||||
|
$newPreview->save();
|
||||||
|
}
|
||||||
|
|
||||||
$persistentVolumes = $application->persistentStorages()->get();
|
$persistentVolumes = $application->persistentStorages()->get();
|
||||||
foreach ($persistentVolumes as $volume) {
|
foreach ($persistentVolumes as $volume) {
|
||||||
$newPersistentVolume = $volume->replicate()->fill([
|
$newName = '';
|
||||||
'name' => $newApplication->uuid.'-'.str($volume->name)->afterLast('-'),
|
if (str_starts_with($volume->name, $application->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($application->uuid, $newApplication->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $newApplication->uuid.'-'.$volume->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
'resource_id' => $newApplication->id,
|
'resource_id' => $newApplication->id,
|
||||||
]);
|
]);
|
||||||
$newPersistentVolume->save();
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopApplication::dispatch($application, false, false);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $application->destination->server;
|
||||||
|
$targetServer = $newApplication->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
queue_application_deployment(
|
||||||
|
deployment_uuid: (string) new Cuid2,
|
||||||
|
application: $application,
|
||||||
|
server: $sourceServer,
|
||||||
|
destination: $application->destination,
|
||||||
|
no_questions_asked: true
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileStorages = $application->fileStorages()->get();
|
||||||
|
foreach ($fileStorages as $storage) {
|
||||||
|
$newStorage = $storage->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resource_id' => $newApplication->id,
|
||||||
|
]);
|
||||||
|
$newStorage->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$environmentVaribles = $application->environment_variables()->get();
|
||||||
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
|
$newEnvironmentVariable = $environmentVarible->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resourceable_id' => $newApplication->id,
|
||||||
|
]);
|
||||||
|
$newEnvironmentVariable->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($databases as $database) {
|
foreach ($databases as $database) {
|
||||||
$uuid = (string) new Cuid2;
|
$uuid = (string) new Cuid2;
|
||||||
$newDatabase = $database->replicate()->fill([
|
$newDatabase = $database->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
'started_at' => null,
|
'started_at' => null,
|
||||||
@@ -142,51 +275,294 @@ class CloneMe extends Component
|
|||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
$newDatabase->save();
|
$newDatabase->save();
|
||||||
|
|
||||||
|
$tags = $database->tags;
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$newDatabase->tags()->attach($tag->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newDatabase->persistentStorages()->delete();
|
||||||
|
$persistentVolumes = $database->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$originalName = $volume->name;
|
||||||
|
$newName = '';
|
||||||
|
|
||||||
|
if (str_starts_with($originalName, 'postgres-data-')) {
|
||||||
|
$newName = 'postgres-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'mysql-data-')) {
|
||||||
|
$newName = 'mysql-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'redis-data-')) {
|
||||||
|
$newName = 'redis-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'clickhouse-data-')) {
|
||||||
|
$newName = 'clickhouse-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'mariadb-data-')) {
|
||||||
|
$newName = 'mariadb-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'mongodb-data-')) {
|
||||||
|
$newName = 'mongodb-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'keydb-data-')) {
|
||||||
|
$newName = 'keydb-data-'.$newDatabase->uuid;
|
||||||
|
} elseif (str_starts_with($originalName, 'dragonfly-data-')) {
|
||||||
|
$newName = 'dragonfly-data-'.$newDatabase->uuid;
|
||||||
|
} else {
|
||||||
|
if (str_starts_with($volume->name, $database->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($database->uuid, $newDatabase->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $newDatabase->uuid.'-'.$volume->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
|
'resource_id' => $newDatabase->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopDatabase::dispatch($database);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $database->destination->server;
|
||||||
|
$targetServer = $newDatabase->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
StartDatabase::dispatch($database);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileStorages = $database->fileStorages()->get();
|
||||||
|
foreach ($fileStorages as $storage) {
|
||||||
|
$newStorage = $storage->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resource_id' => $newDatabase->id,
|
||||||
|
]);
|
||||||
|
$newStorage->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledBackups = $database->scheduledBackups()->get();
|
||||||
|
foreach ($scheduledBackups as $backup) {
|
||||||
|
$uuid = (string) new Cuid2;
|
||||||
|
$newBackup = $backup->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'database_id' => $newDatabase->id,
|
||||||
|
'database_type' => $newDatabase->getMorphClass(),
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
$newBackup->save();
|
||||||
|
}
|
||||||
|
|
||||||
$environmentVaribles = $database->environment_variables()->get();
|
$environmentVaribles = $database->environment_variables()->get();
|
||||||
foreach ($environmentVaribles as $environmentVarible) {
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
$payload = [];
|
$payload = [];
|
||||||
if ($database->type() === 'standalone-postgresql') {
|
$payload['resourceable_id'] = $newDatabase->id;
|
||||||
$payload['standalone_postgresql_id'] = $newDatabase->id;
|
$payload['resourceable_type'] = $newDatabase->getMorphClass();
|
||||||
} elseif ($database->type() === 'standalone-redis') {
|
$newEnvironmentVariable = $environmentVarible->replicate([
|
||||||
$payload['standalone_redis_id'] = $newDatabase->id;
|
'id',
|
||||||
} elseif ($database->type() === 'standalone-mongodb') {
|
'created_at',
|
||||||
$payload['standalone_mongodb_id'] = $newDatabase->id;
|
'updated_at',
|
||||||
} elseif ($database->type() === 'standalone-mysql') {
|
])->fill($payload);
|
||||||
$payload['standalone_mysql_id'] = $newDatabase->id;
|
|
||||||
} elseif ($database->type() === 'standalone-mariadb') {
|
|
||||||
$payload['standalone_mariadb_id'] = $newDatabase->id;
|
|
||||||
}
|
|
||||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
|
|
||||||
$newEnvironmentVariable->save();
|
$newEnvironmentVariable->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($services as $service) {
|
foreach ($services as $service) {
|
||||||
$uuid = (string) new Cuid2;
|
$uuid = (string) new Cuid2;
|
||||||
$newService = $service->replicate()->fill([
|
$newService = $service->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
$newService->save();
|
$newService->save();
|
||||||
|
|
||||||
|
$tags = $service->tags;
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$newService->tags()->attach($tag->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledTasks = $service->scheduled_tasks()->get();
|
||||||
|
foreach ($scheduledTasks as $task) {
|
||||||
|
$newTask = $task->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'uuid' => (string) new Cuid2,
|
||||||
|
'service_id' => $newService->id,
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
$newTask->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$environmentVariables = $service->environment_variables()->get();
|
||||||
|
foreach ($environmentVariables as $environmentVariable) {
|
||||||
|
$newEnvironmentVariable = $environmentVariable->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resourceable_id' => $newService->id,
|
||||||
|
'resourceable_type' => $newService->getMorphClass(),
|
||||||
|
]);
|
||||||
|
$newEnvironmentVariable->save();
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($newService->applications() as $application) {
|
foreach ($newService->applications() as $application) {
|
||||||
$application->update([
|
$application->update([
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$persistentVolumes = $application->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$newName = '';
|
||||||
|
if (str_starts_with($volume->name, $application->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($application->uuid, $application->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $application->uuid.'-'.$volume->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
|
'resource_id' => $application->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopService::dispatch($application, false, false);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $application->service->destination->server;
|
||||||
|
$targetServer = $newService->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
StartService::dispatch($application);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileStorages = $application->fileStorages()->get();
|
||||||
|
foreach ($fileStorages as $storage) {
|
||||||
|
$newStorage = $storage->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resource_id' => $application->id,
|
||||||
|
]);
|
||||||
|
$newStorage->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($newService->databases() as $database) {
|
foreach ($newService->databases() as $database) {
|
||||||
$database->update([
|
$database->update([
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$persistentVolumes = $database->persistentStorages()->get();
|
||||||
|
foreach ($persistentVolumes as $volume) {
|
||||||
|
$newName = '';
|
||||||
|
if (str_starts_with($volume->name, $database->uuid)) {
|
||||||
|
$newName = str($volume->name)->replace($database->uuid, $database->uuid);
|
||||||
|
} else {
|
||||||
|
$newName = $database->uuid.'-'.$volume->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$newPersistentVolume = $volume->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'name' => $newName,
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
]);
|
||||||
|
$newPersistentVolume->save();
|
||||||
|
|
||||||
|
if ($this->cloneVolumeData) {
|
||||||
|
try {
|
||||||
|
StopService::dispatch($database->service, false, false);
|
||||||
|
$sourceVolume = $volume->name;
|
||||||
|
$targetVolume = $newPersistentVolume->name;
|
||||||
|
$sourceServer = $database->service->destination->server;
|
||||||
|
$targetServer = $newService->destination->server;
|
||||||
|
|
||||||
|
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
|
||||||
|
|
||||||
|
StartService::dispatch($database->service);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileStorages = $database->fileStorages()->get();
|
||||||
|
foreach ($fileStorages as $storage) {
|
||||||
|
$newStorage = $storage->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'resource_id' => $database->id,
|
||||||
|
]);
|
||||||
|
$newStorage->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheduledBackups = $database->scheduledBackups()->get();
|
||||||
|
foreach ($scheduledBackups as $backup) {
|
||||||
|
$uuid = (string) new Cuid2;
|
||||||
|
$newBackup = $backup->replicate([
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
])->fill([
|
||||||
|
'uuid' => $uuid,
|
||||||
|
'database_id' => $database->id,
|
||||||
|
'database_type' => $database->getMorphClass(),
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
$newBackup->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$newService->parse();
|
$newService->parse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
handleError($e, $this);
|
||||||
|
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
if (! isset($e)) {
|
||||||
return redirect()->route('project.resource.index', [
|
return redirect()->route('project.resource.index', [
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_uuid' => $environment->uuid,
|
||||||
]);
|
]);
|
||||||
} catch (\Exception $e) {
|
}
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@ class Execution extends Component
|
|||||||
if (! $project) {
|
if (! $project) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
$environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']);
|
||||||
if (! $environment) {
|
if (! $environment) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,7 @@ class Index extends Component
|
|||||||
if (! $project) {
|
if (! $project) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
$environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']);
|
||||||
if (! $environment) {
|
if (! $environment) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ class Index extends Component
|
|||||||
) {
|
) {
|
||||||
return redirect()->route('project.database.configuration', [
|
return redirect()->route('project.database.configuration', [
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_uuid' => $environment->uuid,
|
||||||
'database_uuid' => $database->uuid,
|
'database_uuid' => $database->uuid,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@@ -40,8 +40,26 @@ class BackupEdit extends Component
|
|||||||
#[Validate(['required', 'string'])]
|
#[Validate(['required', 'string'])]
|
||||||
public string $frequency = '';
|
public string $frequency = '';
|
||||||
|
|
||||||
#[Validate(['required', 'integer', 'min:1'])]
|
#[Validate(['string'])]
|
||||||
public int $numberOfBackupsLocally = 1;
|
public string $timezone = '';
|
||||||
|
|
||||||
|
#[Validate(['required', 'integer'])]
|
||||||
|
public int $databaseBackupRetentionAmountLocally = 0;
|
||||||
|
|
||||||
|
#[Validate(['required', 'integer'])]
|
||||||
|
public ?int $databaseBackupRetentionDaysLocally = 0;
|
||||||
|
|
||||||
|
#[Validate(['required', 'numeric', 'min:0'])]
|
||||||
|
public ?float $databaseBackupRetentionMaxStorageLocally = 0;
|
||||||
|
|
||||||
|
#[Validate(['required', 'integer'])]
|
||||||
|
public ?int $databaseBackupRetentionAmountS3 = 0;
|
||||||
|
|
||||||
|
#[Validate(['required', 'integer'])]
|
||||||
|
public ?int $databaseBackupRetentionDaysS3 = 0;
|
||||||
|
|
||||||
|
#[Validate(['required', 'numeric', 'min:0'])]
|
||||||
|
public ?float $databaseBackupRetentionMaxStorageS3 = 0;
|
||||||
|
|
||||||
#[Validate(['required', 'boolean'])]
|
#[Validate(['required', 'boolean'])]
|
||||||
public bool $saveS3 = false;
|
public bool $saveS3 = false;
|
||||||
@@ -68,19 +86,30 @@ class BackupEdit extends Component
|
|||||||
public function syncData(bool $toModel = false)
|
public function syncData(bool $toModel = false)
|
||||||
{
|
{
|
||||||
if ($toModel) {
|
if ($toModel) {
|
||||||
$this->customValidate();
|
|
||||||
$this->backup->enabled = $this->backupEnabled;
|
$this->backup->enabled = $this->backupEnabled;
|
||||||
$this->backup->frequency = $this->frequency;
|
$this->backup->frequency = $this->frequency;
|
||||||
$this->backup->number_of_backups_locally = $this->numberOfBackupsLocally;
|
$this->backup->database_backup_retention_amount_locally = $this->databaseBackupRetentionAmountLocally;
|
||||||
|
$this->backup->database_backup_retention_days_locally = $this->databaseBackupRetentionDaysLocally;
|
||||||
|
$this->backup->database_backup_retention_max_storage_locally = $this->databaseBackupRetentionMaxStorageLocally;
|
||||||
|
$this->backup->database_backup_retention_amount_s3 = $this->databaseBackupRetentionAmountS3;
|
||||||
|
$this->backup->database_backup_retention_days_s3 = $this->databaseBackupRetentionDaysS3;
|
||||||
|
$this->backup->database_backup_retention_max_storage_s3 = $this->databaseBackupRetentionMaxStorageS3;
|
||||||
$this->backup->save_s3 = $this->saveS3;
|
$this->backup->save_s3 = $this->saveS3;
|
||||||
$this->backup->s3_storage_id = $this->s3StorageId;
|
$this->backup->s3_storage_id = $this->s3StorageId;
|
||||||
$this->backup->databases_to_backup = $this->databasesToBackup;
|
$this->backup->databases_to_backup = $this->databasesToBackup;
|
||||||
$this->backup->dump_all = $this->dumpAll;
|
$this->backup->dump_all = $this->dumpAll;
|
||||||
|
$this->customValidate();
|
||||||
$this->backup->save();
|
$this->backup->save();
|
||||||
} else {
|
} else {
|
||||||
$this->backupEnabled = $this->backup->enabled;
|
$this->backupEnabled = $this->backup->enabled;
|
||||||
$this->frequency = $this->backup->frequency;
|
$this->frequency = $this->backup->frequency;
|
||||||
$this->numberOfBackupsLocally = $this->backup->number_of_backups_locally;
|
$this->timezone = data_get($this->backup->server(), 'settings.server_timezone', 'Instance timezone');
|
||||||
|
$this->databaseBackupRetentionAmountLocally = $this->backup->database_backup_retention_amount_locally;
|
||||||
|
$this->databaseBackupRetentionDaysLocally = $this->backup->database_backup_retention_days_locally;
|
||||||
|
$this->databaseBackupRetentionMaxStorageLocally = $this->backup->database_backup_retention_max_storage_locally;
|
||||||
|
$this->databaseBackupRetentionAmountS3 = $this->backup->database_backup_retention_amount_s3;
|
||||||
|
$this->databaseBackupRetentionDaysS3 = $this->backup->database_backup_retention_days_s3;
|
||||||
|
$this->databaseBackupRetentionMaxStorageS3 = $this->backup->database_backup_retention_max_storage_s3;
|
||||||
$this->saveS3 = $this->backup->save_s3;
|
$this->saveS3 = $this->backup->save_s3;
|
||||||
$this->s3StorageId = $this->backup->s3_storage_id;
|
$this->s3StorageId = $this->backup->s3_storage_id;
|
||||||
$this->databasesToBackup = $this->backup->databases_to_backup;
|
$this->databasesToBackup = $this->backup->databases_to_backup;
|
||||||
@@ -99,11 +128,29 @@ class BackupEdit extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($this->delete_associated_backups_locally) {
|
$server = null;
|
||||||
$this->deleteAssociatedBackupsLocally();
|
if ($this->backup->database instanceof \App\Models\ServiceDatabase) {
|
||||||
|
$server = $this->backup->database->service->destination->server;
|
||||||
|
} elseif ($this->backup->database->destination && $this->backup->database->destination->server) {
|
||||||
|
$server = $this->backup->database->destination->server;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filenames = $this->backup->executions()
|
||||||
|
->whereNotNull('filename')
|
||||||
|
->where('filename', '!=', '')
|
||||||
|
->where('scheduled_database_backup_id', $this->backup->id)
|
||||||
|
->pluck('filename')
|
||||||
|
->filter()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
if (! empty($filenames)) {
|
||||||
|
if ($this->delete_associated_backups_locally && $server) {
|
||||||
|
deleteBackupsLocally($filenames, $server);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->delete_associated_backups_s3 && $this->backup->s3) {
|
||||||
|
deleteBackupsS3($filenames, $this->backup->s3);
|
||||||
}
|
}
|
||||||
if ($this->delete_associated_backups_s3) {
|
|
||||||
$this->deleteAssociatedBackupsS3();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->backup->delete();
|
$this->backup->delete();
|
||||||
@@ -119,7 +166,9 @@ class BackupEdit extends Component
|
|||||||
} else {
|
} else {
|
||||||
return redirect()->route('project.database.backup.index', $this->parameters);
|
return redirect()->route('project.database.backup.index', $this->parameters);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Exception $e) {
|
||||||
|
$this->dispatch('error', 'Failed to delete backup: '.$e->getMessage());
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,63 +205,12 @@ class BackupEdit extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteAssociatedBackupsLocally()
|
|
||||||
{
|
|
||||||
$executions = $this->backup->executions;
|
|
||||||
$backupFolder = null;
|
|
||||||
|
|
||||||
foreach ($executions as $execution) {
|
|
||||||
if ($this->backup->database->getMorphClass() === \App\Models\ServiceDatabase::class) {
|
|
||||||
$server = $this->backup->database->service->destination->server;
|
|
||||||
} else {
|
|
||||||
$server = $this->backup->database->destination->server;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $backupFolder) {
|
|
||||||
$backupFolder = dirname($execution->filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete_backup_locally($execution->filename, $server);
|
|
||||||
$execution->delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str($backupFolder)->isNotEmpty()) {
|
|
||||||
$this->deleteEmptyBackupFolder($backupFolder, $server);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function deleteAssociatedBackupsS3()
|
|
||||||
{
|
|
||||||
//Add function to delete backups from S3
|
|
||||||
}
|
|
||||||
|
|
||||||
private function deleteAssociatedBackupsSftp()
|
|
||||||
{
|
|
||||||
//Add function to delete backups from SFTP
|
|
||||||
}
|
|
||||||
|
|
||||||
private function deleteEmptyBackupFolder($folderPath, $server)
|
|
||||||
{
|
|
||||||
$checkEmpty = instant_remote_process(["[ -z \"$(ls -A '$folderPath')\" ] && echo 'empty' || echo 'not empty'"], $server);
|
|
||||||
|
|
||||||
if (trim($checkEmpty) === 'empty') {
|
|
||||||
instant_remote_process(["rmdir '$folderPath'"], $server);
|
|
||||||
|
|
||||||
$parentFolder = dirname($folderPath);
|
|
||||||
$checkParentEmpty = instant_remote_process(["[ -z \"$(ls -A '$parentFolder')\" ] && echo 'empty' || echo 'not empty'"], $server);
|
|
||||||
|
|
||||||
if (trim($checkParentEmpty) === 'empty') {
|
|
||||||
instant_remote_process(["rmdir '$parentFolder'"], $server);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.database.backup-edit', [
|
return view('livewire.project.database.backup-edit', [
|
||||||
'checkboxes' => [
|
'checkboxes' => [
|
||||||
['id' => 'delete_associated_backups_locally', 'label' => __('database.delete_backups_locally')],
|
['id' => 'delete_associated_backups_locally', 'label' => __('database.delete_backups_locally')],
|
||||||
// ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected S3 Storage.']
|
['id' => 'delete_associated_backups_s3', 'label' => 'All backups will be permanently deleted (associated with this backup job) from the selected S3 Storage.'],
|
||||||
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected SFTP Storage.']
|
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected SFTP Storage.']
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@@ -18,9 +18,9 @@ class BackupExecutions extends Component
|
|||||||
|
|
||||||
public $setDeletableBackup;
|
public $setDeletableBackup;
|
||||||
|
|
||||||
public $delete_backup_s3 = true;
|
public $delete_backup_s3 = false;
|
||||||
|
|
||||||
public $delete_backup_sftp = true;
|
public $delete_backup_sftp = false;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
@@ -57,23 +57,25 @@ class BackupExecutions extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === \App\Models\ServiceDatabase::class) {
|
$server = $execution->scheduledDatabaseBackup->database->getMorphClass() === \App\Models\ServiceDatabase::class
|
||||||
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->service->destination->server);
|
? $execution->scheduledDatabaseBackup->database->service->destination->server
|
||||||
} else {
|
: $execution->scheduledDatabaseBackup->database->destination->server;
|
||||||
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->delete_backup_s3) {
|
try {
|
||||||
// Add logic to delete from S3
|
if ($execution->filename) {
|
||||||
}
|
deleteBackupsLocally($execution->filename, $server);
|
||||||
|
|
||||||
if ($this->delete_backup_sftp) {
|
if ($this->delete_backup_s3 && $execution->scheduledDatabaseBackup->s3) {
|
||||||
// Add logic to delete from SFTP
|
deleteBackupsS3($execution->filename, $execution->scheduledDatabaseBackup->s3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$execution->delete();
|
$execution->delete();
|
||||||
$this->dispatch('success', 'Backup deleted.');
|
$this->dispatch('success', 'Backup deleted.');
|
||||||
$this->refreshBackupExecutions();
|
$this->refreshBackupExecutions();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->dispatch('error', 'Failed to delete backup: '.$e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function download_file($exeuctionId)
|
public function download_file($exeuctionId)
|
||||||
@@ -83,8 +85,10 @@ class BackupExecutions extends Component
|
|||||||
|
|
||||||
public function refreshBackupExecutions(): void
|
public function refreshBackupExecutions(): void
|
||||||
{
|
{
|
||||||
if ($this->backup) {
|
if ($this->backup && $this->backup->exists) {
|
||||||
$this->executions = $this->backup->executions()->get();
|
$this->executions = $this->backup->executions()->get()->toArray();
|
||||||
|
} else {
|
||||||
|
$this->executions = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,35 +117,12 @@ class BackupExecutions extends Component
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getServerTimezone()
|
|
||||||
{
|
|
||||||
$server = $this->server();
|
|
||||||
if (! $server) {
|
|
||||||
return 'UTC';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $server->settings->server_timezone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function formatDateInServerTimezone($date)
|
|
||||||
{
|
|
||||||
$serverTimezone = $this->getServerTimezone();
|
|
||||||
$dateObj = new \DateTime($date);
|
|
||||||
try {
|
|
||||||
$dateObj->setTimezone(new \DateTimeZone($serverTimezone));
|
|
||||||
} catch (\Exception) {
|
|
||||||
$dateObj->setTimezone(new \DateTimeZone('UTC'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $dateObj->format('Y-m-d H:i:s T');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.database.backup-executions', [
|
return view('livewire.project.database.backup-executions', [
|
||||||
'checkboxes' => [
|
'checkboxes' => [
|
||||||
['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently form S3 Storage'],
|
['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently form S3 Storage'],
|
||||||
['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently form SFTP Storage'],
|
// ['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently form SFTP Storage'],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@@ -9,11 +9,9 @@ class BackupNow extends Component
|
|||||||
{
|
{
|
||||||
public $backup;
|
public $backup;
|
||||||
|
|
||||||
public function backup_now()
|
public function backupNow()
|
||||||
{
|
{
|
||||||
dispatch(new DatabaseBackupJob(
|
DatabaseBackupJob::dispatch($this->backup);
|
||||||
backup: $this->backup
|
|
||||||
));
|
|
||||||
$this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
|
$this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,23 +6,34 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Configuration extends Component
|
class Configuration extends Component
|
||||||
{
|
{
|
||||||
|
public $currentRoute;
|
||||||
|
|
||||||
public $database;
|
public $database;
|
||||||
|
|
||||||
|
public $project;
|
||||||
|
|
||||||
|
public $environment;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
$this->currentRoute = request()->route()->getName();
|
||||||
if (! $project) {
|
|
||||||
return redirect()->route('dashboard');
|
$project = currentTeam()
|
||||||
}
|
->projects()
|
||||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
->select('id', 'uuid', 'team_id')
|
||||||
if (! $environment) {
|
->where('uuid', request()->route('project_uuid'))
|
||||||
return redirect()->route('dashboard');
|
->firstOrFail();
|
||||||
}
|
$environment = $project->environments()
|
||||||
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
->select('id', 'name', 'project_id', 'uuid')
|
||||||
if (! $database) {
|
->where('uuid', request()->route('environment_uuid'))
|
||||||
return redirect()->route('dashboard');
|
->firstOrFail();
|
||||||
}
|
$database = $environment->databases()
|
||||||
|
->where('uuid', request()->route('database_uuid'))
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
$this->project = $project;
|
||||||
|
$this->environment = $environment;
|
||||||
if (str($this->database->status)->startsWith('running') && is_null($this->database->config_hash)) {
|
if (str($this->database->status)->startsWith('running') && is_null($this->database->config_hash)) {
|
||||||
$this->database->isConfigurationChanged(true);
|
$this->database->isConfigurationChanged(true);
|
||||||
$this->dispatch('configurationChanged');
|
$this->dispatch('configurationChanged');
|
||||||
|
@@ -43,8 +43,10 @@ class Heading extends Component
|
|||||||
|
|
||||||
public function check_status($showNotification = false)
|
public function check_status($showNotification = false)
|
||||||
{
|
{
|
||||||
GetContainersStatus::run($this->database->destination->server);
|
if ($this->database->destination->server->isFunctional()) {
|
||||||
$this->database->refresh();
|
GetContainersStatus::dispatch($this->database->destination->server);
|
||||||
|
}
|
||||||
|
|
||||||
if ($showNotification) {
|
if ($showNotification) {
|
||||||
$this->dispatch('success', 'Database status updated.');
|
$this->dispatch('success', 'Database status updated.');
|
||||||
}
|
}
|
||||||
|
@@ -37,6 +37,12 @@ class Import extends Component
|
|||||||
|
|
||||||
public array $importCommands = [];
|
public array $importCommands = [];
|
||||||
|
|
||||||
|
public bool $dumpAll = false;
|
||||||
|
|
||||||
|
public string $restoreCommandText = '';
|
||||||
|
|
||||||
|
public string $customLocation = '';
|
||||||
|
|
||||||
public string $postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB';
|
public string $postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB';
|
||||||
|
|
||||||
public string $mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE';
|
public string $mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE';
|
||||||
@@ -56,10 +62,62 @@ class Import extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
if (isDev()) {
|
||||||
|
$this->customLocation = '/data/coolify/pg-dump-all-1736245863.gz';
|
||||||
|
}
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
$this->getContainers();
|
$this->getContainers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updatedDumpAll($value)
|
||||||
|
{
|
||||||
|
switch ($this->resource->getMorphClass()) {
|
||||||
|
case \App\Models\StandaloneMariadb::class:
|
||||||
|
if ($value === true) {
|
||||||
|
$this->mariadbRestoreCommand = <<<'EOD'
|
||||||
|
for pid in $(mariadb -u root -p$MARIADB_ROOT_PASSWORD -N -e "SELECT id FROM information_schema.processlist WHERE user != 'root';"); do
|
||||||
|
mariadb -u root -p$MARIADB_ROOT_PASSWORD -e "KILL $pid" 2>/dev/null || true
|
||||||
|
done && \
|
||||||
|
mariadb -u root -p$MARIADB_ROOT_PASSWORD -N -e "SELECT CONCAT('DROP DATABASE IF EXISTS \`',schema_name,'\`;') FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema','mysql','performance_schema','sys');" | mariadb -u root -p$MARIADB_ROOT_PASSWORD && \
|
||||||
|
mariadb -u root -p$MARIADB_ROOT_PASSWORD -e "CREATE DATABASE IF NOT EXISTS \`default\`;" && \
|
||||||
|
(gunzip -cf $tmpPath 2>/dev/null || cat $tmpPath) | sed -e '/^CREATE DATABASE/d' -e '/^USE \`mysql\`/d' | mariadb -u root -p$MARIADB_ROOT_PASSWORD default
|
||||||
|
EOD;
|
||||||
|
$this->restoreCommandText = $this->mariadbRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | mariadb -u root -p$MARIADB_ROOT_PASSWORD default';
|
||||||
|
} else {
|
||||||
|
$this->mariadbRestoreCommand = 'mariadb -u $MARIADB_USER -p$MARIADB_PASSWORD $MARIADB_DATABASE';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case \App\Models\StandaloneMysql::class:
|
||||||
|
if ($value === true) {
|
||||||
|
$this->mysqlRestoreCommand = <<<'EOD'
|
||||||
|
for pid in $(mysql -u root -p$MYSQL_ROOT_PASSWORD -N -e "SELECT id FROM information_schema.processlist WHERE user != 'root';"); do
|
||||||
|
mysql -u root -p$MYSQL_ROOT_PASSWORD -e "KILL $pid" 2>/dev/null || true
|
||||||
|
done && \
|
||||||
|
mysql -u root -p$MYSQL_ROOT_PASSWORD -N -e "SELECT CONCAT('DROP DATABASE IF EXISTS \`',schema_name,'\`;') FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema','mysql','performance_schema','sys');" | mysql -u root -p$MYSQL_ROOT_PASSWORD && \
|
||||||
|
mysql -u root -p$MYSQL_ROOT_PASSWORD -e "CREATE DATABASE IF NOT EXISTS \`default\`;" && \
|
||||||
|
(gunzip -cf $tmpPath 2>/dev/null || cat $tmpPath) | sed -e '/^CREATE DATABASE/d' -e '/^USE \`mysql\`/d' | mysql -u root -p$MYSQL_ROOT_PASSWORD default
|
||||||
|
EOD;
|
||||||
|
$this->restoreCommandText = $this->mysqlRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | mysql -u root -p$MYSQL_ROOT_PASSWORD default';
|
||||||
|
} else {
|
||||||
|
$this->mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case \App\Models\StandalonePostgresql::class:
|
||||||
|
if ($value === true) {
|
||||||
|
$this->postgresqlRestoreCommand = <<<'EOD'
|
||||||
|
psql -U $POSTGRES_USER -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IS NOT NULL AND pid <> pg_backend_pid()" && \
|
||||||
|
psql -U $POSTGRES_USER -t -c "SELECT datname FROM pg_database WHERE NOT datistemplate" | xargs -I {} dropdb -U $POSTGRES_USER --if-exists {} && \
|
||||||
|
createdb -U $POSTGRES_USER postgres
|
||||||
|
EOD;
|
||||||
|
$this->restoreCommandText = $this->postgresqlRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | psql -U $POSTGRES_USER postgres';
|
||||||
|
} else {
|
||||||
|
$this->postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public function getContainers()
|
public function getContainers()
|
||||||
{
|
{
|
||||||
$this->containers = collect();
|
$this->containers = collect();
|
||||||
@@ -87,6 +145,24 @@ class Import extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function checkFile()
|
||||||
|
{
|
||||||
|
if (filled($this->customLocation)) {
|
||||||
|
try {
|
||||||
|
$result = instant_remote_process(["ls -l {$this->customLocation}"], $this->server, throwError: false);
|
||||||
|
if (blank($result)) {
|
||||||
|
$this->dispatch('error', 'The file does not exist or has been deleted.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->filename = $this->customLocation;
|
||||||
|
$this->dispatch('success', 'The file exists.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function runImport()
|
public function runImport()
|
||||||
{
|
{
|
||||||
if ($this->filename === '') {
|
if ($this->filename === '') {
|
||||||
@@ -95,46 +171,83 @@ class Import extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$uploadedFilename = "upload/{$this->resource->uuid}/restore";
|
$this->importCommands = [];
|
||||||
$path = Storage::path($uploadedFilename);
|
if (filled($this->customLocation)) {
|
||||||
if (! Storage::exists($uploadedFilename)) {
|
$backupFileName = '/tmp/restore_'.$this->resource->uuid;
|
||||||
|
$this->importCommands[] = "docker cp {$this->customLocation} {$this->container}:{$backupFileName}";
|
||||||
|
$tmpPath = $backupFileName;
|
||||||
|
} else {
|
||||||
|
$backupFileName = "upload/{$this->resource->uuid}/restore";
|
||||||
|
$path = Storage::path($backupFileName);
|
||||||
|
if (! Storage::exists($backupFileName)) {
|
||||||
$this->dispatch('error', 'The file does not exist or has been deleted.');
|
$this->dispatch('error', 'The file does not exist or has been deleted.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$tmpPath = '/tmp/'.basename($uploadedFilename);
|
$tmpPath = '/tmp/'.basename($backupFileName).'_'.$this->resource->uuid;
|
||||||
instant_scp($path, $tmpPath, $this->server);
|
instant_scp($path, $tmpPath, $this->server);
|
||||||
Storage::delete($uploadedFilename);
|
Storage::delete($backupFileName);
|
||||||
$this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}";
|
$this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the restore command to a script file
|
||||||
|
$scriptPath = "/tmp/restore_{$this->resource->uuid}.sh";
|
||||||
|
|
||||||
switch ($this->resource->getMorphClass()) {
|
switch ($this->resource->getMorphClass()) {
|
||||||
case \App\Models\StandaloneMariadb::class:
|
case \App\Models\StandaloneMariadb::class:
|
||||||
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mariadbRestoreCommand} < {$tmpPath}'";
|
$restoreCommand = $this->mariadbRestoreCommand;
|
||||||
$this->importCommands[] = "rm {$tmpPath}";
|
if ($this->dumpAll) {
|
||||||
|
$restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | mariadb -u root -p\$MARIADB_ROOT_PASSWORD";
|
||||||
|
} else {
|
||||||
|
$restoreCommand .= " < {$tmpPath}";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case \App\Models\StandaloneMysql::class:
|
case \App\Models\StandaloneMysql::class:
|
||||||
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mysqlRestoreCommand} < {$tmpPath}'";
|
$restoreCommand = $this->mysqlRestoreCommand;
|
||||||
$this->importCommands[] = "rm {$tmpPath}";
|
if ($this->dumpAll) {
|
||||||
|
$restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | mysql -u root -p\$MYSQL_ROOT_PASSWORD";
|
||||||
|
} else {
|
||||||
|
$restoreCommand .= " < {$tmpPath}";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case \App\Models\StandalonePostgresql::class:
|
case \App\Models\StandalonePostgresql::class:
|
||||||
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->postgresqlRestoreCommand} {$tmpPath}'";
|
$restoreCommand = $this->postgresqlRestoreCommand;
|
||||||
$this->importCommands[] = "rm {$tmpPath}";
|
if ($this->dumpAll) {
|
||||||
|
$restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | psql -U \$POSTGRES_USER postgres";
|
||||||
|
} else {
|
||||||
|
$restoreCommand .= " {$tmpPath}";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case \App\Models\StandaloneMongodb::class:
|
case \App\Models\StandaloneMongodb::class:
|
||||||
$this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mongodbRestoreCommand}{$tmpPath}'";
|
$restoreCommand = $this->mongodbRestoreCommand;
|
||||||
$this->importCommands[] = "rm {$tmpPath}";
|
if ($this->dumpAll === false) {
|
||||||
|
$restoreCommand .= "{$tmpPath}";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->importCommands[] = "docker exec {$this->container} sh -c 'rm {$tmpPath}'";
|
$restoreCommandBase64 = base64_encode($restoreCommand);
|
||||||
|
$this->importCommands[] = "echo \"{$restoreCommandBase64}\" | base64 -d > {$scriptPath}";
|
||||||
|
$this->importCommands[] = "chmod +x {$scriptPath}";
|
||||||
|
$this->importCommands[] = "docker cp {$scriptPath} {$this->container}:{$scriptPath}";
|
||||||
|
|
||||||
|
$this->importCommands[] = "docker exec {$this->container} sh -c '{$scriptPath}'";
|
||||||
$this->importCommands[] = "docker exec {$this->container} sh -c 'echo \"Import finished with exit code $?\"'";
|
$this->importCommands[] = "docker exec {$this->container} sh -c 'echo \"Import finished with exit code $?\"'";
|
||||||
|
|
||||||
if (! empty($this->importCommands)) {
|
if (! empty($this->importCommands)) {
|
||||||
$activity = remote_process($this->importCommands, $this->server, ignore_errors: true);
|
$activity = remote_process($this->importCommands, $this->server, ignore_errors: true, callEventOnFinish: 'RestoreJobFinished', callEventData: [
|
||||||
|
'scriptPath' => $scriptPath,
|
||||||
|
'tmpPath' => $tmpPath,
|
||||||
|
'container' => $this->container,
|
||||||
|
'serverId' => $this->server->id,
|
||||||
|
]);
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
} finally {
|
||||||
|
$this->filename = null;
|
||||||
|
$this->importCommands = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,8 +9,6 @@ use App\Models\StandalonePostgresql;
|
|||||||
use Exception;
|
use Exception;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
use function Aws\filter;
|
|
||||||
|
|
||||||
class General extends Component
|
class General extends Component
|
||||||
{
|
{
|
||||||
public StandalonePostgresql $database;
|
public StandalonePostgresql $database;
|
||||||
@@ -126,10 +124,52 @@ class General extends Component
|
|||||||
|
|
||||||
public function save_init_script($script)
|
public function save_init_script($script)
|
||||||
{
|
{
|
||||||
$this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']);
|
$initScripts = collect($this->database->init_scripts ?? []);
|
||||||
$this->database->init_scripts = array_merge($this->database->init_scripts, [$script]);
|
|
||||||
|
$existingScript = $initScripts->firstWhere('filename', $script['filename']);
|
||||||
|
$oldScript = $initScripts->firstWhere('index', $script['index']);
|
||||||
|
|
||||||
|
if ($existingScript && $existingScript['index'] !== $script['index']) {
|
||||||
|
$this->dispatch('error', 'A script with this filename already exists.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$container_name = $this->database->uuid;
|
||||||
|
$configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
|
if ($oldScript && $oldScript['filename'] !== $script['filename']) {
|
||||||
|
$old_file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$oldScript['filename']}";
|
||||||
|
$delete_command = "rm -f $old_file_path";
|
||||||
|
try {
|
||||||
|
instant_remote_process([$delete_command], $this->server);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->dispatch('error', 'Failed to remove old init script from server: '.$e->getMessage());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = $initScripts->search(function ($item) use ($script) {
|
||||||
|
return $item['index'] === $script['index'];
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($index !== false) {
|
||||||
|
$initScripts[$index] = $script;
|
||||||
|
} else {
|
||||||
|
$initScripts->push($script);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->database->init_scripts = $initScripts->values()
|
||||||
|
->map(function ($item, $index) {
|
||||||
|
$item['index'] = $index;
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
})
|
||||||
|
->all();
|
||||||
|
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
$this->dispatch('success', 'Init script saved.');
|
$this->dispatch('success', 'Init script saved and updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete_init_script($script)
|
public function delete_init_script($script)
|
||||||
@@ -137,13 +177,33 @@ class General extends Component
|
|||||||
$collection = collect($this->database->init_scripts);
|
$collection = collect($this->database->init_scripts);
|
||||||
$found = $collection->firstWhere('filename', $script['filename']);
|
$found = $collection->firstWhere('filename', $script['filename']);
|
||||||
if ($found) {
|
if ($found) {
|
||||||
$this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray();
|
$container_name = $this->database->uuid;
|
||||||
$this->database->save();
|
$configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
$this->refresh();
|
$file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$script['filename']}";
|
||||||
$this->dispatch('success', 'Init script deleted.');
|
|
||||||
|
$command = "rm -f $file_path";
|
||||||
|
try {
|
||||||
|
instant_remote_process([$command], $this->server);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->dispatch('error', 'Failed to remove init script from server: '.$e->getMessage());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$updatedScripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])
|
||||||
|
->values()
|
||||||
|
->map(function ($item, $index) {
|
||||||
|
$item['index'] = $index;
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
})
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$this->database->init_scripts = $updatedScripts;
|
||||||
|
$this->database->save();
|
||||||
|
$this->refresh();
|
||||||
|
$this->dispatch('success', 'Init script deleted from the database and the server.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function refresh(): void
|
public function refresh(): void
|
||||||
|
@@ -88,12 +88,12 @@ class General extends Component
|
|||||||
if (version_compare($this->redis_version, '6.0', '>=')) {
|
if (version_compare($this->redis_version, '6.0', '>=')) {
|
||||||
$this->database->runtime_environment_variables()->updateOrCreate(
|
$this->database->runtime_environment_variables()->updateOrCreate(
|
||||||
['key' => 'REDIS_USERNAME'],
|
['key' => 'REDIS_USERNAME'],
|
||||||
['value' => $this->redis_username, 'standalone_redis_id' => $this->database->id]
|
['value' => $this->redis_username, 'resourceable_id' => $this->database->id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$this->database->runtime_environment_variables()->updateOrCreate(
|
$this->database->runtime_environment_variables()->updateOrCreate(
|
||||||
['key' => 'REDIS_PASSWORD'],
|
['key' => 'REDIS_PASSWORD'],
|
||||||
['value' => $this->redis_password, 'standalone_redis_id' => $this->database->id]
|
['value' => $this->redis_password, 'resourceable_id' => $this->database->id]
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
|
@@ -23,11 +23,11 @@ class EnvironmentEdit extends Component
|
|||||||
#[Validate(['nullable', 'string', 'max:255'])]
|
#[Validate(['nullable', 'string', 'max:255'])]
|
||||||
public ?string $description = null;
|
public ?string $description = null;
|
||||||
|
|
||||||
public function mount(string $project_uuid, string $environment_name)
|
public function mount(string $project_uuid, string $environment_uuid)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->firstOrFail();
|
$this->project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->firstOrFail();
|
||||||
$this->environment = $this->project->environments()->where('name', $environment_name)->firstOrFail();
|
$this->environment = $this->project->environments()->where('uuid', $environment_uuid)->firstOrFail();
|
||||||
$this->syncData();
|
$this->syncData();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -52,7 +52,10 @@ class EnvironmentEdit extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->syncData(true);
|
$this->syncData(true);
|
||||||
$this->redirectRoute('project.environment.edit', ['environment_name' => $this->environment->name, 'project_uuid' => $this->project->uuid]);
|
$this->redirectRoute('project.environment.edit', [
|
||||||
|
'environment_uuid' => $this->environment->uuid,
|
||||||
|
'project_uuid' => $this->project->uuid,
|
||||||
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -30,4 +30,11 @@ class Index extends Component
|
|||||||
{
|
{
|
||||||
return view('livewire.project.index');
|
return view('livewire.project.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function navigateToProject($projectUuid)
|
||||||
|
{
|
||||||
|
$project = collect($this->projects)->firstWhere('uuid', $projectUuid);
|
||||||
|
|
||||||
|
return $this->redirect($project->navigateTo(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -52,14 +52,8 @@ class DockerCompose extends Component
|
|||||||
'dockerComposeRaw' => 'required',
|
'dockerComposeRaw' => 'required',
|
||||||
]);
|
]);
|
||||||
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
|
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
|
||||||
|
|
||||||
$isValid = validateComposeFile($this->dockerComposeRaw, $server_id);
|
|
||||||
if ($isValid !== 'OK') {
|
|
||||||
return $this->dispatch('error', "Invalid docker-compose file.\n$isValid");
|
|
||||||
}
|
|
||||||
|
|
||||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
||||||
|
|
||||||
$destination_uuid = $this->query['destination'];
|
$destination_uuid = $this->query['destination'];
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
@@ -87,7 +81,8 @@ class DockerCompose extends Component
|
|||||||
'value' => $variable,
|
'value' => $variable,
|
||||||
'is_build_time' => false,
|
'is_build_time' => false,
|
||||||
'is_preview' => false,
|
'is_preview' => false,
|
||||||
'service_id' => $service->id,
|
'resourceable_id' => $service->id,
|
||||||
|
'resourceable_type' => $service->getMorphClass(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$service->name = "service-$service->uuid";
|
$service->name = "service-$service->uuid";
|
||||||
@@ -96,7 +91,7 @@ class DockerCompose extends Component
|
|||||||
|
|
||||||
return redirect()->route('project.service.configuration', [
|
return redirect()->route('project.service.configuration', [
|
||||||
'service_uuid' => $service->uuid,
|
'service_uuid' => $service->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_uuid' => $environment->uuid,
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
@@ -6,6 +6,7 @@ use App\Models\Application;
|
|||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
|
use App\Services\DockerImageParser;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
@@ -28,12 +29,10 @@ class DockerImage extends Component
|
|||||||
$this->validate([
|
$this->validate([
|
||||||
'dockerImage' => 'required',
|
'dockerImage' => 'required',
|
||||||
]);
|
]);
|
||||||
$image = str($this->dockerImage)->before(':');
|
|
||||||
if (str($this->dockerImage)->contains(':')) {
|
$parser = new DockerImageParser;
|
||||||
$tag = str($this->dockerImage)->after(':');
|
$parser->parse($this->dockerImage);
|
||||||
} else {
|
|
||||||
$tag = 'latest';
|
|
||||||
}
|
|
||||||
$destination_uuid = $this->query['destination'];
|
$destination_uuid = $this->query['destination'];
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
@@ -45,7 +44,7 @@ class DockerImage extends Component
|
|||||||
$destination_class = $destination->getMorphClass();
|
$destination_class = $destination->getMorphClass();
|
||||||
|
|
||||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
||||||
$application = Application::create([
|
$application = Application::create([
|
||||||
'name' => 'docker-image-'.new Cuid2,
|
'name' => 'docker-image-'.new Cuid2,
|
||||||
'repository_project_id' => 0,
|
'repository_project_id' => 0,
|
||||||
@@ -53,8 +52,8 @@ class DockerImage extends Component
|
|||||||
'git_branch' => 'main',
|
'git_branch' => 'main',
|
||||||
'build_pack' => 'dockerimage',
|
'build_pack' => 'dockerimage',
|
||||||
'ports_exposes' => 80,
|
'ports_exposes' => 80,
|
||||||
'docker_registry_image_name' => $image,
|
'docker_registry_image_name' => $parser->getFullImageNameWithoutTag(),
|
||||||
'docker_registry_image_tag' => $tag,
|
'docker_registry_image_tag' => $parser->getTag(),
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
'destination_id' => $destination->id,
|
'destination_id' => $destination->id,
|
||||||
'destination_type' => $destination_class,
|
'destination_type' => $destination_class,
|
||||||
@@ -69,7 +68,7 @@ class DockerImage extends Component
|
|||||||
|
|
||||||
return redirect()->route('project.application.configuration', [
|
return redirect()->route('project.application.configuration', [
|
||||||
'application_uuid' => $application->uuid,
|
'application_uuid' => $application->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_uuid' => $environment->uuid,
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\New;
|
|||||||
|
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class EmptyProject extends Component
|
class EmptyProject extends Component
|
||||||
{
|
{
|
||||||
@@ -12,8 +13,9 @@ class EmptyProject extends Component
|
|||||||
$project = Project::create([
|
$project = Project::create([
|
||||||
'name' => generate_random_name(),
|
'name' => generate_random_name(),
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
|
'uuid' => (string) new Cuid2,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production']);
|
return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $project->environments->first()->uuid]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -105,7 +105,7 @@ class GithubPrivateRepository extends Component
|
|||||||
$this->page = 1;
|
$this->page = 1;
|
||||||
$this->selected_github_app_id = $github_app_id;
|
$this->selected_github_app_id = $github_app_id;
|
||||||
$this->github_app = GithubApp::where('id', $github_app_id)->first();
|
$this->github_app = GithubApp::where('id', $github_app_id)->first();
|
||||||
$this->token = generate_github_installation_token($this->github_app);
|
$this->token = generateGithubInstallationToken($this->github_app);
|
||||||
$this->loadRepositoryByPage();
|
$this->loadRepositoryByPage();
|
||||||
if ($this->repositories->count() < $this->total_repositories_count) {
|
if ($this->repositories->count() < $this->total_repositories_count) {
|
||||||
while ($this->repositories->count() < $this->total_repositories_count) {
|
while ($this->repositories->count() < $this->total_repositories_count) {
|
||||||
@@ -177,7 +177,7 @@ class GithubPrivateRepository extends Component
|
|||||||
$destination_class = $destination->getMorphClass();
|
$destination_class = $destination->getMorphClass();
|
||||||
|
|
||||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
||||||
|
|
||||||
$application = Application::create([
|
$application = Application::create([
|
||||||
'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name),
|
'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name),
|
||||||
@@ -211,7 +211,7 @@ class GithubPrivateRepository extends Component
|
|||||||
|
|
||||||
return redirect()->route('project.application.configuration', [
|
return redirect()->route('project.application.configuration', [
|
||||||
'application_uuid' => $application->uuid,
|
'application_uuid' => $application->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_uuid' => $environment->uuid,
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
@@ -136,7 +136,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
$this->get_git_source();
|
$this->get_git_source();
|
||||||
|
|
||||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
||||||
if ($this->git_source === 'other') {
|
if ($this->git_source === 'other') {
|
||||||
$application_init = [
|
$application_init = [
|
||||||
'name' => generate_random_name(),
|
'name' => generate_random_name(),
|
||||||
@@ -184,7 +184,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
|
|
||||||
return redirect()->route('project.application.configuration', [
|
return redirect()->route('project.application.configuration', [
|
||||||
'application_uuid' => $application->uuid,
|
'application_uuid' => $application->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_uuid' => $environment->uuid,
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
@@ -188,11 +188,22 @@ class PublicGitRepository extends Component
|
|||||||
|
|
||||||
private function getGitSource()
|
private function getGitSource()
|
||||||
{
|
{
|
||||||
|
$this->git_branch = 'main';
|
||||||
|
$this->base_directory = '/';
|
||||||
|
|
||||||
$this->repository_url_parsed = Url::fromString($this->repository_url);
|
$this->repository_url_parsed = Url::fromString($this->repository_url);
|
||||||
$this->git_host = $this->repository_url_parsed->getHost();
|
$this->git_host = $this->repository_url_parsed->getHost();
|
||||||
$this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2);
|
$this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2);
|
||||||
|
|
||||||
if ($this->repository_url_parsed->getSegment(3) === 'tree') {
|
if ($this->repository_url_parsed->getSegment(3) === 'tree') {
|
||||||
$this->git_branch = str($this->repository_url_parsed->getPath())->after('tree/')->value();
|
$path = str($this->repository_url_parsed->getPath())->trim('/');
|
||||||
|
$this->git_branch = str($path)->after('tree/')->before('/')->value();
|
||||||
|
$this->base_directory = str($path)->after($this->git_branch)->after('/')->value();
|
||||||
|
if (filled($this->base_directory)) {
|
||||||
|
$this->base_directory = '/'.$this->base_directory;
|
||||||
|
} else {
|
||||||
|
$this->base_directory = '/';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->git_branch = 'main';
|
$this->git_branch = 'main';
|
||||||
}
|
}
|
||||||
@@ -225,7 +236,7 @@ class PublicGitRepository extends Component
|
|||||||
$this->validate();
|
$this->validate();
|
||||||
$destination_uuid = $this->query['destination'];
|
$destination_uuid = $this->query['destination'];
|
||||||
$project_uuid = $this->parameters['project_uuid'];
|
$project_uuid = $this->parameters['project_uuid'];
|
||||||
$environment_name = $this->parameters['environment_name'];
|
$environment_uuid = $this->parameters['environment_uuid'];
|
||||||
|
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
@@ -237,7 +248,7 @@ class PublicGitRepository extends Component
|
|||||||
$destination_class = $destination->getMorphClass();
|
$destination_class = $destination->getMorphClass();
|
||||||
|
|
||||||
$project = Project::where('uuid', $project_uuid)->first();
|
$project = Project::where('uuid', $project_uuid)->first();
|
||||||
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
|
$environment = $project->load(['environments'])->environments->where('uuid', $environment_uuid)->first();
|
||||||
|
|
||||||
if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services) {
|
if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services) {
|
||||||
$server = $destination->server;
|
$server = $destination->server;
|
||||||
@@ -260,7 +271,7 @@ class PublicGitRepository extends Component
|
|||||||
|
|
||||||
return redirect()->route('project.service.configuration', [
|
return redirect()->route('project.service.configuration', [
|
||||||
'service_uuid' => $service->uuid,
|
'service_uuid' => $service->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_uuid' => $environment->uuid,
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -319,7 +330,7 @@ class PublicGitRepository extends Component
|
|||||||
|
|
||||||
return redirect()->route('project.application.configuration', [
|
return redirect()->route('project.application.configuration', [
|
||||||
'application_uuid' => $application->uuid,
|
'application_uuid' => $application->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_uuid' => $environment->uuid,
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
@@ -23,6 +23,8 @@ class Select extends Component
|
|||||||
|
|
||||||
public Collection|null|Server $servers;
|
public Collection|null|Server $servers;
|
||||||
|
|
||||||
|
public bool $onlyBuildServerAvailable = false;
|
||||||
|
|
||||||
public ?Collection $standaloneDockers;
|
public ?Collection $standaloneDockers;
|
||||||
|
|
||||||
public ?Collection $swarmDockers;
|
public ?Collection $swarmDockers;
|
||||||
@@ -61,7 +63,7 @@ class Select extends Component
|
|||||||
}
|
}
|
||||||
$projectUuid = data_get($this->parameters, 'project_uuid');
|
$projectUuid = data_get($this->parameters, 'project_uuid');
|
||||||
$this->environments = Project::whereUuid($projectUuid)->first()->environments;
|
$this->environments = Project::whereUuid($projectUuid)->first()->environments;
|
||||||
$this->selectedEnvironment = data_get($this->parameters, 'environment_name');
|
$this->selectedEnvironment = data_get($this->parameters, 'environment_uuid');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
@@ -73,23 +75,13 @@ class Select extends Component
|
|||||||
{
|
{
|
||||||
return redirect()->route('project.resource.create', [
|
return redirect()->route('project.resource.create', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
'environment_name' => $this->selectedEnvironment,
|
'environment_uuid' => $this->selectedEnvironment,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function addExistingPostgresql()
|
|
||||||
// {
|
|
||||||
// try {
|
|
||||||
// instantCommand("psql {$this->existingPostgresqlUrl} -c 'SELECT 1'");
|
|
||||||
// $this->dispatch('success', 'Successfully connected to the database.');
|
|
||||||
// } catch (\Throwable $e) {
|
|
||||||
// return handleError($e, $this);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
public function loadServices()
|
public function loadServices()
|
||||||
{
|
{
|
||||||
$services = get_service_templates(true);
|
$services = get_service_templates();
|
||||||
$services = collect($services)->map(function ($service, $key) {
|
$services = collect($services)->map(function ($service, $key) {
|
||||||
$default_logo = 'images/default.webp';
|
$default_logo = 'images/default.webp';
|
||||||
$logo = data_get($service, 'logo', $default_logo);
|
$logo = data_get($service, 'logo', $default_logo);
|
||||||
@@ -308,7 +300,7 @@ class Select extends Component
|
|||||||
|
|
||||||
return redirect()->route('project.resource.create', [
|
return redirect()->route('project.resource.create', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
'environment_uuid' => $this->parameters['environment_uuid'],
|
||||||
'type' => $this->type,
|
'type' => $this->type,
|
||||||
'destination' => $this->destination_uuid,
|
'destination' => $this->destination_uuid,
|
||||||
'server_id' => $this->server_id,
|
'server_id' => $this->server_id,
|
||||||
@@ -323,7 +315,7 @@ class Select extends Component
|
|||||||
} else {
|
} else {
|
||||||
return redirect()->route('project.resource.create', [
|
return redirect()->route('project.resource.create', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
'environment_uuid' => $this->parameters['environment_uuid'],
|
||||||
'type' => $this->type,
|
'type' => $this->type,
|
||||||
'destination' => $this->destination_uuid,
|
'destination' => $this->destination_uuid,
|
||||||
'server_id' => $this->server_id,
|
'server_id' => $this->server_id,
|
||||||
@@ -335,5 +327,11 @@ class Select extends Component
|
|||||||
{
|
{
|
||||||
$this->servers = Server::isUsable()->get()->sortBy('name');
|
$this->servers = Server::isUsable()->get()->sortBy('name');
|
||||||
$this->allServers = $this->servers;
|
$this->allServers = $this->servers;
|
||||||
|
|
||||||
|
if ($this->allServers && $this->allServers->isNotEmpty()) {
|
||||||
|
$this->onlyBuildServerAvailable = $this->allServers->every(function ($server) {
|
||||||
|
return $server->isBuildServer();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -46,7 +46,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
|||||||
$destination_class = $destination->getMorphClass();
|
$destination_class = $destination->getMorphClass();
|
||||||
|
|
||||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
||||||
|
|
||||||
$port = get_port_from_dockerfile($this->dockerfile);
|
$port = get_port_from_dockerfile($this->dockerfile);
|
||||||
if (! $port) {
|
if (! $port) {
|
||||||
@@ -78,7 +78,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
|||||||
|
|
||||||
return redirect()->route('project.application.configuration', [
|
return redirect()->route('project.application.configuration', [
|
||||||
'application_uuid' => $application->uuid,
|
'application_uuid' => $application->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_uuid' => $environment->uuid,
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user