Just v2
This commit is contained in:
Andras Bacsai
2022-02-10 15:47:44 +01:00
committed by GitHub
parent a64b095c13
commit 460ae85226
403 changed files with 22039 additions and 12465 deletions

View File

@@ -0,0 +1,230 @@
import { decrypt, encrypt } from '$lib/crypto';
import { removeProxyConfiguration } from '$lib/haproxy';
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
import { getDomain, removeDestinationDocker } from '$lib/common';
import { prisma } from './common';
export async function listApplications(teamId) {
return await prisma.application.findMany({ where: { teams: { some: { id: teamId } } } });
}
export async function newApplication({ name, teamId }) {
return await prisma.application.create({
data: {
name,
teams: { connect: { id: teamId } },
settings: { create: { debug: false, previews: false } }
}
});
}
export async function importApplication({
name,
teamId,
fqdn,
port,
buildCommand,
startCommand,
installCommand
}) {
return await prisma.application.create({
data: {
name,
fqdn,
port,
buildCommand,
startCommand,
installCommand,
teams: { connect: { id: teamId } }
}
});
}
export async function removeApplication({ id, teamId }) {
const { fqdn, destinationDockerId, destinationDocker } = await prisma.application.findUnique({
where: { id },
include: { destinationDocker: true }
});
const domain = getDomain(fqdn);
if (destinationDockerId) {
const host = getEngine(destinationDocker.engine);
const { stdout: containers } = await asyncExecShell(
`DOCKER_HOST=${host} docker ps -a --filter network=${destinationDocker.network} --filter name=${id} --format '{{json .}}'`
);
if (containers) {
const containersArray = containers.trim().split('\n');
for (const container of containersArray) {
const containerObj = JSON.parse(container);
const id = containerObj.ID;
const preview = containerObj.Image.split('-')[1];
await removeDestinationDocker({ id, engine: destinationDocker.engine });
if (preview) {
await removeProxyConfiguration({ domain: `${preview}.${domain}` });
} else {
await removeProxyConfiguration({ domain });
}
}
}
}
await prisma.applicationSettings.deleteMany({ where: { application: { id } } });
await prisma.buildLog.deleteMany({ where: { applicationId: id } });
await prisma.secret.deleteMany({ where: { applicationId: id } });
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
}
export async function getApplicationWebhook({ projectId, branch }) {
try {
let body = await prisma.application.findFirst({
where: { projectId, branch },
include: {
destinationDocker: true,
settings: true,
gitSource: { include: { githubApp: true, gitlabApp: true } },
secrets: true
}
});
if (body.gitSource?.githubApp?.clientSecret) {
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
}
if (body.gitSource?.githubApp?.webhookSecret) {
body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret);
}
if (body.gitSource?.githubApp?.privateKey) {
body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey);
}
if (body?.gitSource?.gitlabApp?.appSecret) {
body.gitSource.gitlabApp.appSecret = decrypt(body.gitSource.gitlabApp.appSecret);
}
if (body?.gitSource?.gitlabApp?.webhookToken) {
body.gitSource.gitlabApp.webhookToken = decrypt(body.gitSource.gitlabApp.webhookToken);
}
if (body?.secrets.length > 0) {
body.secrets = body.secrets.map((s) => {
s.value = decrypt(s.value);
return s;
});
}
return { ...body };
} catch (e) {
throw { status: 404, body: { message: e.message } };
}
}
export async function getApplication({ id, teamId }) {
let body = await prisma.application.findFirst({
where: { id, teams: { some: { id: teamId } } },
include: {
destinationDocker: true,
settings: true,
gitSource: { include: { githubApp: true, gitlabApp: true } },
secrets: true
}
});
if (body.gitSource?.githubApp?.clientSecret) {
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
}
if (body.gitSource?.githubApp?.webhookSecret) {
body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret);
}
if (body.gitSource?.githubApp?.privateKey) {
body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey);
}
if (body?.gitSource?.gitlabApp?.appSecret) {
body.gitSource.gitlabApp.appSecret = decrypt(body.gitSource.gitlabApp.appSecret);
}
if (body?.secrets.length > 0) {
body.secrets = body.secrets.map((s) => {
s.value = decrypt(s.value);
return s;
});
}
return { ...body };
}
export async function configureGitRepository({ id, repository, branch, projectId, webhookToken }) {
if (webhookToken) {
const encryptedWebhookToken = encrypt(webhookToken);
return await prisma.application.update({
where: { id },
data: {
repository,
branch,
projectId,
gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } }
}
});
} else {
return await prisma.application.update({
where: { id },
data: { repository, branch, projectId }
});
}
}
export async function configureBuildPack({ id, buildPack }) {
return await prisma.application.update({ where: { id }, data: { buildPack } });
}
export async function configureApplication({
id,
buildPack,
name,
fqdn,
port,
installCommand,
buildCommand,
startCommand,
baseDirectory,
publishDirectory
}) {
return await prisma.application.update({
where: { id },
data: {
buildPack,
fqdn,
port,
installCommand,
buildCommand,
startCommand,
baseDirectory,
publishDirectory,
name
}
});
}
export async function setApplicationSettings({ id, debug, previews }) {
return await prisma.application.update({
where: { id },
data: { settings: { update: { debug, previews } } },
include: { destinationDocker: true }
});
}
export async function createBuild({
id,
applicationId,
destinationDockerId,
gitSourceId,
githubAppId,
gitlabAppId,
type
}) {
return await prisma.build.create({
data: {
id,
applicationId,
destinationDockerId,
gitSourceId,
githubAppId,
gitlabAppId,
status: 'running',
type
}
});
}

