Merge branch 'v3' into patch-1

This commit is contained in:
Jenish J
2023-07-17 20:15:27 +05:30
committed by GitHub
21 changed files with 609 additions and 612 deletions

View File

@@ -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 }}

View File

@@ -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:

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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}

View File

@@ -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)

View File

@@ -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",

View File

@@ -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')
});
}
};

View File

@@ -18,7 +18,6 @@ import {
isDev, isDev,
listSettings, listSettings,
prisma, prisma,
sentryDSN,
startTraefikProxy, startTraefikProxy,
startTraefikTCPProxy, startTraefikTCPProxy,
version version
@@ -32,12 +31,12 @@ import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handler
import { checkContainer } from './lib/docker'; import { checkContainer } from './lib/docker';
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib'; import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
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'
@@ -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`
@@ -651,7 +653,7 @@ async function cleanupStorage() {
// } // }
// } catch (error) {} // } catch (error) {}
// if (lowDiskSpace) { // if (lowDiskSpace) {
// await cleanupDockerStorage(destination.id); // await cleanupDockerStorage(destination.id);
// } // }
} }
} }

View File

@@ -8,7 +8,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';
@@ -19,12 +18,11 @@ import { saveBuildLog } from './buildPacks/common';
import { scheduler } from './scheduler'; import { scheduler } from './scheduler';
import type { ExecaChildProcess } from 'execa'; import type { ExecaChildProcess } from 'execa';
export const version = '3.12.32'; export const version = '3.12.33';
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'),
@@ -841,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({
@@ -1678,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 }> {

View File

@@ -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.register(fastifyJwt, {
secret: secretKey
});
fastify.decorate("authenticate", async function (request, reply) { fastify.decorate('authenticate', async function (request, reply) {
try { try {
await request.jwtVerify() await request.jwtVerify();
} catch (err) { } catch (err) {
reply.send(err) reply.send(err);
} }
}) });
}) });
declare module 'fastify' { declare module 'fastify' {
export interface FastifyInstance { export interface FastifyInstance {
authenticate(): Promise<void> authenticate(): Promise<void>;
} }
} }

View File

@@ -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

View File

@@ -1,235 +1,312 @@
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,
const certificates = await prisma.certificate.findMany({ where: { team: { id: teamId } } }) privateKey: decrypt(key.privateKey),
let cns = []; createdAt: key.createdAt
for (const certificate of certificates) { });
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 }) }
} const certificates = await prisma.certificate.findMany({ where: { team: { id: teamId } } });
let cns = [];
for (const certificate of certificates) {
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
});
}
return { return {
settings, settings,
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) {
try { try {
let { let {
previewSeparator, previewSeparator,
numberOfDockerImagesKeptLocally, numberOfDockerImagesKeptLocally,
doNotTrack, doNotTrack,
fqdn, fqdn,
isAPIDebuggingEnabled, isAPIDebuggingEnabled,
isRegistrationEnabled, isRegistrationEnabled,
dualCerts, dualCerts,
minPort, minPort,
maxPort, maxPort,
isAutoUpdateEnabled, isAutoUpdateEnabled,
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({
for (const application of applications) { where: { previewApplication: { some: { id: { not: undefined } } } },
for (const preview of application.previewApplication) { include: { previewApplication: true }
const { protocol } = new URL(preview.customDomain) });
const { pullmergeRequestId } = preview for (const application of applications) {
const { fqdn } = application for (const preview of application.previewApplication) {
const newPreviewDomain = `${protocol}//${pullmergeRequestId}${previewSeparator}${getDomain(fqdn)}` const { protocol } = new URL(preview.customDomain);
await prisma.previewApplication.update({ where: { id: preview.id }, data: { customDomain: newPreviewDomain } }) const { pullmergeRequestId } = preview;
} const { fqdn } = application;
} const newPreviewDomain = `${protocol}//${pullmergeRequestId}${previewSeparator}${getDomain(
} 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,
if (fqdn) { numberOfDockerImagesKeptLocally,
await prisma.setting.update({ where: { id }, data: { fqdn } }); doNotTrack,
} isRegistrationEnabled,
await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } }); dualCerts,
if (minPort && maxPort) { isAutoUpdateEnabled,
await prisma.setting.update({ where: { id }, data: { minPort, maxPort } }); isDNSCheckEnabled,
} DNSServers,
if (doNotTrack === false) { isAPIDebuggingEnabled
// Sentry.init({ }
// dsn: sentryDSN, });
// environment: isDev ? 'development' : 'production', if (fqdn) {
// release: version await prisma.setting.update({ where: { id }, data: { fqdn } });
// }); }
// console.log('Sentry initialized') await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } });
} if (minPort && maxPort) {
return reply.code(201).send() await prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
} catch ({ status, message }) { }
return errorHandler({ status, message }) if (doNotTrack === false) {
} // Sentry.init({
// dsn: sentryDSN,
// environment: isDev ? 'development' : 'production',
// release: version
// });
// console.log('Sentry initialized')
}
return reply.code(201).send();
} catch ({ 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(',')]);
} }
let ip; let ip;
try { try {
ip = await dns.resolve(fqdn); ip = await dns.resolve(fqdn);
} catch (error) { } catch (error) {
// 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 });
} }
} }
export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: FastifyReply) { export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: FastifyReply) {
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({ data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } } }) await prisma.sshKey.create({
return reply.code(201).send() data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } }
} catch ({ status, message }) { });
return errorHandler({ status, message }) return reply.code(201).send();
} } catch ({ 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(
try { request: FastifyRequest<OnlyIdInBody>,
const teamId = request.user.teamId; reply: FastifyReply
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 }) try {
await prisma.certificate.deleteMany({ where: { id, teamId } }) const teamId = request.user.teamId;
return reply.code(201).send() const { id } = request.body;
} catch ({ status, message }) { await executeCommand({
return errorHandler({ status, message }) 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 prisma.certificate.deleteMany({ where: { id, teamId } });
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function setDockerRegistry(request: FastifyRequest<SetDefaultRegistry>, reply: FastifyReply) { export async function setDockerRegistry(
try { request: FastifyRequest<SetDefaultRegistry>,
const teamId = request.user.teamId; reply: FastifyReply
const { id, username, password } = request.body; ) {
try {
const teamId = request.user.teamId;
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({
} else { where: { id },
await prisma.dockerRegistry.updateMany({ where: { id, teamId }, data: { username, password: encryptedPassword } }) data: { username, password: encryptedPassword }
} });
return reply.code(201).send() } else {
} catch ({ status, message }) { await prisma.dockerRegistry.updateMany({
return errorHandler({ status, message }) where: { id, teamId },
} data: { username, password: encryptedPassword }
});
}
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function addDockerRegistry(request: FastifyRequest<AddDefaultRegistry>, reply: FastifyReply) { export async function addDockerRegistry(
try { request: FastifyRequest<AddDefaultRegistry>,
const teamId = request.user.teamId; reply: FastifyReply
const { name, url, username, password } = request.body; ) {
try {
const teamId = request.user.teamId;
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
) {
try {
const teamId = request.user.teamId;
const { id } = request.body;
await prisma.application.updateMany({
where: { dockerRegistryId: id },
data: { dockerRegistryId: null }
});
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } });
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function deleteDockerRegistry(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
try {
const teamId = request.user.teamId;
const { id } = request.body;
await prisma.application.updateMany({ where: { dockerRegistryId: id }, data: { dockerRegistryId: null } })
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } })
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}

View File

@@ -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",

View File

@@ -1,13 +1,10 @@
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 {
message: 'Whoops!',
return { code: error?.code ?? 'UNKNOWN'
message: 'Whoops!', };
code: error?.code ?? 'UNKNOWN' };
};
};

View File

@@ -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"
@@ -384,7 +379,7 @@
</div> </div>
<div class="drawer-side"> <div class="drawer-side">
<label for="main-drawer" class="drawer-overlay w-full" /> <label for="main-drawer" class="drawer-overlay w-full" />
<ul class="menu bg-coolgray-200 w-60 p-2 space-y-3 pt-4 "> <ul class="menu bg-coolgray-200 w-60 p-2 space-y-3 pt-4">
<li> <li>
<a <a
class="no-underline icons hover:text-white hover:bg-pink-500" class="no-underline icons hover:text-white hover:bg-pink-500"
@@ -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"
> >

View File

@@ -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/#simple-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}>

View File

@@ -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')

View File

@@ -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,8 +169,8 @@
<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
> >

View File

@@ -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.32", "version": "3.12.33",
"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": {