mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-17 12:33:06 +00:00
Merge branch 'v2' into feature/implement-basic-auth-handling
This commit is contained in:
112
.github/workflows/production-release-dockerhub.yml
vendored
112
.github/workflows/production-release-dockerhub.yml
vendored
@@ -1,112 +0,0 @@
|
|||||||
name: Production Release to DockerHub
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "this-branch-does-not-exists"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
arm64:
|
|
||||||
runs-on: [self-hosted, arm64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Get current package version
|
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
|
||||||
id: package-version
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-arm64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-arm64,mode=max
|
|
||||||
amd64:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Get current package version
|
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
|
||||||
id: package-version
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-amd64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
|
|
||||||
aarch64:
|
|
||||||
runs-on: [self-hosted, arm64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Get current package version
|
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
|
||||||
id: package-version
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/aarch64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-aarch64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-aarch64,mode=max
|
|
||||||
merge-manifest:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [amd64, arm64, aarch64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Get current package version
|
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
|
||||||
id: package-version
|
|
||||||
- name: Create & publish manifest
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools create --append coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64 --append coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64 --tag coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
|
||||||
docker buildx imagetools create coollabsio/coolify:${{steps.package-version.outputs.current-version}} --tag coollabsio/coolify:latest
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
|
||||||
5
.github/workflows/production-release.yml
vendored
5
.github/workflows/production-release.yml
vendored
@@ -14,6 +14,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: "v3"
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -44,6 +46,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: "v3"
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -95,7 +99,6 @@ jobs:
|
|||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ fromJSON(steps.meta.outputs.json).tags[0] }}-aarch64 --tag ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
|
docker buildx imagetools create --append ${{ fromJSON(steps.meta.outputs.json).tags[0] }}-aarch64 --tag ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
110
.github/workflows/release-candidate.yml
vendored
110
.github/workflows/release-candidate.yml
vendored
@@ -1,110 +0,0 @@
|
|||||||
name: Release Candidate to ghcr.io
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [prereleased]
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
IMAGE_NAME: "coollabsio/coolify"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
amd64:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Extract metadata
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v4
|
|
||||||
with:
|
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
tags: |
|
|
||||||
type=ref,event=branch
|
|
||||||
type=ref,event=pr
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
aarch64:
|
|
||||||
runs-on: [self-hosted, arm64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Extract metadata
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v4
|
|
||||||
with:
|
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
tags: |
|
|
||||||
type=ref,event=branch
|
|
||||||
type=ref,event=pr
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/aarch64
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}-aarch64
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
merge-manifest:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [amd64, aarch64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Extract metadata
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v4
|
|
||||||
with:
|
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
tags: |
|
|
||||||
type=ref,event=branch
|
|
||||||
type=ref,event=pr
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
- name: Create & publish manifest
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools create --append ${{ steps.meta.outputs.tags }}-aarch64 --tag ${{ steps.meta.outputs.tags }}
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
|
||||||
86
.github/workflows/staging-release-dockerhub.yml
vendored
86
.github/workflows/staging-release-dockerhub.yml
vendored
@@ -1,86 +0,0 @@
|
|||||||
name: Staging Release to DockerHub
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "this-branch-does-not-exists"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
arm64:
|
|
||||||
runs-on: [self-hosted, arm64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: "next"
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Get current package version
|
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
|
||||||
id: package-version
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:next-arm64
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-arm64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-arm64,mode=max
|
|
||||||
amd64:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: "next"
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Get current package version
|
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
|
||||||
id: package-version
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:next
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
|
||||||
merge-manifest:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [arm64, amd64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Create & publish manifest
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools create --append coollabsio/coolify:next-arm64 --tag coollabsio/coolify:next
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
|
||||||
4
.github/workflows/staging-release.yml
vendored
4
.github/workflows/staging-release.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: "next"
|
ref: "v3"
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: "next"
|
ref: "v3"
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ ARG DOCKER_COMPOSE_VERSION=2.6.1
|
|||||||
# https://github.com/buildpacks/pack/releases
|
# https://github.com/buildpacks/pack/releases
|
||||||
ARG PACK_VERSION=0.27.0
|
ARG PACK_VERSION=0.27.0
|
||||||
|
|
||||||
RUN apt update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3
|
RUN apt update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3 vim
|
||||||
RUN apt-get clean autoclean && apt-get autoremove --yes && rm -rf /var/lib/{apt,dpkg,cache,log}/
|
RUN apt-get clean autoclean && apt-get autoremove --yes && rm -rf /var/lib/{apt,dpkg,cache,log}/
|
||||||
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||||
RUN npm install -g npm@${PNPM_VERSION}
|
RUN npm install -g npm@${PNPM_VERSION}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ If you have a new service / build pack you would like to add, raise an idea [her
|
|||||||
|
|
||||||
## How to install
|
## How to install
|
||||||
|
|
||||||
For more details goto the [docs](https://docs.coollabs.io/coolify/installation).
|
For more details goto the [docs](https://docs.coollabs.io/coolify-v3/installation).
|
||||||
|
|
||||||
Installation is automated with the following command:
|
Installation is automated with the following command:
|
||||||
|
|
||||||
@@ -79,9 +79,9 @@ Deploy your resource to:
|
|||||||
### Services
|
### Services
|
||||||
|
|
||||||
- [Appwrite](https://appwrite.io)
|
- [Appwrite](https://appwrite.io)
|
||||||
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
|
- [WordPress](https://docs.coollabs.io/coolify-v3/services/wordpress)
|
||||||
- [Ghost](https://ghost.org)
|
- [Ghost](https://ghost.org)
|
||||||
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
|
- [Plausible Analytics](https://docs.coollabs.io/coolify-v3/services/plausible-analytics)
|
||||||
- [NocoDB](https://nocodb.com)
|
- [NocoDB](https://nocodb.com)
|
||||||
- [VSCode Server](https://github.com/cdr/code-server)
|
- [VSCode Server](https://github.com/cdr/code-server)
|
||||||
- [MinIO](https://min.io)
|
- [MinIO](https://min.io)
|
||||||
|
|||||||
@@ -26,8 +26,6 @@
|
|||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@ladjs/graceful": "3.2.1",
|
"@ladjs/graceful": "3.2.1",
|
||||||
"@prisma/client": "4.8.1",
|
"@prisma/client": "4.8.1",
|
||||||
"@sentry/node": "7.30.0",
|
|
||||||
"@sentry/tracing": "7.30.0",
|
|
||||||
"axe": "11.2.1",
|
"axe": "11.2.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bree": "9.1.3",
|
"bree": "9.1.3",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ async function main() {
|
|||||||
await prisma.setting.create({
|
await prisma.setting.create({
|
||||||
data: {
|
data: {
|
||||||
id: '0',
|
id: '0',
|
||||||
arch: process.arch,
|
arch: process.arch
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -81,16 +81,246 @@ async function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Set new preview secrets
|
// Set new preview secrets
|
||||||
const secrets = await prisma.secret.findMany({ where: { isPRMRSecret: false } })
|
const secrets = await prisma.secret.findMany({ where: { isPRMRSecret: false } });
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
for (const secret of secrets) {
|
for (const secret of secrets) {
|
||||||
const previewSecrets = await prisma.secret.findMany({ where: { applicationId: secret.applicationId, name: secret.name, isPRMRSecret: true } })
|
const previewSecrets = await prisma.secret.findMany({
|
||||||
|
where: { applicationId: secret.applicationId, name: secret.name, isPRMRSecret: true }
|
||||||
|
});
|
||||||
if (previewSecrets.length === 0) {
|
if (previewSecrets.length === 0) {
|
||||||
await prisma.secret.create({ data: { ...secret, id: undefined, isPRMRSecret: true } })
|
await prisma.secret.create({ data: { ...secret, id: undefined, isPRMRSecret: true } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function reEncryptSecrets() {
|
||||||
|
const { execaCommand } = await import('execa');
|
||||||
|
const date = new Date().getTime();
|
||||||
|
await execaCommand('env | grep COOLIFY > .env', { shell: true });
|
||||||
|
const secretOld = process.env['COOLIFY_SECRET_KEY'];
|
||||||
|
let secretNew = process.env['COOLIFY_SECRET_KEY_BETTER'];
|
||||||
|
if (!secretNew) {
|
||||||
|
console.log('No COOLIFY_SECRET_KEY_BETTER found... Generating new one...');
|
||||||
|
const { stdout: newKey } = await execaCommand(
|
||||||
|
'openssl rand -base64 1024 | sha256sum | base64 | head -c 32',
|
||||||
|
{ shell: true }
|
||||||
|
);
|
||||||
|
secretNew = newKey;
|
||||||
|
}
|
||||||
|
if (secretOld !== secretNew) {
|
||||||
|
console.log(
|
||||||
|
'Secrets (COOLIFY_SECRET_KEY & COOLIFY_SECRET_KEY_BETTER) are different, so re-encrypting everything...'
|
||||||
|
);
|
||||||
|
await execaCommand(`sed -i '/COOLIFY_SECRET_KEY=/d' .env`, { shell: true });
|
||||||
|
await execaCommand(`sed -i '/COOLIFY_SECRET_KEY_BETTER=/d' .env`, { shell: true });
|
||||||
|
await execaCommand(`echo "COOLIFY_SECRET_KEY=${secretNew}" >> .env`, { shell: true });
|
||||||
|
await execaCommand('echo "COOLIFY_SECRET_KEY_BETTER=' + secretNew + '" >> .env ', {
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
await execaCommand(`echo "COOLIFY_SECRET_KEY_OLD_${date}=${secretOld}" >> .env`, {
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
console.log(`Backup database to /app/db/prod.db_${date}.`);
|
||||||
|
await execaCommand(`cp /app/db/prod.db /app/db/prod.db_${date}`, { shell: true });
|
||||||
|
const transactions = [];
|
||||||
|
const secrets = await prisma.secret.findMany();
|
||||||
|
if (secrets.length > 0) {
|
||||||
|
for (const secret of secrets) {
|
||||||
|
const value = decrypt(secret.value, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.secret.update({
|
||||||
|
where: { id: secret.id },
|
||||||
|
data: { value: newValue }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const serviceSecrets = await prisma.serviceSecret.findMany();
|
||||||
|
if (serviceSecrets.length > 0) {
|
||||||
|
for (const secret of serviceSecrets) {
|
||||||
|
const value = decrypt(secret.value, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.serviceSecret.update({
|
||||||
|
where: { id: secret.id },
|
||||||
|
data: { value: newValue }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const gitlabApps = await prisma.gitlabApp.findMany();
|
||||||
|
if (gitlabApps.length > 0) {
|
||||||
|
for (const gitlabApp of gitlabApps) {
|
||||||
|
const value = decrypt(gitlabApp.privateSshKey, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
const appSecret = decrypt(gitlabApp.appSecret, secretOld);
|
||||||
|
const newAppSecret = encrypt(appSecret, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.gitlabApp.update({
|
||||||
|
where: { id: gitlabApp.id },
|
||||||
|
data: { privateSshKey: newValue, appSecret: newAppSecret }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const githubApps = await prisma.githubApp.findMany();
|
||||||
|
if (githubApps.length > 0) {
|
||||||
|
for (const githubApp of githubApps) {
|
||||||
|
const clientSecret = decrypt(githubApp.clientSecret, secretOld);
|
||||||
|
const newClientSecret = encrypt(clientSecret, secretNew);
|
||||||
|
const webhookSecret = decrypt(githubApp.webhookSecret, secretOld);
|
||||||
|
const newWebhookSecret = encrypt(webhookSecret, secretNew);
|
||||||
|
const privateKey = decrypt(githubApp.privateKey, secretOld);
|
||||||
|
const newPrivateKey = encrypt(privateKey, secretNew);
|
||||||
|
|
||||||
|
transactions.push(
|
||||||
|
prisma.githubApp.update({
|
||||||
|
where: { id: githubApp.id },
|
||||||
|
data: {
|
||||||
|
clientSecret: newClientSecret,
|
||||||
|
webhookSecret: newWebhookSecret,
|
||||||
|
privateKey: newPrivateKey
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const databases = await prisma.database.findMany();
|
||||||
|
if (databases.length > 0) {
|
||||||
|
for (const database of databases) {
|
||||||
|
const dbUserPassword = decrypt(database.dbUserPassword, secretOld);
|
||||||
|
const newDbUserPassword = encrypt(dbUserPassword, secretNew);
|
||||||
|
const rootUserPassword = decrypt(database.rootUserPassword, secretOld);
|
||||||
|
const newRootUserPassword = encrypt(rootUserPassword, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.database.update({
|
||||||
|
where: { id: database.id },
|
||||||
|
data: {
|
||||||
|
dbUserPassword: newDbUserPassword,
|
||||||
|
rootUserPassword: newRootUserPassword
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const databaseSecrets = await prisma.databaseSecret.findMany();
|
||||||
|
if (databaseSecrets.length > 0) {
|
||||||
|
for (const databaseSecret of databaseSecrets) {
|
||||||
|
const value = decrypt(databaseSecret.value, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.databaseSecret.update({
|
||||||
|
where: { id: databaseSecret.id },
|
||||||
|
data: { value: newValue }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const wordpresses = await prisma.wordpress.findMany();
|
||||||
|
if (wordpresses.length > 0) {
|
||||||
|
for (const wordpress of wordpresses) {
|
||||||
|
const value = decrypt(wordpress.ftpHostKey, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
const ftpHostKeyPrivate = decrypt(wordpress.ftpHostKeyPrivate, secretOld);
|
||||||
|
const newFtpHostKeyPrivate = encrypt(ftpHostKeyPrivate, secretNew);
|
||||||
|
let newFtpPassword = undefined;
|
||||||
|
if (wordpress.ftpPassword != null) {
|
||||||
|
const ftpPassword = decrypt(wordpress.ftpPassword, secretOld);
|
||||||
|
newFtpPassword = encrypt(ftpPassword, secretNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions.push(
|
||||||
|
prisma.wordpress.update({
|
||||||
|
where: { id: wordpress.id },
|
||||||
|
data: {
|
||||||
|
ftpHostKey: newValue,
|
||||||
|
ftpHostKeyPrivate: newFtpHostKeyPrivate,
|
||||||
|
ftpPassword: newFtpPassword
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sshKeys = await prisma.sshKey.findMany();
|
||||||
|
if (sshKeys.length > 0) {
|
||||||
|
for (const key of sshKeys) {
|
||||||
|
const value = decrypt(key.privateKey, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.sshKey.update({
|
||||||
|
where: { id: key.id },
|
||||||
|
data: {
|
||||||
|
privateKey: newValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dockerRegistries = await prisma.dockerRegistry.findMany();
|
||||||
|
if (dockerRegistries.length > 0) {
|
||||||
|
for (const registry of dockerRegistries) {
|
||||||
|
const value = decrypt(registry.password, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.dockerRegistry.update({
|
||||||
|
where: { id: registry.id },
|
||||||
|
data: {
|
||||||
|
password: newValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const certificates = await prisma.certificate.findMany();
|
||||||
|
if (certificates.length > 0) {
|
||||||
|
for (const certificate of certificates) {
|
||||||
|
const value = decrypt(certificate.key, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.certificate.update({
|
||||||
|
where: { id: certificate.id },
|
||||||
|
data: {
|
||||||
|
key: newValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await prisma.$transaction(transactions);
|
||||||
|
} else {
|
||||||
|
console.log('secrets are the same, so no need to re-encrypt');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const encrypt = (text, secret) => {
|
||||||
|
if (text && secret) {
|
||||||
|
const iv = crypto.randomBytes(16);
|
||||||
|
const cipher = crypto.createCipheriv(algorithm, secret, iv);
|
||||||
|
const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]);
|
||||||
|
return JSON.stringify({
|
||||||
|
iv: iv.toString('hex'),
|
||||||
|
content: encrypted.toString('hex')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const decrypt = (hashString, secret) => {
|
||||||
|
if (hashString && secret) {
|
||||||
|
try {
|
||||||
|
const hash = JSON.parse(hashString);
|
||||||
|
const decipher = crypto.createDecipheriv(algorithm, secret, Buffer.from(hash.iv, 'hex'));
|
||||||
|
const decrpyted = Buffer.concat([
|
||||||
|
decipher.update(Buffer.from(hash.content, 'hex')),
|
||||||
|
decipher.final()
|
||||||
|
]);
|
||||||
|
return decrpyted.toString();
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ decryptionError: error.message });
|
||||||
|
return hashString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
main()
|
main()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -99,15 +329,11 @@ main()
|
|||||||
.finally(async () => {
|
.finally(async () => {
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
});
|
});
|
||||||
|
reEncryptSecrets()
|
||||||
const encrypt = (text) => {
|
.catch((e) => {
|
||||||
if (text) {
|
console.error(e);
|
||||||
const iv = crypto.randomBytes(16);
|
process.exit(1);
|
||||||
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv);
|
})
|
||||||
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
|
.finally(async () => {
|
||||||
return JSON.stringify({
|
await prisma.$disconnect();
|
||||||
iv: iv.toString('hex'),
|
|
||||||
content: encrypted.toString('hex')
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
import Fastify from 'fastify';
|
|
||||||
import cors from '@fastify/cors';
|
|
||||||
import serve from '@fastify/static';
|
|
||||||
import env from '@fastify/env';
|
|
||||||
import cookie from '@fastify/cookie';
|
|
||||||
import multipart from '@fastify/multipart';
|
|
||||||
import path, { join } from 'path';
|
|
||||||
import autoLoad from '@fastify/autoload';
|
import autoLoad from '@fastify/autoload';
|
||||||
|
import cookie from '@fastify/cookie';
|
||||||
|
import cors from '@fastify/cors';
|
||||||
|
import env from '@fastify/env';
|
||||||
|
import multipart from '@fastify/multipart';
|
||||||
|
import serve from '@fastify/static';
|
||||||
|
import Fastify from 'fastify';
|
||||||
import socketIO from 'fastify-socket.io';
|
import socketIO from 'fastify-socket.io';
|
||||||
|
import path, { join } from 'path';
|
||||||
import socketIOServer from './realtime';
|
import socketIOServer from './realtime';
|
||||||
|
|
||||||
|
import Graceful from '@ladjs/graceful';
|
||||||
|
import { compareVersions } from 'compare-versions';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
|
||||||
import {
|
import {
|
||||||
cleanupDockerStorage,
|
cleanupDockerStorage,
|
||||||
createRemoteEngineConfiguration,
|
createRemoteEngineConfiguration,
|
||||||
@@ -18,26 +23,20 @@ import {
|
|||||||
isDev,
|
isDev,
|
||||||
listSettings,
|
listSettings,
|
||||||
prisma,
|
prisma,
|
||||||
sentryDSN,
|
|
||||||
startTraefikProxy,
|
startTraefikProxy,
|
||||||
startTraefikTCPProxy,
|
startTraefikTCPProxy,
|
||||||
version
|
version
|
||||||
} from './lib/common';
|
} from './lib/common';
|
||||||
import { scheduler } from './lib/scheduler';
|
|
||||||
import { compareVersions } from 'compare-versions';
|
|
||||||
import Graceful from '@ladjs/graceful';
|
|
||||||
import yaml from 'js-yaml';
|
|
||||||
import fs from 'fs/promises';
|
|
||||||
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
|
||||||
import { checkContainer } from './lib/docker';
|
import { checkContainer } from './lib/docker';
|
||||||
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
|
import { scheduler } from './lib/scheduler';
|
||||||
|
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
||||||
import { refreshTags, refreshTemplates } from './routes/api/v1/handlers';
|
import { refreshTags, refreshTemplates } from './routes/api/v1/handlers';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
config: {
|
config: {
|
||||||
COOLIFY_APP_ID: string;
|
COOLIFY_APP_ID: string;
|
||||||
COOLIFY_SECRET_KEY: string;
|
COOLIFY_SECRET_KEY: string;
|
||||||
|
COOLIFY_SECRET_KEY_BETTER: string | null;
|
||||||
COOLIFY_DATABASE_URL: string;
|
COOLIFY_DATABASE_URL: string;
|
||||||
COOLIFY_IS_ON: string;
|
COOLIFY_IS_ON: string;
|
||||||
COOLIFY_WHITE_LABELED: string;
|
COOLIFY_WHITE_LABELED: string;
|
||||||
@@ -67,6 +66,10 @@ const host = '0.0.0.0';
|
|||||||
COOLIFY_SECRET_KEY: {
|
COOLIFY_SECRET_KEY: {
|
||||||
type: 'string'
|
type: 'string'
|
||||||
},
|
},
|
||||||
|
COOLIFY_SECRET_KEY_BETTER: {
|
||||||
|
type: 'string',
|
||||||
|
default: null
|
||||||
|
},
|
||||||
COOLIFY_DATABASE_URL: {
|
COOLIFY_DATABASE_URL: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'file:../db/dev.db'
|
default: 'file:../db/dev.db'
|
||||||
@@ -185,17 +188,17 @@ const host = '0.0.0.0';
|
|||||||
// Refresh and check templates
|
// Refresh and check templates
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await refreshTemplates();
|
await refreshTemplates();
|
||||||
}, 60000);
|
}, 60000 * 10);
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await refreshTags();
|
await refreshTags();
|
||||||
}, 60000);
|
}, 60000 * 10);
|
||||||
|
|
||||||
setInterval(
|
setInterval(
|
||||||
async () => {
|
async () => {
|
||||||
await migrateServicesToNewTemplate();
|
await migrateServicesToNewTemplate();
|
||||||
},
|
},
|
||||||
isDev ? 10000 : 60000
|
isDev ? 10000 : 60000 * 10
|
||||||
);
|
);
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
@@ -276,9 +279,6 @@ async function initServer() {
|
|||||||
if (settings.doNotTrack === true) {
|
if (settings.doNotTrack === true) {
|
||||||
console.log('[000] Telemetry disabled...');
|
console.log('[000] Telemetry disabled...');
|
||||||
} else {
|
} else {
|
||||||
if (settings.sentryDSN !== sentryDSN) {
|
|
||||||
await prisma.setting.update({ where: { id: '0' }, data: { sentryDSN } });
|
|
||||||
}
|
|
||||||
// Initialize Sentry
|
// Initialize Sentry
|
||||||
// Sentry.init({
|
// Sentry.init({
|
||||||
// dsn: sentryDSN,
|
// dsn: sentryDSN,
|
||||||
@@ -402,7 +402,9 @@ async function autoUpdater() {
|
|||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||||
if (isAutoUpdateEnabled) {
|
if (isAutoUpdateEnabled) {
|
||||||
await executeCommand({ command: `docker pull ghcr.io/coollabsio/coolify:${latestVersion}` });
|
await executeCommand({
|
||||||
|
command: `docker pull ghcr.io/coollabsio/coolify:${latestVersion}`
|
||||||
|
});
|
||||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||||
@@ -546,7 +548,11 @@ async function copySSLCertificates() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
|
try {
|
||||||
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` });
|
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` });
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,53 +610,54 @@ async function cleanupStorage() {
|
|||||||
if (!destination.remoteVerified) continue;
|
if (!destination.remoteVerified) continue;
|
||||||
enginesDone.add(destination.remoteIpAddress);
|
enginesDone.add(destination.remoteIpAddress);
|
||||||
}
|
}
|
||||||
let lowDiskSpace = false;
|
|
||||||
try {
|
|
||||||
let stdout = null;
|
|
||||||
if (!isDev) {
|
|
||||||
const output = await executeCommand({
|
|
||||||
dockerId: destination.id,
|
|
||||||
command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
|
|
||||||
shell: true
|
|
||||||
});
|
|
||||||
stdout = output.stdout;
|
|
||||||
} else {
|
|
||||||
const output = await executeCommand({
|
|
||||||
command: `df -kPT /`
|
|
||||||
});
|
|
||||||
stdout = output.stdout;
|
|
||||||
}
|
|
||||||
let lines = stdout.trim().split('\n');
|
|
||||||
let header = lines[0];
|
|
||||||
let regex =
|
|
||||||
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
|
||||||
const boundaries = [];
|
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = regex.exec(header))) {
|
|
||||||
boundaries.push(match[0].length);
|
|
||||||
}
|
|
||||||
|
|
||||||
boundaries[boundaries.length - 1] = -1;
|
|
||||||
const data = lines.slice(1).map((line) => {
|
|
||||||
const cl = boundaries.map((boundary) => {
|
|
||||||
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
|
||||||
line = line.slice(boundary);
|
|
||||||
return column.trim();
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
capacity: Number.parseInt(cl[5], 10) / 100
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (data.length > 0) {
|
|
||||||
const { capacity } = data[0];
|
|
||||||
if (capacity > 0.8) {
|
|
||||||
lowDiskSpace = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {}
|
|
||||||
if (lowDiskSpace) {
|
|
||||||
await cleanupDockerStorage(destination.id);
|
await cleanupDockerStorage(destination.id);
|
||||||
}
|
// let lowDiskSpace = false;
|
||||||
|
// try {
|
||||||
|
// let stdout = null;
|
||||||
|
// if (!isDev) {
|
||||||
|
// const output = await executeCommand({
|
||||||
|
// dockerId: destination.id,
|
||||||
|
// command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
|
||||||
|
// shell: true
|
||||||
|
// });
|
||||||
|
// stdout = output.stdout;
|
||||||
|
// } else {
|
||||||
|
// const output = await executeCommand({
|
||||||
|
// command: `df -kPT /`
|
||||||
|
// });
|
||||||
|
// stdout = output.stdout;
|
||||||
|
// }
|
||||||
|
// let lines = stdout.trim().split('\n');
|
||||||
|
// let header = lines[0];
|
||||||
|
// let regex =
|
||||||
|
// /^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
||||||
|
// const boundaries = [];
|
||||||
|
// let match;
|
||||||
|
|
||||||
|
// while ((match = regex.exec(header))) {
|
||||||
|
// boundaries.push(match[0].length);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// boundaries[boundaries.length - 1] = -1;
|
||||||
|
// const data = lines.slice(1).map((line) => {
|
||||||
|
// const cl = boundaries.map((boundary) => {
|
||||||
|
// const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||||
|
// line = line.slice(boundary);
|
||||||
|
// return column.trim();
|
||||||
|
// });
|
||||||
|
// return {
|
||||||
|
// capacity: Number.parseInt(cl[5], 10) / 100
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
// if (data.length > 0) {
|
||||||
|
// const { capacity } = data[0];
|
||||||
|
// if (capacity > 0.8) {
|
||||||
|
// lowDiskSpace = true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } catch (error) {}
|
||||||
|
// if (lowDiskSpace) {
|
||||||
|
// await cleanupDockerStorage(destination.id);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export default async function (data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
|
let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
|
||||||
console.log({ key, environment });
|
|
||||||
if (Object.keys(environment).length > 0) {
|
if (Object.keys(environment).length > 0) {
|
||||||
environment = Object.entries(environment).map(([key, value]) => `${key}=${value}`);
|
environment = Object.entries(environment).map(([key, value]) => `${key}=${value}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { exec } from 'node:child_process';
|
|
||||||
import util from 'util';
|
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
|
import fsNormal from 'fs';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import forge from 'node-forge';
|
import forge from 'node-forge';
|
||||||
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
|
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
|
||||||
@@ -8,7 +7,6 @@ import type { Config } from 'unique-names-generator';
|
|||||||
import generator from 'generate-password';
|
import generator from 'generate-password';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { promises as dns } from 'dns';
|
import { promises as dns } from 'dns';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import * as SSHConfig from 'ssh-config/src/ssh-config';
|
import * as SSHConfig from 'ssh-config/src/ssh-config';
|
||||||
@@ -18,13 +16,13 @@ import { day } from './dayjs';
|
|||||||
import { saveBuildLog } from './buildPacks/common';
|
import { saveBuildLog } from './buildPacks/common';
|
||||||
import { scheduler } from './scheduler';
|
import { scheduler } from './scheduler';
|
||||||
import type { ExecaChildProcess } from 'execa';
|
import type { ExecaChildProcess } from 'execa';
|
||||||
|
import { FastifyReply } from 'fastify';
|
||||||
|
|
||||||
export const version = '3.12.31';
|
export const version = '3.12.34';
|
||||||
export const isDev = process.env.NODE_ENV === 'development';
|
export const isDev = process.env.NODE_ENV === 'development';
|
||||||
export const proxyPort = process.env.COOLIFY_PROXY_PORT;
|
export const proxyPort = process.env.COOLIFY_PROXY_PORT;
|
||||||
export const proxySecurePort = process.env.COOLIFY_PROXY_SECURE_PORT;
|
export const proxySecurePort = process.env.COOLIFY_PROXY_SECURE_PORT;
|
||||||
export const sentryDSN =
|
|
||||||
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
|
|
||||||
const algorithm = 'aes-256-ctr';
|
const algorithm = 'aes-256-ctr';
|
||||||
const customConfig: Config = {
|
const customConfig: Config = {
|
||||||
dictionaries: [adjectives, colors, animals],
|
dictionaries: [adjectives, colors, animals],
|
||||||
@@ -172,13 +170,19 @@ export const base64Encode = (text: string): string => {
|
|||||||
export const base64Decode = (text: string): string => {
|
export const base64Decode = (text: string): string => {
|
||||||
return Buffer.from(text, 'base64').toString('ascii');
|
return Buffer.from(text, 'base64').toString('ascii');
|
||||||
};
|
};
|
||||||
|
export const getSecretKey = () => {
|
||||||
|
if (process.env['COOLIFY_SECRET_KEY_BETTER']) {
|
||||||
|
return process.env['COOLIFY_SECRET_KEY_BETTER'];
|
||||||
|
}
|
||||||
|
return process.env['COOLIFY_SECRET_KEY'];
|
||||||
|
};
|
||||||
export const decrypt = (hashString: string) => {
|
export const decrypt = (hashString: string) => {
|
||||||
if (hashString) {
|
if (hashString) {
|
||||||
try {
|
try {
|
||||||
const hash = JSON.parse(hashString);
|
const hash = JSON.parse(hashString);
|
||||||
const decipher = crypto.createDecipheriv(
|
const decipher = crypto.createDecipheriv(
|
||||||
algorithm,
|
algorithm,
|
||||||
process.env['COOLIFY_SECRET_KEY'],
|
getSecretKey(),
|
||||||
Buffer.from(hash.iv, 'hex')
|
Buffer.from(hash.iv, 'hex')
|
||||||
);
|
);
|
||||||
const decrpyted = Buffer.concat([
|
const decrpyted = Buffer.concat([
|
||||||
@@ -195,7 +199,7 @@ export const decrypt = (hashString: string) => {
|
|||||||
export const encrypt = (text: string) => {
|
export const encrypt = (text: string) => {
|
||||||
if (text) {
|
if (text) {
|
||||||
const iv = crypto.randomBytes(16);
|
const iv = crypto.randomBytes(16);
|
||||||
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv);
|
const cipher = crypto.createCipheriv(algorithm, getSecretKey(), iv);
|
||||||
const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]);
|
const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]);
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
iv: iv.toString('hex'),
|
iv: iv.toString('hex'),
|
||||||
@@ -579,7 +583,8 @@ export async function executeCommand({
|
|||||||
stream = false,
|
stream = false,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
debug
|
debug,
|
||||||
|
timeout = 0
|
||||||
}: {
|
}: {
|
||||||
command: string;
|
command: string;
|
||||||
sshCommand?: boolean;
|
sshCommand?: boolean;
|
||||||
@@ -589,6 +594,7 @@ export async function executeCommand({
|
|||||||
buildId?: string;
|
buildId?: string;
|
||||||
applicationId?: string;
|
applicationId?: string;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
|
timeout?: number;
|
||||||
}): Promise<ExecaChildProcess<string>> {
|
}): Promise<ExecaChildProcess<string>> {
|
||||||
const { execa, execaCommand } = await import('execa');
|
const { execa, execaCommand } = await import('execa');
|
||||||
const { parse } = await import('shell-quote');
|
const { parse } = await import('shell-quote');
|
||||||
@@ -613,20 +619,26 @@ export async function executeCommand({
|
|||||||
}
|
}
|
||||||
if (sshCommand) {
|
if (sshCommand) {
|
||||||
if (shell) {
|
if (shell) {
|
||||||
return execaCommand(`ssh ${remoteIpAddress}-remote ${command}`);
|
return execaCommand(`ssh ${remoteIpAddress}-remote ${command}`, {
|
||||||
|
timeout
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return await execa('ssh', [`${remoteIpAddress}-remote`, dockerCommand, ...dockerArgs]);
|
return await execa('ssh', [`${remoteIpAddress}-remote`, dockerCommand, ...dockerArgs], {
|
||||||
|
timeout
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (stream) {
|
if (stream) {
|
||||||
return await new Promise(async (resolve, reject) => {
|
return await new Promise(async (resolve, reject) => {
|
||||||
let subprocess = null;
|
let subprocess = null;
|
||||||
if (shell) {
|
if (shell) {
|
||||||
subprocess = execaCommand(command, {
|
subprocess = execaCommand(command, {
|
||||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||||
|
timeout
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
subprocess = execa(dockerCommand, dockerArgs, {
|
subprocess = execa(dockerCommand, dockerArgs, {
|
||||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||||
|
timeout
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const logs = [];
|
const logs = [];
|
||||||
@@ -680,19 +692,26 @@ export async function executeCommand({
|
|||||||
} else {
|
} else {
|
||||||
if (shell) {
|
if (shell) {
|
||||||
return await execaCommand(command, {
|
return await execaCommand(command, {
|
||||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||||
|
timeout
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await execa(dockerCommand, dockerArgs, {
|
return await execa(dockerCommand, dockerArgs, {
|
||||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||||
|
timeout
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (shell) {
|
if (shell) {
|
||||||
return execaCommand(command, { shell: true });
|
return execaCommand(command, {
|
||||||
|
shell: true,
|
||||||
|
timeout
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return await execa(dockerCommand, dockerArgs);
|
return await execa(dockerCommand, dockerArgs, {
|
||||||
|
timeout
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -826,7 +845,7 @@ export function generateToken() {
|
|||||||
{
|
{
|
||||||
nbf: Math.floor(Date.now() / 1000) - 30
|
nbf: Math.floor(Date.now() / 1000) - 30
|
||||||
},
|
},
|
||||||
process.env['COOLIFY_SECRET_KEY']
|
getSecretKey()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export function generatePassword({
|
export function generatePassword({
|
||||||
@@ -1038,8 +1057,7 @@ export function generateDatabaseConfiguration(database: any): DatabaseConfigurat
|
|||||||
};
|
};
|
||||||
if (isARM()) {
|
if (isARM()) {
|
||||||
configuration.volume = `${id}-${type}-data:/data`;
|
configuration.volume = `${id}-${type}-data:/data`;
|
||||||
configuration.command = `/usr/local/bin/redis-server --appendonly ${
|
configuration.command = `/usr/local/bin/redis-server --appendonly ${appendOnly ? 'yes' : 'no'
|
||||||
appendOnly ? 'yes' : 'no'
|
|
||||||
} --requirepass ${dbUserPassword}`;
|
} --requirepass ${dbUserPassword}`;
|
||||||
}
|
}
|
||||||
return configuration;
|
return configuration;
|
||||||
@@ -1664,9 +1682,6 @@ export function errorHandler({
|
|||||||
if (message.includes('Unique constraint failed')) {
|
if (message.includes('Unique constraint failed')) {
|
||||||
message = 'This data is unique and already exists. Please try again with a different value.';
|
message = 'This data is unique and already exists. Please try again with a different value.';
|
||||||
}
|
}
|
||||||
if (type === 'normal') {
|
|
||||||
Sentry.captureException(message);
|
|
||||||
}
|
|
||||||
throw { status, message };
|
throw { status, message };
|
||||||
}
|
}
|
||||||
export async function generateSshKeyPair(): Promise<{ publicKey: string; privateKey: string }> {
|
export async function generateSshKeyPair(): Promise<{ publicKey: string; privateKey: string }> {
|
||||||
@@ -1927,3 +1942,51 @@ export function generateSecrets(
|
|||||||
}
|
}
|
||||||
return envs;
|
return envs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function backupPostgresqlDatabase(database, reply) {
|
||||||
|
const backupFolder = '/tmp'
|
||||||
|
const fileName = `${database.id}-${new Date().getTime()}.gz`
|
||||||
|
const backupFileName = `${backupFolder}/${fileName}`
|
||||||
|
console.log({ database })
|
||||||
|
let command = null
|
||||||
|
switch (database?.type) {
|
||||||
|
case 'postgresql':
|
||||||
|
command = `docker exec ${database.id} sh -c "PGPASSWORD=${database.rootUserPassword} pg_dumpall -U postgres | gzip > ${backupFileName}"`
|
||||||
|
break;
|
||||||
|
case 'mongodb':
|
||||||
|
command = `docker exec ${database.id} sh -c "mongodump --archive=${backupFileName} --gzip --username=${database.rootUser} --password=${database.rootUserPassword}"`
|
||||||
|
break;
|
||||||
|
case 'mysql':
|
||||||
|
command = `docker exec ${database.id} sh -c "mysqldump --all-databases --single-transaction --quick --lock-tables=false --user=${database.rootUser} --password=${database.rootUserPassword} | gzip > ${backupFileName}"`
|
||||||
|
break;
|
||||||
|
case 'mariadb':
|
||||||
|
command = `docker exec ${database.id} sh -c "mysqldump --all-databases --single-transaction --quick --lock-tables=false --user=${database.rootUser} --password=${database.rootUserPassword} | gzip > ${backupFileName}"`
|
||||||
|
break;
|
||||||
|
case 'couchdb':
|
||||||
|
command = `docker exec ${database.id} sh -c "tar -czvf ${backupFileName} /bitnami/couchdb/data"`
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: database.destinationDockerId,
|
||||||
|
command,
|
||||||
|
});
|
||||||
|
const copyCommand = `docker cp ${database.id}:${backupFileName} ${backupFileName}`
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: database.destinationDockerId,
|
||||||
|
command: copyCommand
|
||||||
|
});
|
||||||
|
if (isDev) {
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: database.destinationDockerId,
|
||||||
|
command: `docker cp ${database.id}:${backupFileName} /app/backups/`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const stream = fsNormal.createReadStream(backupFileName);
|
||||||
|
reply.header('Content-Type', 'application/octet-stream');
|
||||||
|
reply.header('Content-Disposition', `attachment; filename=${fileName}`);
|
||||||
|
reply.header('Content-Length', fsNormal.statSync(backupFileName).size);
|
||||||
|
reply.header('Content-Transfer-Encoding', 'binary');
|
||||||
|
return reply.send(stream)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,33 +1,37 @@
|
|||||||
import fp from 'fastify-plugin'
|
import fp from 'fastify-plugin';
|
||||||
import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt'
|
import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt';
|
||||||
|
|
||||||
declare module "@fastify/jwt" {
|
declare module '@fastify/jwt' {
|
||||||
interface FastifyJWT {
|
interface FastifyJWT {
|
||||||
user: {
|
user: {
|
||||||
userId: string,
|
userId: string;
|
||||||
teamId: string,
|
teamId: string;
|
||||||
permission: string,
|
permission: string;
|
||||||
isAdmin: boolean
|
isAdmin: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default fp<FastifyJWTOptions>(async (fastify, opts) => {
|
export default fp<FastifyJWTOptions>(async (fastify, opts) => {
|
||||||
fastify.register(fastifyJwt, {
|
let secretKey = fastify.config.COOLIFY_SECRET_KEY_BETTER;
|
||||||
secret: fastify.config.COOLIFY_SECRET_KEY
|
if (!secretKey) {
|
||||||
})
|
secretKey = fastify.config.COOLIFY_SECRET_KEY;
|
||||||
|
|
||||||
fastify.decorate("authenticate", async function (request, reply) {
|
|
||||||
try {
|
|
||||||
await request.jwtVerify()
|
|
||||||
} catch (err) {
|
|
||||||
reply.send(err)
|
|
||||||
}
|
}
|
||||||
})
|
fastify.register(fastifyJwt, {
|
||||||
})
|
secret: secretKey
|
||||||
|
});
|
||||||
|
|
||||||
|
fastify.decorate('authenticate', async function (request, reply) {
|
||||||
|
try {
|
||||||
|
await request.jwtVerify();
|
||||||
|
} catch (err) {
|
||||||
|
reply.send(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
export interface FastifyInstance {
|
export interface FastifyInstance {
|
||||||
authenticate(): Promise<void>
|
authenticate(): Promise<void>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -646,8 +646,7 @@ export async function restartApplication(
|
|||||||
|
|
||||||
const volumes =
|
const volumes =
|
||||||
persistentStorage?.map((storage) => {
|
persistentStorage?.map((storage) => {
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||||
}${storage.path}`;
|
|
||||||
}) || [];
|
}) || [];
|
||||||
const composeVolumes = volumes.map((volume) => {
|
const composeVolumes = volumes.map((volume) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import yaml from 'js-yaml';
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import {
|
import {
|
||||||
ComposeFile,
|
ComposeFile,
|
||||||
|
backupPostgresqlDatabase,
|
||||||
createDirectories,
|
createDirectories,
|
||||||
decrypt,
|
decrypt,
|
||||||
defaultComposeConfiguration,
|
defaultComposeConfiguration,
|
||||||
@@ -351,6 +352,21 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function backupDatabase(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const { id } = request.params;
|
||||||
|
const database = await prisma.database.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
|
include: { destinationDocker: true, settings: true }
|
||||||
|
});
|
||||||
|
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||||
|
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||||
|
return await backupPostgresqlDatabase(database, reply);
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function stopDatabase(request: FastifyRequest<OnlyId>) {
|
export async function stopDatabase(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
import { backupDatabase, cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
|
|
||||||
@@ -39,6 +39,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
|
|
||||||
fastify.post<OnlyId>('/:id/start', async (request) => await startDatabase(request));
|
fastify.post<OnlyId>('/:id/start', async (request) => await startDatabase(request));
|
||||||
fastify.post<OnlyId>('/:id/stop', async (request) => await stopDatabase(request));
|
fastify.post<OnlyId>('/:id/stop', async (request) => await stopDatabase(request));
|
||||||
|
fastify.post<OnlyId>('/:id/backup', async (request, reply) => await backupDatabase(request, reply));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import type {
|
|||||||
Proxy,
|
Proxy,
|
||||||
SaveDestinationSettings
|
SaveDestinationSettings
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import { removeService } from '../../../../lib/services/common';
|
||||||
|
|
||||||
export async function listDestinations(request: FastifyRequest<ListDestinations>) {
|
export async function listDestinations(request: FastifyRequest<ListDestinations>) {
|
||||||
try {
|
try {
|
||||||
@@ -143,6 +144,35 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
|
|||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function forceDeleteDestination(request: FastifyRequest<OnlyId>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const services = await prisma.service.findMany({ where: { destinationDockerId: id } });
|
||||||
|
for (const service of services) {
|
||||||
|
await removeService({ id: service.id });
|
||||||
|
}
|
||||||
|
const applications = await prisma.application.findMany({ where: { destinationDockerId: id } });
|
||||||
|
for (const application of applications) {
|
||||||
|
await prisma.applicationSettings.deleteMany({ where: { application: { id: application.id } } });
|
||||||
|
await prisma.buildLog.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
await prisma.build.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
await prisma.secret.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
await prisma.applicationConnectedDatabase.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
await prisma.previewApplication.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
}
|
||||||
|
const databases = await prisma.database.findMany({ where: { destinationDockerId: id } });
|
||||||
|
for (const database of databases) {
|
||||||
|
await prisma.databaseSettings.deleteMany({ where: { databaseId: database.id } });
|
||||||
|
await prisma.databaseSecret.deleteMany({ where: { databaseId: database.id } });
|
||||||
|
await prisma.database.delete({ where: { id: database.id } });
|
||||||
|
}
|
||||||
|
await prisma.destinationDocker.delete({ where: { id } });
|
||||||
|
return {};
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
|
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
@@ -318,6 +348,7 @@ export async function verifyRemoteDockerEngineFn(id: string) {
|
|||||||
}
|
}
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } });
|
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
throw new Error('Error while verifying remote docker engine');
|
throw new Error('Error while verifying remote docker engine');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { assignSSHKey, checkDestination, deleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
|
import { assignSSHKey, checkDestination, deleteDestination, forceDeleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
||||||
@@ -14,6 +14,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
|
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
|
||||||
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
|
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
|
||||||
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
|
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
|
||||||
|
fastify.delete<OnlyId>('/:id/force', async (request) => await forceDeleteDestination(request));
|
||||||
fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request));
|
fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request));
|
||||||
|
|
||||||
fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));
|
fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
prisma,
|
prisma,
|
||||||
uniqueName,
|
uniqueName,
|
||||||
version,
|
version,
|
||||||
sentryDSN,
|
|
||||||
executeCommand
|
executeCommand
|
||||||
} from '../../../lib/common';
|
} from '../../../lib/common';
|
||||||
import { scheduler } from '../../../lib/scheduler';
|
import { scheduler } from '../../../lib/scheduler';
|
||||||
@@ -164,7 +163,7 @@ export async function update(request: FastifyRequest<Update>) {
|
|||||||
await executeCommand({ command: `docker pull ${image}` });
|
await executeCommand({ command: `docker pull ${image}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
await executeCommand({ shell: true, command: `env | grep COOLIFY > .env` });
|
await executeCommand({ shell: true, command: `ls .env || env | grep COOLIFY > .env` });
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||||
});
|
});
|
||||||
@@ -452,7 +451,6 @@ export async function getCurrentUser(request: FastifyRequest<GetCurrentUser>, fa
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
settings: await prisma.setting.findUnique({ where: { id: '0' } }),
|
settings: await prisma.setting.findUnique({ where: { id: '0' } }),
|
||||||
sentryDSN,
|
|
||||||
pendingInvitations,
|
pendingInvitations,
|
||||||
token,
|
token,
|
||||||
...request.user
|
...request.user
|
||||||
|
|||||||
@@ -1,34 +1,65 @@
|
|||||||
import { promises as dns } from 'dns';
|
import { promises as dns } from 'dns';
|
||||||
import { X509Certificate } from 'node:crypto';
|
import { X509Certificate } from 'node:crypto';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, executeCommand, getDomain, isDev, isDNSValid, isDomainConfigured, listSettings, prisma, sentryDSN, version } from '../../../../lib/common';
|
import {
|
||||||
import { AddDefaultRegistry, CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey, SetDefaultRegistry } from './types';
|
checkDomainsIsValidInDNS,
|
||||||
|
decrypt,
|
||||||
|
encrypt,
|
||||||
|
errorHandler,
|
||||||
|
executeCommand,
|
||||||
|
getDomain,
|
||||||
|
isDev,
|
||||||
|
isDNSValid,
|
||||||
|
isDomainConfigured,
|
||||||
|
listSettings,
|
||||||
|
prisma
|
||||||
|
} from '../../../../lib/common';
|
||||||
|
import {
|
||||||
|
AddDefaultRegistry,
|
||||||
|
CheckDNS,
|
||||||
|
CheckDomain,
|
||||||
|
DeleteDomain,
|
||||||
|
OnlyIdInBody,
|
||||||
|
SaveSettings,
|
||||||
|
SaveSSHKey,
|
||||||
|
SetDefaultRegistry
|
||||||
|
} from './types';
|
||||||
|
|
||||||
export async function listAllSettings(request: FastifyRequest) {
|
export async function listAllSettings(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } })
|
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } });
|
||||||
let registries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId } } })
|
let registries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId } } });
|
||||||
registries = registries.map((registry) => {
|
registries = registries.map((registry) => {
|
||||||
if (registry.password) {
|
if (registry.password) {
|
||||||
registry.password = decrypt(registry.password)
|
registry.password = decrypt(registry.password);
|
||||||
}
|
}
|
||||||
return registry
|
return registry;
|
||||||
})
|
});
|
||||||
const unencryptedKeys = []
|
const unencryptedKeys = [];
|
||||||
if (sshKeys.length > 0) {
|
if (sshKeys.length > 0) {
|
||||||
for (const key of sshKeys) {
|
for (const key of sshKeys) {
|
||||||
unencryptedKeys.push({ id: key.id, name: key.name, privateKey: decrypt(key.privateKey), createdAt: key.createdAt })
|
unencryptedKeys.push({
|
||||||
|
id: key.id,
|
||||||
|
name: key.name,
|
||||||
|
privateKey: decrypt(key.privateKey),
|
||||||
|
createdAt: key.createdAt
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const certificates = await prisma.certificate.findMany({ where: { team: { id: teamId } } })
|
const certificates = await prisma.certificate.findMany({ where: { team: { id: teamId } } });
|
||||||
let cns = [];
|
let cns = [];
|
||||||
for (const certificate of certificates) {
|
for (const certificate of certificates) {
|
||||||
const x509 = new X509Certificate(certificate.cert);
|
const x509 = new X509Certificate(certificate.cert);
|
||||||
cns.push({ commonName: x509.subject.split('\n').find((s) => s.startsWith('CN=')).replace('CN=', ''), id: certificate.id, createdAt: certificate.createdAt })
|
cns.push({
|
||||||
|
commonName: x509.subject
|
||||||
|
.split('\n')
|
||||||
|
.find((s) => s.startsWith('CN='))
|
||||||
|
.replace('CN=', ''),
|
||||||
|
id: certificate.id,
|
||||||
|
createdAt: certificate.createdAt
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -36,9 +67,9 @@ export async function listAllSettings(request: FastifyRequest) {
|
|||||||
certificates: cns,
|
certificates: cns,
|
||||||
sshKeys: unencryptedKeys,
|
sshKeys: unencryptedKeys,
|
||||||
registries
|
registries
|
||||||
}
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) {
|
export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) {
|
||||||
@@ -57,30 +88,48 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
|||||||
isDNSCheckEnabled,
|
isDNSCheckEnabled,
|
||||||
DNSServers,
|
DNSServers,
|
||||||
proxyDefaultRedirect
|
proxyDefaultRedirect
|
||||||
} = request.body
|
} = request.body;
|
||||||
const { id, previewSeparator: SetPreviewSeparator } = await listSettings();
|
const { id, previewSeparator: SetPreviewSeparator } = await listSettings();
|
||||||
if (numberOfDockerImagesKeptLocally) {
|
if (numberOfDockerImagesKeptLocally) {
|
||||||
numberOfDockerImagesKeptLocally = Number(numberOfDockerImagesKeptLocally)
|
numberOfDockerImagesKeptLocally = Number(numberOfDockerImagesKeptLocally);
|
||||||
}
|
}
|
||||||
if (previewSeparator == '') {
|
if (previewSeparator == '') {
|
||||||
previewSeparator = '.'
|
previewSeparator = '.';
|
||||||
}
|
}
|
||||||
if (SetPreviewSeparator != previewSeparator) {
|
if (SetPreviewSeparator != previewSeparator) {
|
||||||
const applications = await prisma.application.findMany({ where: { previewApplication: { some: { id: { not: undefined } } } }, include: { previewApplication: true } })
|
const applications = await prisma.application.findMany({
|
||||||
|
where: { previewApplication: { some: { id: { not: undefined } } } },
|
||||||
|
include: { previewApplication: true }
|
||||||
|
});
|
||||||
for (const application of applications) {
|
for (const application of applications) {
|
||||||
for (const preview of application.previewApplication) {
|
for (const preview of application.previewApplication) {
|
||||||
const { protocol } = new URL(preview.customDomain)
|
const { protocol } = new URL(preview.customDomain);
|
||||||
const { pullmergeRequestId } = preview
|
const { pullmergeRequestId } = preview;
|
||||||
const { fqdn } = application
|
const { fqdn } = application;
|
||||||
const newPreviewDomain = `${protocol}//${pullmergeRequestId}${previewSeparator}${getDomain(fqdn)}`
|
const newPreviewDomain = `${protocol}//${pullmergeRequestId}${previewSeparator}${getDomain(
|
||||||
await prisma.previewApplication.update({ where: { id: preview.id }, data: { customDomain: newPreviewDomain } })
|
fqdn
|
||||||
|
)}`;
|
||||||
|
await prisma.previewApplication.update({
|
||||||
|
where: { id: preview.id },
|
||||||
|
data: { customDomain: newPreviewDomain }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.setting.update({
|
await prisma.setting.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { previewSeparator, numberOfDockerImagesKeptLocally, doNotTrack, isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers, isAPIDebuggingEnabled }
|
data: {
|
||||||
|
previewSeparator,
|
||||||
|
numberOfDockerImagesKeptLocally,
|
||||||
|
doNotTrack,
|
||||||
|
isRegistrationEnabled,
|
||||||
|
dualCerts,
|
||||||
|
isAutoUpdateEnabled,
|
||||||
|
isDNSCheckEnabled,
|
||||||
|
DNSServers,
|
||||||
|
isAPIDebuggingEnabled
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||||
@@ -97,14 +146,14 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
|||||||
// });
|
// });
|
||||||
// console.log('Sentry initialized')
|
// console.log('Sentry initialized')
|
||||||
}
|
}
|
||||||
return reply.code(201).send()
|
return reply.code(201).send();
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
|
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { fqdn } = request.body
|
const { fqdn } = request.body;
|
||||||
const { DNSServers } = await listSettings();
|
const { DNSServers } = await listSettings();
|
||||||
if (DNSServers) {
|
if (DNSServers) {
|
||||||
dns.setServers([...DNSServers.split(',')]);
|
dns.setServers([...DNSServers.split(',')]);
|
||||||
@@ -116,37 +165,37 @@ export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply:
|
|||||||
// Do not care.
|
// Do not care.
|
||||||
}
|
}
|
||||||
await prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
await prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
||||||
return reply.redirect(302, ip ? `http://${ip[0]}:3000/settings` : undefined)
|
return reply.redirect(302, ip ? `http://${ip[0]}:3000/settings` : undefined);
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body
|
let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body;
|
||||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||||
const found = await isDomainConfigured({ id, fqdn });
|
const found = await isDomainConfigured({ id, fqdn });
|
||||||
if (found) {
|
if (found) {
|
||||||
throw { message: "Domain already configured" };
|
throw { message: 'Domain already configured' };
|
||||||
}
|
}
|
||||||
if (isDNSCheckEnabled && !forceSave && !isDev) {
|
if (isDNSCheckEnabled && !forceSave && !isDev) {
|
||||||
const hostname = request.hostname.split(':')[0]
|
const hostname = request.hostname.split(':')[0];
|
||||||
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
||||||
try {
|
try {
|
||||||
const { domain } = request.params;
|
const { domain } = request.params;
|
||||||
await isDNSValid(request.hostname, domain);
|
await isDNSValid(request.hostname, domain);
|
||||||
return {}
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,82 +203,110 @@ export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: Fas
|
|||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { privateKey, name } = request.body;
|
const { privateKey, name } = request.body;
|
||||||
const found = await prisma.sshKey.findMany({ where: { name } })
|
const found = await prisma.sshKey.findMany({ where: { name } });
|
||||||
if (found.length > 0) {
|
if (found.length > 0) {
|
||||||
throw {
|
throw {
|
||||||
message: "Name already used. Choose another one please."
|
message: 'Name already used. Choose another one please.'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
const encryptedSSHKey = encrypt(privateKey);
|
||||||
const encryptedSSHKey = encrypt(privateKey)
|
await prisma.sshKey.create({
|
||||||
await prisma.sshKey.create({ data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } } })
|
data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } }
|
||||||
return reply.code(201).send()
|
});
|
||||||
|
return reply.code(201).send();
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.body;
|
const { id } = request.body;
|
||||||
await prisma.sshKey.deleteMany({ where: { id, teamId } })
|
await prisma.sshKey.deleteMany({ where: { id, teamId } });
|
||||||
return reply.code(201).send()
|
return reply.code(201).send();
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCertificates(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
export async function deleteCertificates(
|
||||||
|
request: FastifyRequest<OnlyIdInBody>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.body;
|
const { id } = request.body;
|
||||||
await executeCommand({ command: `docker exec coolify-proxy sh -c 'rm -f /etc/traefik/acme/custom/${id}-key.pem /etc/traefik/acme/custom/${id}-cert.pem'`, shell: true })
|
await executeCommand({
|
||||||
await prisma.certificate.deleteMany({ where: { id, teamId } })
|
command: `docker exec coolify-proxy sh -c 'rm -f /etc/traefik/acme/custom/${id}-key.pem /etc/traefik/acme/custom/${id}-cert.pem'`,
|
||||||
return reply.code(201).send()
|
shell: true
|
||||||
|
});
|
||||||
|
await prisma.certificate.deleteMany({ where: { id, teamId } });
|
||||||
|
return reply.code(201).send();
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setDockerRegistry(request: FastifyRequest<SetDefaultRegistry>, reply: FastifyReply) {
|
export async function setDockerRegistry(
|
||||||
|
request: FastifyRequest<SetDefaultRegistry>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { id, username, password } = request.body;
|
const { id, username, password } = request.body;
|
||||||
|
|
||||||
let encryptedPassword = ''
|
let encryptedPassword = '';
|
||||||
if (password) encryptedPassword = encrypt(password)
|
if (password) encryptedPassword = encrypt(password);
|
||||||
|
|
||||||
if (teamId === '0') {
|
if (teamId === '0') {
|
||||||
await prisma.dockerRegistry.update({ where: { id }, data: { username, password: encryptedPassword } })
|
await prisma.dockerRegistry.update({
|
||||||
|
where: { id },
|
||||||
|
data: { username, password: encryptedPassword }
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.dockerRegistry.updateMany({ where: { id, teamId }, data: { username, password: encryptedPassword } })
|
await prisma.dockerRegistry.updateMany({
|
||||||
|
where: { id, teamId },
|
||||||
|
data: { username, password: encryptedPassword }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return reply.code(201).send()
|
return reply.code(201).send();
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function addDockerRegistry(request: FastifyRequest<AddDefaultRegistry>, reply: FastifyReply) {
|
export async function addDockerRegistry(
|
||||||
|
request: FastifyRequest<AddDefaultRegistry>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { name, url, username, password } = request.body;
|
const { name, url, username, password } = request.body;
|
||||||
|
|
||||||
let encryptedPassword = ''
|
let encryptedPassword = '';
|
||||||
if (password) encryptedPassword = encrypt(password)
|
if (password) encryptedPassword = encrypt(password);
|
||||||
await prisma.dockerRegistry.create({ data: { name, url, username, password: encryptedPassword, team: { connect: { id: teamId } } } })
|
await prisma.dockerRegistry.create({
|
||||||
|
data: { name, url, username, password: encryptedPassword, team: { connect: { id: teamId } } }
|
||||||
|
});
|
||||||
|
|
||||||
return reply.code(201).send()
|
return reply.code(201).send();
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function deleteDockerRegistry(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
export async function deleteDockerRegistry(
|
||||||
|
request: FastifyRequest<OnlyIdInBody>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.body;
|
const { id } = request.body;
|
||||||
await prisma.application.updateMany({ where: { dockerRegistryId: id }, data: { dockerRegistryId: null } })
|
await prisma.application.updateMany({
|
||||||
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } })
|
where: { dockerRegistryId: id },
|
||||||
return reply.code(201).send()
|
data: { dockerRegistryId: null }
|
||||||
|
});
|
||||||
|
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } });
|
||||||
|
return reply.code(201).send();
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,8 +42,6 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/svelte": "7.21.1",
|
|
||||||
"@sentry/tracing": "7.21.1",
|
|
||||||
"@sveltejs/adapter-static": "1.0.0-next.48",
|
"@sveltejs/adapter-static": "1.0.0-next.48",
|
||||||
"@tailwindcss/typography": "0.5.8",
|
"@tailwindcss/typography": "0.5.8",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import * as Sentry from '@sentry/svelte';
|
|
||||||
export async function handle({ event, resolve }) {
|
export async function handle({ event, resolve }) {
|
||||||
const response = await resolve(event, { ssr: false });
|
const response = await resolve(event, { ssr: false });
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
export const handleError = ({ error, event }) => {
|
export const handleError = ({ error, event }) => {
|
||||||
Sentry.captureException(error, { event });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: 'Whoops!',
|
message: 'Whoops!',
|
||||||
code: error?.code ?? 'UNKNOWN'
|
code: error?.code ?? 'UNKNOWN'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
|
import { dashify } from './common';
|
||||||
|
|
||||||
export function getAPIUrl() {
|
export function getAPIUrl() {
|
||||||
if (GITPOD_WORKSPACE_URL) {
|
if (GITPOD_WORKSPACE_URL) {
|
||||||
@@ -100,6 +101,14 @@ async function send({
|
|||||||
responseData = await response.json();
|
responseData = await response.json();
|
||||||
} else if (contentType?.indexOf('text/plain') !== -1) {
|
} else if (contentType?.indexOf('text/plain') !== -1) {
|
||||||
responseData = await response.text();
|
responseData = await response.text();
|
||||||
|
} else if (contentType?.indexOf('application/octet-stream') !== -1) {
|
||||||
|
responseData = await response.blob();
|
||||||
|
const fileName = dashify(data.id + '-' + data.name)
|
||||||
|
const fileLink = document.createElement('a');
|
||||||
|
fileLink.href = URL.createObjectURL(new Blob([responseData]))
|
||||||
|
fileLink.download = fileName + '.gz';
|
||||||
|
fileLink.click();
|
||||||
|
fileLink.remove();
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let settings: any;
|
export let settings: any;
|
||||||
export let sentryDSN: any;
|
|
||||||
export let baseSettings: any;
|
export let baseSettings: any;
|
||||||
export let pendingInvitations: any = 0;
|
export let pendingInvitations: any = 0;
|
||||||
|
|
||||||
@@ -98,10 +97,6 @@
|
|||||||
import Toasts from '$lib/components/Toasts.svelte';
|
import Toasts from '$lib/components/Toasts.svelte';
|
||||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import LocalePicker from '$lib/components/LocalePicker.svelte';
|
|
||||||
import * as Sentry from '@sentry/svelte';
|
|
||||||
import { BrowserTracing } from '@sentry/tracing';
|
|
||||||
import { dev } from '$app/env';
|
|
||||||
|
|
||||||
if (userId) $appSession.userId = userId;
|
if (userId) $appSession.userId = userId;
|
||||||
if (teamId) $appSession.teamId = teamId;
|
if (teamId) $appSession.teamId = teamId;
|
||||||
@@ -293,7 +288,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
id="documentation"
|
id="documentation"
|
||||||
href="https://docs.coollabs.io/coolify/"
|
href="https://docs.coollabs.io/coolify-v3/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer external"
|
rel="noreferrer external"
|
||||||
class="icons hover:text-info"
|
class="icons hover:text-info"
|
||||||
@@ -498,7 +493,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="no-underline icons hover:text-white hover:bg-info"
|
class="no-underline icons hover:text-white hover:bg-info"
|
||||||
href="https://docs.coollabs.io/coolify/"
|
href="https://docs.coollabs.io/coolify-v3/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer external"
|
rel="noreferrer external"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -255,12 +255,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="flex flex-row items-center">
|
<div class="flex flex-row items-center">
|
||||||
<div class="title py-4 pr-4">Public Repository from Git</div>
|
<div class="title py-4 pr-4">Public Repository from Git</div>
|
||||||
<DocLink url="https://docs.coollabs.io/coolify/applications/#public-repository-from-git" />
|
<DocLink url="https://docs.coollabs.io/coolify-v3/applications/#public-repository-from-git" />
|
||||||
</div>
|
</div>
|
||||||
<PublicRepository />
|
<PublicRepository />
|
||||||
<div class="flex flex-row items-center pt-10">
|
<div class="flex flex-row items-center pt-10">
|
||||||
<div class="title py-4 pr-4">Simple Dockerfile <Beta /></div>
|
<div class="title py-4 pr-4">Simple Dockerfile <Beta /></div>
|
||||||
<DocLink url="https://docs.coollabs.io/coolify/applications/#dockerfile" />
|
<DocLink url="https://docs.coollabs.io/coolify-v3/applications/#simple-dockerfile" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto max-w-screen-2xl">
|
<div class="mx-auto max-w-screen-2xl">
|
||||||
<form class="flex flex-col" on:submit|preventDefault={handleDockerImage}>
|
<form class="flex flex-col" on:submit|preventDefault={handleDockerImage}>
|
||||||
|
|||||||
@@ -13,17 +13,19 @@
|
|||||||
import Redis from './_Redis.svelte';
|
import Redis from './_Redis.svelte';
|
||||||
import CouchDb from './_CouchDb.svelte';
|
import CouchDb from './_CouchDb.svelte';
|
||||||
import EdgeDB from './_EdgeDB.svelte';
|
import EdgeDB from './_EdgeDB.svelte';
|
||||||
import { post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { addToast, appSession, status } from '$lib/store';
|
import { addToast, appSession, status } from '$lib/store';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
let loading = {
|
let loading = {
|
||||||
main: false,
|
main: false,
|
||||||
public: false
|
public: false,
|
||||||
|
backup: false
|
||||||
};
|
};
|
||||||
let publicUrl = '';
|
let publicUrl = '';
|
||||||
let appendOnly = database.settings.appendOnly;
|
let appendOnly = database.settings.appendOnly;
|
||||||
@@ -109,6 +111,7 @@
|
|||||||
if ($status.database.isPublic) {
|
if ($status.database.isPublic) {
|
||||||
database.publicPort = publicPort;
|
database.publicPort = publicPort;
|
||||||
}
|
}
|
||||||
|
generateUrl();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -130,6 +133,22 @@
|
|||||||
loading.main = false;
|
loading.main = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function backupDatabase() {
|
||||||
|
try {
|
||||||
|
loading.backup = true;
|
||||||
|
addToast({
|
||||||
|
message:
|
||||||
|
'Backup will be downloaded soon and saved to /var/lib/docker/volumes/coolify-local-backup/ on the host system.',
|
||||||
|
type: 'success',
|
||||||
|
timeout: 15000
|
||||||
|
});
|
||||||
|
return await post(`/databases/${id}/backup`, { id, name: database.name });
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading.backup = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-auto max-w-6xl p-4">
|
<div class="mx-auto max-w-6xl p-4">
|
||||||
@@ -144,6 +163,19 @@
|
|||||||
class:bg-databases={!loading.main}
|
class:bg-databases={!loading.main}
|
||||||
disabled={loading.main}>{$t('forms.save')}</button
|
disabled={loading.main}>{$t('forms.save')}</button
|
||||||
>
|
>
|
||||||
|
{#if database.type !== 'redis' && database.type !== 'edgedb'}
|
||||||
|
{#if $status.database.isRunning}
|
||||||
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
|
on:click={backupDatabase}
|
||||||
|
class:loading={loading.backup}
|
||||||
|
class:bg-databases={!loading.backup}
|
||||||
|
disabled={loading.backup}>Backup Database</button
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<button disabled class="btn btn-sm">Backup Database (start the database)</button>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-2 grid-cols-2 auto-rows-max lg:px-10 px-2">
|
<div class="grid gap-2 grid-cols-2 auto-rows-max lg:px-10 px-2">
|
||||||
|
|||||||
@@ -34,14 +34,20 @@
|
|||||||
customClass="max-w-[32rem]"
|
customClass="max-w-[32rem]"
|
||||||
text="Remote Docker Engines are using <span class='text-white font-bold'>SSH</span> to communicate with the remote docker engine.
|
text="Remote Docker Engines are using <span class='text-white font-bold'>SSH</span> to communicate with the remote docker engine.
|
||||||
You need to setup an <span class='text-white font-bold'>SSH key</span> in advance on the server and install Docker.
|
You need to setup an <span class='text-white font-bold'>SSH key</span> in advance on the server and install Docker.
|
||||||
<br>See <a class='text-white' href='https://docs.coollabs.io/coolify/destinations#remote-docker-engine' target='blank'>docs</a> for more details."
|
<br>See <a class='text-white' href='https://docs.coollabs.io-v3/coolify/destinations#remote-docker-engine' target='blank'>docs</a> for more details."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center px-6 pb-8">
|
<div class="flex justify-center px-6 pb-8">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
|
||||||
<div class="flex items-start lg:items-center space-x-0 lg:space-x-4 pb-5 flex-col lg:flex-row space-y-4 lg:space-y-0">
|
<div
|
||||||
|
class="flex items-start lg:items-center space-x-0 lg:space-x-4 pb-5 flex-col lg:flex-row space-y-4 lg:space-y-0"
|
||||||
|
>
|
||||||
<div class="title font-bold">{$t('forms.configuration')}</div>
|
<div class="title font-bold">{$t('forms.configuration')}</div>
|
||||||
<button type="submit" class="btn btn-sm bg-destinations w-full lg:w-fit" class:loading disabled={loading}
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-sm bg-destinations w-full lg:w-fit"
|
||||||
|
class:loading
|
||||||
|
disabled={loading}
|
||||||
>{loading
|
>{loading
|
||||||
? payload.isCoolifyProxyUsed
|
? payload.isCoolifyProxyUsed
|
||||||
? $t('destination.new.saving_and_configuring_proxy')
|
? $t('destination.new.saving_and_configuring_proxy')
|
||||||
|
|||||||
@@ -75,6 +75,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function forceDeleteDestination(destination: any) {
|
||||||
|
let sure = confirm($t('application.confirm_to_delete', { name: destination.name }));
|
||||||
|
if (sure) {
|
||||||
|
sure = confirm(
|
||||||
|
'Are you REALLY sure? This will delete all resources associated with this destination, but not on the destination (server) itself. You will have manually delete everything on the server afterwards.'
|
||||||
|
);
|
||||||
|
if (sure) {
|
||||||
|
sure = confirm('REALLY?');
|
||||||
|
if (sure) {
|
||||||
|
try {
|
||||||
|
await del(`/destinations/${destination.id}/force`, { id: destination.id });
|
||||||
|
return await goto('/', { replaceState: true });
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
function deletable() {
|
function deletable() {
|
||||||
if (!isDestinationDeletable) {
|
if (!isDestinationDeletable) {
|
||||||
return 'Please delete all resources before deleting this.';
|
return 'Please delete all resources before deleting this.';
|
||||||
@@ -88,7 +107,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $page.params.id !== 'new'}
|
{#if $page.params.id !== 'new'}
|
||||||
<nav class="header lg:flex-row flex-col-reverse">
|
<nav class="header lg:flex-row flex-col-reverse gap-2">
|
||||||
<div class="flex flex-row space-x-2 font-bold pt-10 lg:pt-0">
|
<div class="flex flex-row space-x-2 font-bold pt-10 lg:pt-0">
|
||||||
<div class="flex flex-col items-center justify-center title">
|
<div class="flex flex-col items-center justify-center title">
|
||||||
{#if $page.url.pathname === `/destinations/${$page.params.id}`}
|
{#if $page.url.pathname === `/destinations/${$page.params.id}`}
|
||||||
@@ -111,6 +130,16 @@
|
|||||||
>
|
>
|
||||||
<Tooltip triggeredBy="#delete">{deletable()}</Tooltip>
|
<Tooltip triggeredBy="#delete">{deletable()}</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-row flex-wrap justify-center lg:justify-start lg:py-0 items-center">
|
||||||
|
<button
|
||||||
|
id="forceDelete"
|
||||||
|
on:click={() => forceDeleteDestination(destination)}
|
||||||
|
type="submit"
|
||||||
|
disabled={!$appSession.isAdmin && isDestinationDeletable}
|
||||||
|
class="icons bg-transparent text-sm text-red-500"><DeleteIcon /></button
|
||||||
|
>
|
||||||
|
<Tooltip triggeredBy="#forceDelete">Force Delete</Tooltip>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
{/if}
|
{/if}
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
appSecret: source.gitlabApp.appSecret,
|
appSecret: source.gitlabApp.appSecret,
|
||||||
groupName: source.gitlabApp.groupName,
|
groupName: source.gitlabApp.groupName,
|
||||||
customPort: source.customPort,
|
customPort: source.customPort,
|
||||||
customUser: source.customUser,
|
customUser: source.customUser
|
||||||
});
|
});
|
||||||
const from = $page.url.searchParams.get('from');
|
const from = $page.url.searchParams.get('from');
|
||||||
if (from) {
|
if (from) {
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
<div class="grid grid-flow-row gap-2 lg:px-10">
|
<div class="grid grid-flow-row gap-2 lg:px-10">
|
||||||
{#if !source.gitlabAppId}
|
{#if !source.gitlabAppId}
|
||||||
<a
|
<a
|
||||||
href="https://docs.coollabs.io/coolify/sources#how-to-integrate-with-gitlab"
|
href="https://docs.coollabs.io-v3/coolify/sources#how-to-integrate-with-gitlab"
|
||||||
class="font-bold"
|
class="font-bold"
|
||||||
target="_blank noreferrer"
|
target="_blank noreferrer"
|
||||||
rel="noopener noreferrer">Documentation and detailed instructions.</a
|
rel="noopener noreferrer">Documentation and detailed instructions.</a
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "3.12.31",
|
"version": "3.12.34",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": "github:coollabsio/coolify",
|
"repository": "github:coollabsio/coolify",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"build:api": "NODE_ENV=production pnpm run --filter api build",
|
"build:api": "NODE_ENV=production pnpm run --filter api build",
|
||||||
"build:ui": "NODE_ENV=production pnpm run --filter ui build",
|
"build:ui": "NODE_ENV=production pnpm run --filter ui build",
|
||||||
"dockerlogin": "echo $DOCKER_PASS | docker login --username=$DOCKER_USER --password-stdin",
|
"dockerlogin": "echo $DOCKER_PASS | docker login --username=$DOCKER_USER --password-stdin",
|
||||||
"release:staging:amd": "docker build -t ghcr.io/coollabsio/coolify:next . && docker push ghcr.io/coollabsio/coolify:next",
|
"release:staging:amd": "docker build -t ghcr.io/coollabsio/coolify:v3 . && docker push ghcr.io/coollabsio/coolify:v3",
|
||||||
"release:local": "rm -fr ./local-serve && mkdir ./local-serve && pnpm build && cp -Rp apps/api/build/* ./local-serve && cp -Rp apps/ui/build/ ./local-serve/public && cp -Rp apps/api/prisma/ ./local-serve/prisma && cp -Rp apps/api/package.json ./local-serve && env | grep '^COOLIFY_' > ./local-serve/.env && cd ./local-serve && pnpm install . && pnpm start"
|
"release:local": "rm -fr ./local-serve && mkdir ./local-serve && pnpm build && cp -Rp apps/api/build/* ./local-serve && cp -Rp apps/ui/build/ ./local-serve/public && cp -Rp apps/api/prisma/ ./local-serve/prisma && cp -Rp apps/api/package.json ./local-serve && env | grep '^COOLIFY_' > ./local-serve/.env && cd ./local-serve && pnpm install . && pnpm start"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Reference in New Issue
Block a user