View File

@@ -0,0 +1,38 @@
import { getDomain } from '$lib/common';
import { prisma, PrismaErrorHandler } from './common';
export async function isBranchAlreadyUsed({ repository, branch, id }) {
const application = await prisma.application.findUnique({
where: { id },
include: { gitSource: true }
});
return await prisma.application.findFirst({
where: { branch, repository, gitSource: { type: application.gitSource.type }, id: { not: id } }
});
}
export async function isDockerNetworkExists({ network }) {
return await prisma.destinationDocker.findFirst({ where: { network } });
}
export async function isSecretExists({ id, name }) {
return await prisma.secret.findFirst({ where: { name, applicationId: id } });
}
export async function isDomainConfigured({ id, fqdn }) {
const domain = getDomain(fqdn);
const foundApp = await prisma.application.findFirst({
where: { fqdn: { endsWith: domain }, id: { not: id } },
select: { fqdn: true }
});
const foundService = await prisma.service.findFirst({
where: { fqdn: { endsWith: domain }, id: { not: id } },
select: { fqdn: true }
});
const coolifyFqdn = await prisma.setting.findFirst({
where: { fqdn: { endsWith: domain }, id: { not: id } },
select: { fqdn: true }
});
if (foundApp || foundService || coolifyFqdn) return true;
return false;
}

232
src/lib/database/common.ts Normal file
View File

