const dotEnvExtended = require('dotenv-extended'); dotEnvExtended.load(); const crypto = require('crypto'); const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); const algorithm = 'aes-256-ctr'; async function main() { // Enable registration for the first user const settingsFound = await prisma.setting.findFirst({}); if (!settingsFound) { await prisma.setting.create({ data: { id: '0', arch: process.arch } }); } else { await prisma.setting.update({ where: { id: settingsFound.id }, data: { id: '0' } }); } // Create local docker engine const localDocker = await prisma.destinationDocker.findFirst({ where: { engine: '/var/run/docker.sock' } }); if (!localDocker) { await prisma.destinationDocker.create({ data: { engine: '/var/run/docker.sock', name: 'Local Docker', isCoolifyProxyUsed: true, network: 'coolify' } }); } // Set auto-update based on env variable const isAutoUpdateEnabled = process.env['COOLIFY_AUTO_UPDATE'] === 'true'; await prisma.setting.update({ where: { id: '0' }, data: { isAutoUpdateEnabled } }); // Create public github source const github = await prisma.gitSource.findFirst({ where: { htmlUrl: 'https://github.com', forPublic: true } }); if (!github) { await prisma.gitSource.create({ data: { apiUrl: 'https://api.github.com', htmlUrl: 'https://github.com', forPublic: true, name: 'Github Public', type: 'github' } }); } // Create public gitlab source const gitlab = await prisma.gitSource.findFirst({ where: { htmlUrl: 'https://gitlab.com', forPublic: true } }); if (!gitlab) { await prisma.gitSource.create({ data: { apiUrl: 'https://gitlab.com/api/v4', htmlUrl: 'https://gitlab.com', forPublic: true, name: 'Gitlab Public', type: 'gitlab' } }); } // Set new preview secrets const secrets = await prisma.secret.findMany({ where: { isPRMRSecret: false } }); if (secrets.length > 0) { for (const secret of secrets) { const previewSecrets = await prisma.secret.findMany({ where: { applicationId: secret.applicationId, name: secret.name, isPRMRSecret: true } }); if (previewSecrets.length === 0) { await prisma.secret.create({ data: { ...secret, id: undefined, isPRMRSecret: true } }); } } } } async function reEncryptSecrets() { const { execaCommand } = await import('execa'); const image = await execaCommand("docker inspect coolify --format '{{ .Config.Image }}'", { shell: true }); const version = image.stdout.split(':')[1] ?? null; const date = new Date().getTime(); let backupfile = `/app/db/prod.db_${date}`; if (version) { backupfile = `/app/db/prod.db_${version}_${date}`; } await execaCommand('env | grep "^COOLIFY" | sort > .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(`Backup database to ${backupfile}.`); await execaCommand(`cp /app/db/prod.db ${backupfile}`, { shell: true }); 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 }); const transactions = []; const secrets = await prisma.secret.findMany(); if (secrets.length > 0) { for (const secret of secrets) { try { const value = decrypt(secret.value, secretOld); const newValue = encrypt(value, secretNew); transactions.push( prisma.secret.update({ where: { id: secret.id }, data: { value: newValue } }) ); } catch (e) { console.log(e); } } } const serviceSecrets = await prisma.serviceSecret.findMany(); if (serviceSecrets.length > 0) { for (const secret of serviceSecrets) { try { const value = decrypt(secret.value, secretOld); const newValue = encrypt(value, secretNew); transactions.push( prisma.serviceSecret.update({ where: { id: secret.id }, data: { value: newValue } }) ); } catch (e) { console.log(e); } } } const gitlabApps = await prisma.gitlabApp.findMany(); if (gitlabApps.length > 0) { for (const gitlabApp of gitlabApps) { try { 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 } }) ); } catch (e) { console.log(e); } } } const githubApps = await prisma.githubApp.findMany(); if (githubApps.length > 0) { for (const githubApp of githubApps) { try { 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 } }) ); } catch (e) { console.log(e); } } } const databases = await prisma.database.findMany(); if (databases.length > 0) { for (const database of databases) { try { 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 } }) ); } catch (e) { console.log(e); } } } const databaseSecrets = await prisma.databaseSecret.findMany(); if (databaseSecrets.length > 0) { for (const databaseSecret of databaseSecrets) { try { const value = decrypt(databaseSecret.value, secretOld); const newValue = encrypt(value, secretNew); transactions.push( prisma.databaseSecret.update({ where: { id: databaseSecret.id }, data: { value: newValue } }) ); } catch (e) { console.log(e); } } } const wordpresses = await prisma.wordpress.findMany(); if (wordpresses.length > 0) { for (const wordpress of wordpresses) { try { 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 } }) ); } catch (e) { console.log(e); } } } const sshKeys = await prisma.sshKey.findMany(); if (sshKeys.length > 0) { for (const key of sshKeys) { try { const value = decrypt(key.privateKey, secretOld); const newValue = encrypt(value, secretNew); transactions.push( prisma.sshKey.update({ where: { id: key.id }, data: { privateKey: newValue } }) ); } catch (e) { console.log(e); } } } const dockerRegistries = await prisma.dockerRegistry.findMany(); if (dockerRegistries.length > 0) { for (const registry of dockerRegistries) { try { const value = decrypt(registry.password, secretOld); const newValue = encrypt(value, secretNew); transactions.push( prisma.dockerRegistry.update({ where: { id: registry.id }, data: { password: newValue } }) ); } catch (e) { console.log(e); } } } const certificates = await prisma.certificate.findMany(); if (certificates.length > 0) { for (const certificate of certificates) { try { const value = decrypt(certificate.key, secretOld); const newValue = encrypt(value, secretNew); transactions.push( prisma.certificate.update({ where: { id: certificate.id }, data: { key: newValue } }) ); } catch (e) { console.log(e); } } } 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) { 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() ]); if (/�/.test(decrpyted.toString())) { throw new Error('Invalid secret. Skipping...'); } return decrpyted.toString(); } }; main() .catch((e) => { console.error(e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); }); reEncryptSecrets() .catch((e) => { console.error(e); }) .finally(async () => { await prisma.$disconnect(); });