@@ -0,0 +1,232 @@
import { dev } from '$app/env';
import { sentry } from '$lib/common';
import * as Prisma from '@prisma/client';
import { default as ProdPrisma } from '@prisma/client';
import generator from 'generate-password';
import forge from 'node-forge';
export function generatePassword(length = 24) {
return generator.generate({
length,
numbers: true,
strict: true
});
}
let { PrismaClient } = Prisma;
let P = Prisma.Prisma;
if (!dev) {
PrismaClient = ProdPrisma.PrismaClient;
P = ProdPrisma.Prisma;
}
let prismaOptions = {
rejectOnNotFound: false
};
if (dev) {
prismaOptions = {
errorFormat: 'pretty',
rejectOnNotFound: false,
log: [
{
emit: 'event',
level: 'query'
}
]
};
}
export const prisma = new PrismaClient(prismaOptions);
export function PrismaErrorHandler(e) {
sentry.captureException(e);
const payload = {
status: e.status || 500,
body: {
message: 'Ooops, something is not okay, are you okay?',
error: e.error || e.message
}
};
if (e.name === 'NotFoundError') {
payload.status = 404;
}
if (e instanceof P.PrismaClientKnownRequestError) {
if (e.code === 'P2002') {
payload.body.message = 'Already exists. Choose another name.';
}
}
// console.error(e)
return payload;
}
export async function generateSshKeyPair(): Promise<{ publicKey: string; privateKey: string }> {
return await new Promise(async (resolve, reject) => {
forge.pki.rsa.generateKeyPair({ bits: 4096, workers: -1 }, function (err, keys) {
if (keys) {
resolve({
publicKey: forge.ssh.publicKeyToOpenSSH(keys.publicKey),
privateKey: forge.ssh.privateKeyToOpenSSH(keys.privateKey)
});
} else {
reject(keys);
}
});
});
}
export const supportedDatabaseTypesAndVersions = [
{
name: 'mongodb',
fancyName: 'MongoDB',
baseImage: 'bitnami/mongodb',
versions: ['5.0.5', '4.4.11', '4.2.18', '4.0.27']
},
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0.27', '5.7.36'] },
{
name: 'postgresql',
fancyName: 'PostgreSQL',
baseImage: 'bitnami/postgresql',
versions: ['14.1.0', '13.5.0', '12.9.0', '11.14.0', '10.19.0', '9.6.24']
},
{
name: 'redis',
fancyName: 'Redis',
baseImage: 'bitnami/redis',
versions: ['6.2.6', '6.0.16', '5.0.14']
},
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
];
export const supportedServiceTypesAndVersions = [
{
name: 'plausibleanalytics',
fancyName: 'Plausible Analytics',
baseImage: 'plausible/analytics',
versions: ['latest']
},
{ name: 'nocodb', fancyName: 'NocoDB', baseImage: 'nocodb/nocodb', versions: ['latest'] },
{ name: 'minio', fancyName: 'MinIO', baseImage: 'minio/minio', versions: ['latest'] },
{
name: 'vscodeserver',
fancyName: 'VSCode Server',
baseImage: 'codercom/code-server',
versions: ['latest']
},
{
name: 'wordpress',
fancyName: 'Wordpress',
baseImage: 'wordpress',
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3']
}
];
export function getVersions(type) {
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
if (found) {
return found.versions;
}
return [];
}
export function getDatabaseImage(type) {
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
if (found) {
return found.baseImage;
}
return '';
}
export function getServiceImage(type) {
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
if (found) {
return found.baseImage;
}
return '';
}
export function generateDatabaseConfiguration(database) {
const {
id,
dbUser,
dbUserPassword,
rootUser,
rootUserPassword,
defaultDatabase,
version,
type,
settings: { appendOnly }
} = database;
const baseImage = getDatabaseImage(type);
if (type === 'mysql') {
return {
// url: `mysql://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 3306}/${defaultDatabase}`,
privatePort: 3306,
environmentVariables: {
MYSQL_USER: dbUser,
MYSQL_PASSWORD: dbUserPassword,
MYSQL_ROOT_PASSWORD: rootUserPassword,
MYSQL_ROOT_USER: rootUser,
MYSQL_DATABASE: defaultDatabase
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/mysql/data`,
ulimits: {}
};
} else if (type === 'mongodb') {
return {
// url: `mongodb://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 27017}/${defaultDatabase}`,
privatePort: 27017,
environmentVariables: {
MONGODB_ROOT_USER: rootUser,
MONGODB_ROOT_PASSWORD: rootUserPassword
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/mongodb`,
ulimits: {}
};
} else if (type === 'postgresql') {
return {
// url: `psql://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 5432}/${defaultDatabase}`,
privatePort: 5432,
environmentVariables: {
POSTGRESQL_PASSWORD: dbUserPassword,
POSTGRESQL_USERNAME: dbUser,
POSTGRESQL_DATABASE: defaultDatabase
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/postgresql`,
ulimits: {}
};
} else if (type === 'redis') {
return {
// url: `redis://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 6379}/${defaultDatabase}`,
privatePort: 6379,
environmentVariables: {
REDIS_PASSWORD: dbUserPassword,
REDIS_AOF_ENABLED: appendOnly ? 'yes' : 'no'
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/redis/data`,
ulimits: {}
};
} else if (type === 'couchdb') {
return {
// url: `couchdb://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 5984}/${defaultDatabase}`,
privatePort: 5984,
environmentVariables: {
COUCHDB_PASSWORD: dbUserPassword,
COUCHDB_USER: dbUser
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/couchdb`,
ulimits: {}
};
}
// } else if (type === 'clickhouse') {
// return {
// url: `clickhouse://${dbUser}:${dbUserPassword}@${id}:${port}/${defaultDatabase}`,
// privatePort: 9000,
// image: `bitnami/clickhouse-server:${version}`,
// volume: `${id}-${type}-data:/var/lib/clickhouse`,
// ulimits: {
// nofile: {
// soft: 262144,
// hard: 262144
// }
// }
// }
// }
}

View File

@@ -0,0 +1,148 @@
import { decrypt, encrypt } from '$lib/crypto';
import { dockerInstance } from '$lib/docker';
import cuid from 'cuid';
import { generatePassword } from '.';
import { prisma, PrismaErrorHandler } from './common';
import getPort from 'get-port';
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
export async function listDatabases(teamId) {
return await prisma.database.findMany({ where: { teams: { some: { id: teamId } } } });
}
export async function newDatabase({ name, teamId }) {
const dbUser = cuid();
const dbUserPassword = encrypt(generatePassword());
const rootUser = cuid();
const rootUserPassword = encrypt(generatePassword());
const defaultDatabase = cuid();
let publicPort = await getPort();
let i = 0;
do {
const usedPorts = await prisma.database.findMany({ where: { publicPort } });
if (usedPorts.length === 0) break;
publicPort = await getPort();
i++;
} while (i < 10);
if (i === 9) {
throw {
error: 'No free port found!? Is it possible?'
};
}
return await prisma.database.create({
data: {
name,
publicPort,
defaultDatabase,
dbUser,
dbUserPassword,
rootUser,
rootUserPassword,
teams: { connect: { id: teamId } },
settings: { create: { isPublic: false } }
}
});
}
export async function getDatabase({ id, teamId }) {
const body = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId } } },
include: { destinationDocker: true, settings: true }
});
if (body.dbUserPassword) body.dbUserPassword = decrypt(body.dbUserPassword);
if (body.rootUserPassword) body.rootUserPassword = decrypt(body.rootUserPassword);
return { ...body };
}
export async function removeDatabase({ id }) {
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
await prisma.database.delete({ where: { id } });
return;
}
export async function configureDatabaseType({ id, type }) {
return await prisma.database.update({
where: { id },
data: { type }
});
}
export async function setDatabase({
id,
version,
isPublic,
appendOnly
}: {
id: string;
version?: string;
isPublic?: boolean;
appendOnly?: boolean;
}) {
return await prisma.database.update({
where: { id },
data: {
version,
settings: { upsert: { update: { isPublic, appendOnly }, create: { isPublic, appendOnly } } }
}
});
}
export async function updateDatabase({
id,
name,
defaultDatabase,
dbUser,
dbUserPassword,
rootUser,
rootUserPassword,
version
}) {
const encryptedDbUserPassword = dbUserPassword && encrypt(dbUserPassword);
const encryptedRootUserPassword = rootUserPassword && encrypt(rootUserPassword);
return await prisma.database.update({
where: { id },
data: {
name,
defaultDatabase,
dbUser,
dbUserPassword: encryptedDbUserPassword,
rootUser,
rootUserPassword: encryptedRootUserPassword,
version
}
});
}
// export async function setDatabaseSettings({ id, isPublic }) {
// try {
// await prisma.databaseSettings.update({ where: { databaseId: id }, data: { isPublic } })
// return { status: 201 }
// } catch (e) {
// throw PrismaErrorHandler(e)
// }
// }
export async function stopDatabase(database) {
let everStarted = false;
const {
id,
destinationDockerId,
destinationDocker: { engine }
} = database;
if (destinationDockerId) {
try {
const host = getEngine(engine);
const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
);
if (stdout) {
everStarted = true;
await removeContainer(id, engine);
}
} catch (error) {
//
}
}
return everStarted;
}

View File

@@ -0,0 +1,132 @@
import { asyncExecShell, getEngine } from '$lib/common';
import { dockerInstance } from '$lib/docker';
import { defaultProxyImageHttp, defaultProxyImageTcp, startCoolifyProxy } from '$lib/haproxy';
import { getDatabaseImage } from '.';
import { prisma, PrismaErrorHandler } from './common';
export async function listDestinations(teamId) {
return await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId } } } });
}
export async function configureDestinationForService({ id, destinationId }) {
return await prisma.service.update({
where: { id },
data: { destinationDocker: { connect: { id: destinationId } } }
});
}
export async function configureDestinationForApplication({ id, destinationId }) {
return await prisma.application.update({
where: { id },
data: { destinationDocker: { connect: { id: destinationId } } }
});
}
export async function configureDestinationForDatabase({ id, destinationId }) {
await prisma.database.update({
where: { id },
data: { destinationDocker: { connect: { id: destinationId } } }
});
const {
destinationDockerId,
destinationDocker: { engine },
version,
type
} = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } });
if (destinationDockerId) {
const host = getEngine(engine);
if (type && version) {
const baseImage = getDatabaseImage(type);
asyncExecShell(`DOCKER_HOST=${host} docker pull ${baseImage}:${version}`);
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageTcp}`);
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageHttp}`);
asyncExecShell(`DOCKER_HOST=${host} docker pull certbot/certbot:latest`);
asyncExecShell(`DOCKER_HOST=${host} docker pull alpine:latest`);
}
}
}
export async function updateDestination({ id, name, engine, network }) {
return await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
}
export async function newDestination({ name, teamId, engine, network, isCoolifyProxyUsed }) {
const host = getEngine(engine);
const docker = dockerInstance({ destinationDocker: { engine, network } });
const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } });
if (found.length === 0) {
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
}
await prisma.destinationDocker.create({
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
});
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
const destination = destinations.find((destination) => destination.network === network);
if (destinations.length > 0) {
const proxyConfigured = destinations.find(
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
);
if (proxyConfigured) {
if (proxyConfigured.isCoolifyProxyUsed) {
isCoolifyProxyUsed = true;
} else {
isCoolifyProxyUsed = false;
}
}
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
}
if (isCoolifyProxyUsed) await startCoolifyProxy(engine);
return destination.id;
}
export async function removeDestination({ id }) {
const destination = await prisma.destinationDocker.delete({ where: { id } });
if (destination.isCoolifyProxyUsed) {
const host = getEngine(destination.engine);
const { network } = destination;
const { stdout: found } = await asyncExecShell(
`DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=coolify-haproxy --format '{{.}}'`
);
if (found) {
await asyncExecShell(
`DOCKER_HOST="${host}" docker network disconnect ${network} coolify-haproxy`
);
await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`);
}
}
}
export async function getDestination({ id, teamId }) {
return await prisma.destinationDocker.findFirst({
where: { id, teams: { some: { id: teamId } } }
});
}
export async function getDestinationByApplicationId({ id, teamId }) {
return await prisma.destinationDocker.findFirst({
where: { application: { some: { id } }, teams: { some: { id: teamId } } }
});
}
export async function setDestinationSettings({ engine, isCoolifyProxyUsed }) {
return await prisma.destinationDocker.updateMany({
where: { engine },
data: { isCoolifyProxyUsed }
});
// if (isCoolifyProxyUsed) {
// await installCoolifyProxy(engine)
// await configureNetworkCoolifyProxy(engine)
// } else {
// // TODO: must check if other destination is using the proxy??? or not?
// const domain = await prisma.setting.findUnique({ where: { name: 'domain' }, rejectOnNotFound: false })
// if (!domain) {
// await uninstallCoolifyProxy(engine)
// } else {
// return {
// stastus: 500,
// body: {
// message: 'You can not disable the Coolify proxy while the domain is set for Coolify itself.'
// }
// }
// }
// }
}

View File

@@ -0,0 +1,71 @@
import { decrypt, encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common';
export async function listSources(teamId) {
return await prisma.gitSource.findMany({
where: { teams: { some: { id: teamId } } },
include: { githubApp: true, gitlabApp: true }
});
}
export async function newSource({ name, teamId, type, htmlUrl, apiUrl, organization }) {
return await prisma.gitSource.create({
data: {
teams: { connect: { id: teamId } },
name,
type,
htmlUrl,
apiUrl,
organization
}
});
}
export async function removeSource({ id }) {
// TODO: Disconnect application with this sourceId! Maybe not needed?
const source = await prisma.gitSource.delete({
where: { id },
include: { githubApp: true, gitlabApp: true }
});
if (source.githubAppId) await prisma.githubApp.delete({ where: { id: source.githubAppId } });
if (source.gitlabAppId) await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
}
export async function getSource({ id, teamId }) {
let body = await prisma.gitSource.findFirst({
where: { id, teams: { some: { id: teamId } } },
include: { githubApp: true, gitlabApp: true }
});
if (body?.githubApp?.clientSecret)
body.githubApp.clientSecret = decrypt(body.githubApp.clientSecret);
if (body?.githubApp?.webhookSecret)
body.githubApp.webhookSecret = decrypt(body.githubApp.webhookSecret);
if (body?.githubApp?.privateKey) body.githubApp.privateKey = decrypt(body.githubApp.privateKey);
if (body?.gitlabApp?.appSecret) body.gitlabApp.appSecret = decrypt(body.gitlabApp.appSecret);
return body;
}
export async function addSource({ id, appId, teamId, oauthId, groupName, appSecret }) {
const encrptedAppSecret = encrypt(appSecret);
return await prisma.gitlabApp.create({
data: {
teams: { connect: { id: teamId } },
appId,
oauthId,
groupName,
appSecret: encrptedAppSecret,
gitSource: { connect: { id } }
}
});
}
export async function configureGitsource({ id, gitSourceId }) {
return await prisma.application.update({
where: { id },
data: { gitSource: { connect: { id: gitSourceId } } }
});
}
export async function updateGitsource({ id, name }) {
return await prisma.gitSource.update({
where: { id },
data: { name }
});
}

View File

@@ -0,0 +1,44 @@
import { decrypt, encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common';
export async function addInstallation({ gitSourceId, installation_id }) {
const source = await prisma.gitSource.findUnique({
where: { id: gitSourceId },
include: { githubApp: true }
});
return await prisma.githubApp.update({
where: { id: source.githubAppId },
data: { installationId: Number(installation_id) }
});
}
export async function getUniqueGithubApp({ githubAppId }) {
let body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
return body;
}
export async function createGithubApp({
id,
client_id,
slug,
client_secret,
pem,
webhook_secret,
state
}) {
const encryptedClientSecret = encrypt(client_secret);
const encryptedWebhookSecret = encrypt(webhook_secret);
const encryptedPem = encrypt(pem);
return await prisma.githubApp.create({
data: {
appId: id,
name: slug,
clientId: client_id,
clientSecret: encryptedClientSecret,
webhookSecret: encryptedWebhookSecret,
privateKey: encryptedPem,
gitSource: { connect: { id: state } }
}
});
}

View File

@@ -0,0 +1,37 @@
import { encrypt } from '$lib/crypto';
import { generateSshKeyPair, prisma, PrismaErrorHandler } from './common';
export async function updateDeployKey({ id, deployKeyId }) {
const application = await prisma.application.findUnique({
where: { id },
include: { gitSource: { include: { gitlabApp: true } } }
});
return await prisma.gitlabApp.update({
where: { id: application.gitSource.gitlabApp.id },
data: { deployKeyId }
});
}
export async function getSshKey({ id }) {
const application = await prisma.application.findUnique({
where: { id },
include: { gitSource: { include: { gitlabApp: true } } }
});
return { status: 200, body: { publicKey: application.gitSource.gitlabApp.publicSshKey } };
}
export async function generateSshKey({ id }) {
const application = await prisma.application.findUnique({
where: { id },
include: { gitSource: { include: { gitlabApp: true } } }
});
if (!application.gitSource?.gitlabApp?.privateSshKey) {
const keys = await generateSshKeyPair();
const encryptedPrivateKey = encrypt(keys.privateKey);
await prisma.gitlabApp.update({
where: { id: application.gitSource.gitlabApp.id },
data: { privateSshKey: encryptedPrivateKey, publicSshKey: keys.publicKey }
});
return { status: 201, body: { publicKey: keys.publicKey } };
} else {
return { status: 200 };
}
}

14
src/lib/database/index.ts Normal file
View File

@@ -0,0 +1,14 @@
export * from './applications';
export * from './checks';
export * from './common';
export * from './destinations';
export * from './github';
export * from './gitlab';
export * from './gitSources';
export * from './logs';
export * from './settings';
export * from './users';
export * from './teams';
export * from './secrets';
export * from './databases';
export * from './services';

13
src/lib/database/logs.ts Normal file
View File

@@ -0,0 +1,13 @@
import { prisma, PrismaErrorHandler } from './common';
export async function listLogs({ buildId, last = 0 }) {
try {
const body = await prisma.buildLog.findMany({
where: { buildId, time: { gt: last } },
orderBy: { time: 'asc' }
});
return [...body];
} catch (e) {
throw PrismaErrorHandler(e);
}
}

View File

@@ -0,0 +1,21 @@
import { encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common';
export async function listSecrets({ applicationId }) {
return await prisma.secret.findMany({
where: { applicationId },
orderBy: { createdAt: 'desc' },
select: { id: true, createdAt: true, name: true, isBuildSecret: true }
});
}
export async function createSecret({ id, name, value, isBuildSecret }) {
value = encrypt(value);
return await prisma.secret.create({
data: { name, value, isBuildSecret, application: { connect: { id } } }
});
}
export async function removeSecret({ id, name }) {
return await prisma.secret.deleteMany({ where: { applicationId: id, name } });
}

View File

@@ -0,0 +1,137 @@
import { decrypt, encrypt } from '$lib/crypto';
import { dockerInstance } from '$lib/docker';
import cuid from 'cuid';
import { generatePassword } from '.';
import { prisma, PrismaErrorHandler } from './common';
export async function listServices(teamId) {
return await prisma.service.findMany({ where: { teams: { some: { id: teamId } } } });
}
export async function newService({ name, teamId }) {
return await prisma.service.create({ data: { name, teams: { connect: { id: teamId } } } });
}
export async function getService({ id, teamId }) {
const body = await prisma.service.findFirst({
where: { id, teams: { some: { id: teamId } } },
include: {
destinationDocker: true,
plausibleAnalytics: true,
minio: true,
vscodeserver: true,
wordpress: true
}
});
if (body.plausibleAnalytics?.postgresqlPassword)
body.plausibleAnalytics.postgresqlPassword = decrypt(
body.plausibleAnalytics.postgresqlPassword
);
if (body.plausibleAnalytics?.password)
body.plausibleAnalytics.password = decrypt(body.plausibleAnalytics.password);
if (body.plausibleAnalytics?.secretKeyBase)
body.plausibleAnalytics.secretKeyBase = decrypt(body.plausibleAnalytics.secretKeyBase);
if (body.minio?.rootUserPassword)
body.minio.rootUserPassword = decrypt(body.minio.rootUserPassword);
if (body.vscodeserver?.password) body.vscodeserver.password = decrypt(body.vscodeserver.password);
if (body.wordpress?.mysqlPassword)
body.wordpress.mysqlPassword = decrypt(body.wordpress.mysqlPassword);
if (body.wordpress?.mysqlRootUserPassword)
body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword);
return { ...body };
}
export async function configureServiceType({ id, type }) {
if (type === 'plausibleanalytics') {
const password = encrypt(generatePassword());
const postgresqlUser = cuid();
const postgresqlPassword = encrypt(generatePassword());
const postgresqlDatabase = 'plausibleanalytics';
const secretKeyBase = encrypt(generatePassword(64));
await prisma.service.update({
where: { id },
data: {
type,
plausibleAnalytics: {
create: {
postgresqlDatabase,
postgresqlUser,
postgresqlPassword,
password,
secretKeyBase
}
}
}
});
} else if (type === 'nocodb') {
await prisma.service.update({
where: { id },
data: { type }
});
} else if (type === 'minio') {
const rootUser = cuid();
const rootUserPassword = encrypt(generatePassword());
await prisma.service.update({
where: { id },
data: { type, minio: { create: { rootUser, rootUserPassword } } }
});
} else if (type === 'vscodeserver') {
const password = encrypt(generatePassword());
await prisma.service.update({
where: { id },
data: { type, vscodeserver: { create: { password } } }
});
} else if (type === 'wordpress') {
const mysqlUser = cuid();
const mysqlPassword = encrypt(generatePassword());
const mysqlRootUser = cuid();
const mysqlRootUserPassword = encrypt(generatePassword());
await prisma.service.update({
where: { id },
data: {
type,
wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } }
}
});
}
}
export async function setService({ id, version }) {
return await prisma.service.update({
where: { id },
data: { version }
});
}
export async function updatePlausibleAnalyticsService({ id, fqdn, email, username, name }) {
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
await prisma.service.update({ where: { id }, data: { name, fqdn } });
}
export async function updateNocoDbOrMinioService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateVsCodeServer({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateWordpress({ id, fqdn, name, mysqlDatabase, extraConfig }) {
return await prisma.service.update({
where: { id },
data: { fqdn, name, wordpress: { update: { mysqlDatabase, extraConfig } } }
});
}
export async function updateMinioService({ id, publicPort }) {
return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
}
export async function removeService({ id }) {
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
await prisma.minio.deleteMany({ where: { serviceId: id } });
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
await prisma.service.delete({ where: { id } });
}

View File

@@ -0,0 +1,8 @@
import { decrypt } from '$lib/crypto';
import { prisma } from './common';
export async function listSettings() {
let settings = await prisma.setting.findFirst({});
if (settings.proxyPassword) settings.proxyPassword = decrypt(settings.proxyPassword);
return settings;
}

20
src/lib/database/teams.ts Normal file
View File

@@ -0,0 +1,20 @@
import { prisma, PrismaErrorHandler } from './common';
export async function listTeams() {
return await prisma.team.findMany();
}
export async function newTeam({ name, userId }) {
return await prisma.team.create({
data: {
name,
permissions: { create: { user: { connect: { id: userId } }, permission: 'owner' } },
users: { connect: { id: userId } }
}
});
}
export async function getMyTeams({ userId }) {
return await prisma.permission.findMany({
where: { userId },
include: { team: { include: { _count: { select: { users: true } } } } }
});
}

125
src/lib/database/users.ts Normal file
View File

@@ -0,0 +1,125 @@
import cuid from 'cuid';
import bcrypt from 'bcrypt';
import { prisma, PrismaErrorHandler } from './common';
import { asyncExecShell, removeContainer, uniqueName } from '$lib/common';
import * as db from '$lib/database';
import { startCoolifyProxy } from '$lib/haproxy';
export async function login({ email, password }) {
const saltRounds = 15;
const users = await prisma.user.count();
const userFound = await prisma.user.findUnique({
where: { email },
include: { teams: true },
rejectOnNotFound: false
});
// Registration disabled if database is not seeded properly
const { isRegistrationEnabled, id } = await db.listSettings();
let uid = cuid();
// Disable registration if we are registering the first user.
if (users === 0) {
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
// Create default network & start Coolify Proxy
asyncExecShell(`docker network create --attachable coolify`)
.then(() => {
console.log('Network created');
})
.catch(() => {
console.log('Network already exists');
});
startCoolifyProxy('/var/run/docker.sock')
.then(() => {
console.log('Coolify Proxy Started');
})
.catch((err) => {
console.log(err);
});
uid = '0';
}
if (userFound) {
if (userFound.type === 'email') {
const passwordMatch = await bcrypt.compare(password, userFound.password);
if (!passwordMatch) {
throw {
error: 'Wrong password or email address.'
};
}
uid = userFound.id;
}
} else {
// If registration disabled, return 403
if (!isRegistrationEnabled) {
throw {
error: 'Registration disabled by administrator.'
};
}
const hashedPassword = await bcrypt.hash(password, saltRounds);
if (users === 0) {
await prisma.user.create({
data: {
id: uid,
email,
password: hashedPassword,
type: 'email',
teams: {
create: {
id: uid,
name: uniqueName(),
destinationDocker: { connect: { network: 'coolify' } }
}
},
permission: { create: { teamId: uid, permission: 'owner' } }
},
include: { teams: true }
});
} else {
await prisma.user.create({
data: {
id: uid,
email,
password: hashedPassword,
type: 'email',
teams: {
create: {
id: uid,
name: uniqueName()
}
},
permission: { create: { teamId: uid, permission: 'owner' } }
},
include: { teams: true }
});
}
}
// const token = jsonwebtoken.sign({}, secretKey, {
// expiresIn: 15778800,
// algorithm: 'HS256',
// audience: 'coolify',
// issuer: 'coolify',
// jwtid: uid,
// subject: `User:${uid}`,
// notBefore: -1000
// });
return {
status: 200,
headers: {
'Set-Cookie': `teamId=${uid}; HttpOnly; Path=/; Max-Age=15778800;`
},
body: {
uid,
teamId: uid
}
};
}
export async function getUser({ userId }) {
return await prisma.user.findUnique({ where: { id: userId } });
}