Compare commits

...

15 Commits

Author SHA1 Message Date
Andras Bacsai
0097004882 Merge pull request #217 from coollabsio/v2.0.29
v2.0.29
2022-03-12 00:28:26 +01:00
Andras Bacsai
1bc9e4c2d3 fix: Autodeploy true by default for GH repos 2022-03-11 23:56:11 +01:00
Andras Bacsai
36c7e1a3c3 feat: Install pnpm into docker image if pnpm lock file is used 2022-03-11 23:55:57 +01:00
Andras Bacsai
c6b4d04e26 Revert double build 2022-03-11 22:48:55 +01:00
Andras Bacsai
fa6cf068c7 feat: Autodeploy pause 2022-03-11 22:36:21 +01:00
Andras Bacsai
7c273a3a48 feat: Check ssl for new apps/services first 2022-03-11 21:28:27 +01:00
Andras Bacsai
3de2ea1523 chore: version++ 2022-03-11 21:19:03 +01:00
Andras Bacsai
c5c9f84503 feat: Webhooks inititate all applications with the correct branch 2022-03-11 21:18:12 +01:00
Andras Bacsai
16ea9a3e07 Update options request 2022-03-11 20:52:11 +01:00
Andras Bacsai
48f952c798 fix: Personal Gitlab repos 2022-03-11 20:47:26 +01:00
Andras Bacsai
f78ea5de07 Remove colors Tailwind 2022-03-11 20:47:13 +01:00
Andras Bacsai
5adbd5e784 Merge pull request #210 from coollabsio/v2.0.28
v2.0.28
2022-03-04 15:39:35 +01:00
Andras Bacsai
5b2afa79d7 chore: version++ 2022-03-04 15:20:03 +01:00
Andras Bacsai
dc4e6d02b7 feat: Service secrets 2022-03-04 15:14:25 +01:00
Andras Bacsai
8ae61c8f78 fix: do not error if proxy is not running 2022-03-04 14:20:20 +01:00
42 changed files with 664 additions and 96 deletions

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": "2.0.27", "version": "2.0.29",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0", "dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",

View File

@@ -0,0 +1,13 @@
-- CreateTable
CREATE TABLE "ServiceSecret" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"value" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"serviceId" TEXT NOT NULL,
CONSTRAINT "ServiceSecret_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "ServiceSecret_name_serviceId_key" ON "ServiceSecret"("name", "serviceId");

View File

@@ -0,0 +1,19 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_ApplicationSettings" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT NOT NULL,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"debug" BOOLEAN NOT NULL DEFAULT false,
"previews" BOOLEAN NOT NULL DEFAULT false,
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_ApplicationSettings" ("applicationId", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt") SELECT "applicationId", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt" FROM "ApplicationSettings";
DROP TABLE "ApplicationSettings";
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -104,6 +104,7 @@ model ApplicationSettings {
dualCerts Boolean @default(false) dualCerts Boolean @default(false)
debug Boolean @default(false) debug Boolean @default(false)
previews Boolean @default(false) previews Boolean @default(false)
autodeploy Boolean @default(true)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
@@ -122,6 +123,18 @@ model Secret {
@@unique([name, applicationId, isPRMRSecret]) @@unique([name, applicationId, isPRMRSecret])
} }
model ServiceSecret {
id String @id @default(cuid())
name String
value String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
serviceId String
@@unique([name, serviceId])
}
model BuildLog { model BuildLog {
id String @id @default(cuid()) id String @id @default(cuid())
applicationId String? applicationId String?
@@ -252,6 +265,7 @@ model Service {
minio Minio? minio Minio?
vscodeserver Vscodeserver? vscodeserver Vscodeserver?
wordpress Wordpress? wordpress Wordpress?
serviceSecret ServiceSecret[]
} }
model PlausibleAnalytics { model PlausibleAnalytics {

View File

@@ -4,13 +4,19 @@ import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => { const createDockerfile = async (data, image): Promise<void> => {
const { applicationId, tag, port, startCommand, workdir, baseDirectory } = data; const { applicationId, tag, port, startCommand, workdir, baseDirectory } = data;
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
const isPnpm = startCommand.includes('pnpm');
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
Dockerfile.push('WORKDIR /usr/src/app'); Dockerfile.push('WORKDIR /usr/src/app');
Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push(`LABEL coolify.image=true`);
if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
Dockerfile.push('RUN pnpm add -g pnpm');
}
Dockerfile.push( Dockerfile.push(
`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${baseDirectory || ''} ./` `COPY --from=${applicationId}:${tag}-cache /usr/src/app/${baseDirectory || ''} ./`
); );
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD ${startCommand}`); Dockerfile.push(`CMD ${startCommand}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));

View File

@@ -13,7 +13,10 @@ const createDockerfile = async (data, image): Promise<void> => {
pullmergeRequestId pullmergeRequestId
} = data; } = data;
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
const isPnpm =
installCommand.includes('pnpm') ||
buildCommand.includes('pnpm') ||
startCommand.includes('pnpm');
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
Dockerfile.push('WORKDIR /usr/src/app'); Dockerfile.push('WORKDIR /usr/src/app');
Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push(`LABEL coolify.image=true`);
@@ -32,6 +35,10 @@ const createDockerfile = async (data, image): Promise<void> => {
} }
}); });
} }
if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
Dockerfile.push('RUN pnpm add -g pnpm');
}
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
try { try {
await fs.stat(`${workdir}/yarn.lock`); await fs.stat(`${workdir}/yarn.lock`);

View File

@@ -13,7 +13,10 @@ const createDockerfile = async (data, image): Promise<void> => {
pullmergeRequestId pullmergeRequestId
} = data; } = data;
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
const isPnpm =
installCommand.includes('pnpm') ||
buildCommand.includes('pnpm') ||
startCommand.includes('pnpm');
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
Dockerfile.push('WORKDIR /usr/src/app'); Dockerfile.push('WORKDIR /usr/src/app');
Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push(`LABEL coolify.image=true`);
@@ -32,6 +35,10 @@ const createDockerfile = async (data, image): Promise<void> => {
} }
}); });
} }
if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
Dockerfile.push('RUN pnpm add -g pnpm');
}
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
try { try {
await fs.stat(`${workdir}/yarn.lock`); await fs.stat(`${workdir}/yarn.lock`);

View File

@@ -13,7 +13,10 @@ const createDockerfile = async (data, image): Promise<void> => {
pullmergeRequestId pullmergeRequestId
} = data; } = data;
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
const isPnpm =
installCommand.includes('pnpm') ||
buildCommand.includes('pnpm') ||
startCommand.includes('pnpm');
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
Dockerfile.push('WORKDIR /usr/src/app'); Dockerfile.push('WORKDIR /usr/src/app');
Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push(`LABEL coolify.image=true`);
@@ -32,6 +35,10 @@ const createDockerfile = async (data, image): Promise<void> => {
} }
}); });
} }
if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
Dockerfile.push('RUN pnpm add -g pnpm');
}
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
try { try {
await fs.stat(`${workdir}/yarn.lock`); await fs.stat(`${workdir}/yarn.lock`);

View File

@@ -11,6 +11,7 @@ import { version as currentVersion } from '../../package.json';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import Cookie from 'cookie'; import Cookie from 'cookie';
import os from 'os'; import os from 'os';
import cuid from 'cuid';
try { try {
if (!dev) { if (!dev) {

View File

@@ -13,7 +13,7 @@ export function findBuildPack(pack, packageManager = 'npm') {
if (pack === 'node') { if (pack === 'node') {
return { return {
...metaData, ...metaData,
installCommand: null, ...defaultBuildAndDeploy(packageManager),
buildCommand: null, buildCommand: null,
startCommand: null, startCommand: null,
publishDirectory: null, publishDirectory: null,

View File

@@ -58,15 +58,6 @@ export async function removeApplication({ id, teamId }) {
const id = containerObj.ID; const id = containerObj.ID;
const preview = containerObj.Image.split('-')[1]; const preview = containerObj.Image.split('-')[1];
await removeDestinationDocker({ id, engine: destinationDocker.engine }); await removeDestinationDocker({ id, engine: destinationDocker.engine });
try {
if (preview) {
await removeProxyConfiguration({ domain: `${preview}.${domain}` });
} else {
await removeProxyConfiguration({ domain });
}
} catch (error) {
console.log(error);
}
} }
} }
} }
@@ -79,8 +70,8 @@ export async function removeApplication({ id, teamId }) {
export async function getApplicationWebhook({ projectId, branch }) { export async function getApplicationWebhook({ projectId, branch }) {
try { try {
let body = await prisma.application.findFirst({ let application = await prisma.application.findFirst({
where: { projectId, branch }, where: { projectId, branch, settings: { autodeploy: true } },
include: { include: {
destinationDocker: true, destinationDocker: true,
settings: true, settings: true,
@@ -88,30 +79,38 @@ export async function getApplicationWebhook({ projectId, branch }) {
secrets: true secrets: true
} }
}); });
if (application.gitSource?.githubApp?.clientSecret) {
if (body.gitSource?.githubApp?.clientSecret) { application.gitSource.githubApp.clientSecret = decrypt(
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret); application.gitSource.githubApp.clientSecret
);
} }
if (body.gitSource?.githubApp?.webhookSecret) { if (application.gitSource?.githubApp?.webhookSecret) {
body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret); application.gitSource.githubApp.webhookSecret = decrypt(
application.gitSource.githubApp.webhookSecret
);
} }
if (body.gitSource?.githubApp?.privateKey) { if (application.gitSource?.githubApp?.privateKey) {
body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey); application.gitSource.githubApp.privateKey = decrypt(
application.gitSource.githubApp.privateKey
);
} }
if (body?.gitSource?.gitlabApp?.appSecret) { if (application?.gitSource?.gitlabApp?.appSecret) {
body.gitSource.gitlabApp.appSecret = decrypt(body.gitSource.gitlabApp.appSecret); application.gitSource.gitlabApp.appSecret = decrypt(
application.gitSource.gitlabApp.appSecret
);
} }
if (body?.gitSource?.gitlabApp?.webhookToken) { if (application?.gitSource?.gitlabApp?.webhookToken) {
body.gitSource.gitlabApp.webhookToken = decrypt(body.gitSource.gitlabApp.webhookToken); application.gitSource.gitlabApp.webhookToken = decrypt(
application.gitSource.gitlabApp.webhookToken
);
} }
if (body?.secrets.length > 0) { if (application?.secrets.length > 0) {
body.secrets = body.secrets.map((s) => { application.secrets = application.secrets.map((s) => {
s.value = decrypt(s.value); s.value = decrypt(s.value);
return s; return s;
}); });
} }
return { ...application };
return { ...body };
} catch (e) { } catch (e) {
throw { status: 404, body: { message: e.message } }; throw { status: 404, body: { message: e.message } };
} }
@@ -157,24 +156,41 @@ export async function getApplication({ id, teamId }) {
return { ...body }; return { ...body };
} }
export async function configureGitRepository({ id, repository, branch, projectId, webhookToken }) { export async function configureGitRepository({
id,
repository,
branch,
projectId,
webhookToken,
autodeploy
}) {
if (webhookToken) { if (webhookToken) {
const encryptedWebhookToken = encrypt(webhookToken); const encryptedWebhookToken = encrypt(webhookToken);
return await prisma.application.update({ await prisma.application.update({
where: { id }, where: { id },
data: { data: {
repository, repository,
branch, branch,
projectId, projectId,
gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } } gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } },
settings: { update: { autodeploy } }
} }
}); });
} else { } else {
return await prisma.application.update({ await prisma.application.update({
where: { id }, where: { id },
data: { repository, branch, projectId } data: { repository, branch, projectId, settings: { update: { autodeploy } } }
}); });
} }
if (!autodeploy) {
const applications = await prisma.application.findMany({ where: { branch, projectId } });
for (const application of applications) {
await prisma.applicationSettings.updateMany({
where: { applicationId: application.id },
data: { autodeploy: false }
});
}
}
} }
export async function configureBuildPack({ id, buildPack }) { export async function configureBuildPack({ id, buildPack }) {
@@ -209,10 +225,14 @@ export async function configureApplication({
}); });
} }
export async function setApplicationSettings({ id, debug, previews, dualCerts }) { export async function checkDoubleBranch(branch, projectId) {
const applications = await prisma.application.findMany({ where: { branch, projectId } });
return applications.length > 1;
}
export async function setApplicationSettings({ id, debug, previews, dualCerts, autodeploy }) {
return await prisma.application.update({ return await prisma.application.update({
where: { id }, where: { id },
data: { settings: { update: { debug, previews, dualCerts } } }, data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
include: { destinationDocker: true } include: { destinationDocker: true }
}); });
} }

View File

@@ -15,6 +15,9 @@ export async function isDockerNetworkExists({ network }) {
return await prisma.destinationDocker.findFirst({ where: { network } }); return await prisma.destinationDocker.findFirst({ where: { network } });
} }
export async function isServiceSecretExists({ id, name }) {
return await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
}
export async function isSecretExists({ id, name, isPRMRSecret }) { export async function isSecretExists({ id, name, isPRMRSecret }) {
return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } }); return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
} }

View File

@@ -1,6 +1,19 @@
import { encrypt, decrypt } from '$lib/crypto'; import { encrypt, decrypt } from '$lib/crypto';
import { prisma } from './common'; import { prisma } from './common';
export async function listServiceSecrets(serviceId: string) {
let secrets = await prisma.serviceSecret.findMany({
where: { serviceId },
orderBy: { createdAt: 'desc' }
});
secrets = secrets.map((secret) => {
secret.value = decrypt(secret.value);
return secret;
});
return secrets;
}
export async function listSecrets(applicationId: string) { export async function listSecrets(applicationId: string) {
let secrets = await prisma.secret.findMany({ let secrets = await prisma.secret.findMany({
where: { applicationId }, where: { applicationId },
@@ -14,6 +27,12 @@ export async function listSecrets(applicationId: string) {
return secrets; return secrets;
} }
export async function createServiceSecret({ id, name, value }) {
value = encrypt(value);
return await prisma.serviceSecret.create({
data: { name, value, service: { connect: { id } } }
});
}
export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) { export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
value = encrypt(value); value = encrypt(value);
return await prisma.secret.create({ return await prisma.secret.create({
@@ -21,10 +40,24 @@ export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecre
}); });
} }
export async function updateServiceSecret({ id, name, value }) {
value = encrypt(value);
const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
if (found) {
return await prisma.serviceSecret.updateMany({
where: { serviceId: id, name },
data: { value }
});
} else {
return await prisma.serviceSecret.create({
data: { name, value, service: { connect: { id } } }
});
}
}
export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) { export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
value = encrypt(value); value = encrypt(value);
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } }); const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
console.log(found);
if (found) { if (found) {
return await prisma.secret.updateMany({ return await prisma.secret.updateMany({
@@ -38,6 +71,10 @@ export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecre
} }
} }
export async function removeServiceSecret({ id, name }) {
return await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } });
}
export async function removeSecret({ id, name }) { export async function removeSecret({ id, name }) {
return await prisma.secret.deleteMany({ where: { applicationId: id, name } }); return await prisma.secret.deleteMany({ where: { applicationId: id, name } });
} }

View File

@@ -19,7 +19,8 @@ export async function getService({ id, teamId }) {
plausibleAnalytics: true, plausibleAnalytics: true,
minio: true, minio: true,
vscodeserver: true, vscodeserver: true,
wordpress: true wordpress: true,
serviceSecret: true
} }
}); });
@@ -42,6 +43,12 @@ export async function getService({ id, teamId }) {
if (body.wordpress?.mysqlRootUserPassword) if (body.wordpress?.mysqlRootUserPassword)
body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword); body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword);
if (body?.serviceSecret.length > 0) {
body.serviceSecret = body.serviceSecret.map((s) => {
s.value = decrypt(s.value);
return s;
});
}
return { ...body }; return { ...body };
} }
@@ -159,5 +166,7 @@ export async function removeService({ id }) {
await prisma.minio.deleteMany({ where: { serviceId: id } }); await prisma.minio.deleteMany({ where: { serviceId: id } });
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } }); await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
await prisma.wordpress.deleteMany({ where: { serviceId: id } }); await prisma.wordpress.deleteMany({ where: { serviceId: id } });
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
await prisma.service.delete({ where: { id } }); await prisma.service.delete({ where: { id } });
} }

View File

@@ -16,6 +16,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
secrets, secrets,
pullmergeRequestId pullmergeRequestId
} = data; } = data;
const isPnpm = installCommand.includes('pnpm') || buildCommand.includes('pnpm');
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${imageForBuild}`); Dockerfile.push(`FROM ${imageForBuild}`);
Dockerfile.push('WORKDIR /usr/src/app'); Dockerfile.push('WORKDIR /usr/src/app');
@@ -35,7 +36,10 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
} }
}); });
} }
// TODO: If build command defined, install command should be the default yarn install if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
Dockerfile.push('RUN pnpm add -g pnpm');
}
if (installCommand) { if (installCommand) {
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
try { try {

View File

@@ -112,9 +112,15 @@ export async function haproxyInstance() {
} }
export async function configureHAProxy() { export async function configureHAProxy() {
const haproxy = await haproxyInstance();
try { try {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
} catch (error) {
return 'Error: HAProxy is not running';
}
try {
const data = { const data = {
applications: [], applications: [],
services: [], services: [],

View File

@@ -49,7 +49,12 @@ export async function completeTransaction(transactionId) {
} }
export async function deleteProxy({ id }) { export async function deleteProxy({ id }) {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
await checkHAProxy(haproxy); try {
await checkHAProxy(haproxy);
} catch (error) {
return 'Error: HAProxy is not running';
}
let transactionId; let transactionId;
try { try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json(); await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json();

View File

@@ -99,7 +99,8 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) {
export async function generateSSLCerts() { export async function generateSSLCerts() {
const ssls = []; const ssls = [];
const applications = await db.prisma.application.findMany({ const applications = await db.prisma.application.findMany({
include: { destinationDocker: true, settings: true } include: { destinationDocker: true, settings: true },
orderBy: { createdAt: 'desc' }
}); });
for (const application of applications) { for (const application of applications) {
const { const {
@@ -139,7 +140,8 @@ export async function generateSSLCerts() {
plausibleAnalytics: true, plausibleAnalytics: true,
vscodeserver: true, vscodeserver: true,
wordpress: true wordpress: true
} },
orderBy: { createdAt: 'desc' }
}); });
for (const service of services) { for (const service of services) {

View File

@@ -120,7 +120,7 @@ buildWorker.on('completed', async (job: Bullmq.Job) => {
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} finally { } finally {
const workdir = `/tmp/build-sources/${job.data.repository}/`; const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
await asyncExecShell(`rm -fr ${workdir}`); await asyncExecShell(`rm -fr ${workdir}`);
} }
return; return;

View File

@@ -1,4 +1,3 @@
import { dev } from '$app/env';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { configureHAProxy } from '$lib/haproxy/configuration'; import { configureHAProxy } from '$lib/haproxy/configuration';

View File

@@ -108,11 +108,9 @@
try { try {
loading = true; loading = true;
await post(`/applications/${id}/stop.json`, {}); await post(`/applications/${id}/stop.json`, {});
isRunning = false; return window.location.reload();
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} finally {
loading = false;
} }
} }
</script> </script>

View File

@@ -25,9 +25,11 @@
let selected = { let selected = {
projectId: undefined, projectId: undefined,
repository: undefined, repository: undefined,
branch: undefined branch: undefined,
autodeploy: application.settings.autodeploy || true
}; };
let showSave = false; let showSave = false;
async function loadRepositoriesByPage(page = 0) { async function loadRepositoriesByPage(page = 0) {
return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, { return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, {
Authorization: `token ${$gitTokens.githubToken}` Authorization: `token ${$gitTokens.githubToken}`
@@ -69,7 +71,14 @@
`/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}` `/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}`
); );
if (data.used) { if (data.used) {
errorNotification('This branch is already used by another application.'); const sure = confirm(
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
);
if (sure) {
selected.autodeploy = false;
showSave = true;
return true;
}
showSave = false; showSave = false;
return true; return true;
} }

View File

@@ -30,6 +30,7 @@
let projects = []; let projects = [];
let branches = []; let branches = [];
let showSave = false; let showSave = false;
let autodeploy = application.settings.autodeploy || true;
let selected = { let selected = {
group: undefined, group: undefined,
@@ -138,7 +139,14 @@
`/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}` `/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
); );
if (data.used) { if (data.used) {
errorNotification('This branch is already used by another application.'); const sure = confirm(
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
);
if (sure) {
autodeploy = false;
showSave = true;
return true;
}
showSave = false; showSave = false;
return true; return true;
} }
@@ -235,10 +243,14 @@
const url = `/applications/${id}/configuration/repository.json`; const url = `/applications/${id}/configuration/repository.json`;
try { try {
const repository = `${selected.group.full_path.replace('-personal', '')}/${
selected.project.name
}`;
await post(url, { await post(url, {
repository: `${selected.group.full_path}/${selected.project.name}`, repository,
branch: selected.branch.name, branch: selected.branch.name,
projectId: selected.project.id, projectId: selected.project.id,
autodeploy,
webhookToken webhookToken
}); });
return await goto(from || `/applications/${id}/configuration/buildpack`); return await goto(from || `/applications/${id}/configuration/buildpack`);

View File

@@ -30,14 +30,21 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { id } = event.params; const { id } = event.params;
let { repository, branch, projectId, webhookToken } = await event.request.json(); let { repository, branch, projectId, webhookToken, autodeploy } = await event.request.json();
repository = repository.toLowerCase(); repository = repository.toLowerCase();
branch = branch.toLowerCase(); branch = branch.toLowerCase();
projectId = Number(projectId); projectId = Number(projectId);
try { try {
await db.configureGitRepository({ id, repository, branch, projectId, webhookToken }); await db.configureGitRepository({
id,
repository,
branch,
projectId,
webhookToken,
autodeploy
});
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return ErrorHandler(error); return ErrorHandler(error);

View File

@@ -56,6 +56,7 @@
let debug = application.settings.debug; let debug = application.settings.debug;
let previews = application.settings.previews; let previews = application.settings.previews;
let dualCerts = application.settings.dualCerts; let dualCerts = application.settings.dualCerts;
let autodeploy = application.settings.autodeploy;
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) { if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
application.fqdn = `http://${cuid()}.demo.coolify.io`; application.fqdn = `http://${cuid()}.demo.coolify.io`;
@@ -75,10 +76,32 @@
if (name === 'dualCerts') { if (name === 'dualCerts') {
dualCerts = !dualCerts; dualCerts = !dualCerts;
} }
if (name === 'autodeploy') {
autodeploy = !autodeploy;
}
try { try {
await post(`/applications/${id}/settings.json`, { previews, debug, dualCerts }); await post(`/applications/${id}/settings.json`, {
previews,
debug,
dualCerts,
autodeploy,
branch: application.branch,
projectId: application.projectId
});
return toast.push('Settings saved.'); return toast.push('Settings saved.');
} catch ({ error }) { } catch ({ error }) {
if (name === 'debug') {
debug = !debug;
}
if (name === 'previews') {
previews = !previews;
}
if (name === 'dualCerts') {
dualCerts = !dualCerts;
}
if (name === 'autodeploy') {
autodeploy = !autodeploy;
}
return errorNotification(error); return errorNotification(error);
} }
} }
@@ -383,22 +406,23 @@
<div class="flex space-x-1 pb-5 font-bold"> <div class="flex space-x-1 pb-5 font-bold">
<div class="title">Features</div> <div class="title">Features</div>
</div> </div>
<!-- <ul class="mt-2 divide-y divide-stone-800">
<Setting
bind:setting={forceSSL}
on:click={() => changeSettings('forceSSL')}
title="Force https"
description="Creates a https redirect for all requests from http and also generates a https certificate for the domain through Let's Encrypt."
/>
</ul> -->
<div class="px-10 pb-10"> <div class="px-10 pb-10">
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={autodeploy}
on:click={() => changeSettings('autodeploy')}
title="Enable Automatic Deployment"
description="Enable automatic deployment through webhooks."
/>
</div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
isCenter={false} isCenter={false}
bind:setting={previews} bind:setting={previews}
on:click={() => changeSettings('previews')} on:click={() => changeSettings('previews')}
title="Enable MR/PR Previews" title="Enable MR/PR Previews"
description="Creates previews from pull and merge requests." description="Enable preview deployments from pull or merge requests."
/> />
</div> </div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">

View File

@@ -8,10 +8,17 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { id } = event.params; const { id } = event.params;
const { debug, previews, dualCerts } = await event.request.json(); const { debug, previews, dualCerts, autodeploy, branch, projectId } = await event.request.json();
try { try {
await db.setApplicationSettings({ id, debug, previews, dualCerts }); const isDouble = await db.checkDoubleBranch(branch, projectId);
if (isDouble && autodeploy) {
throw {
message:
'Cannot activate automatic deployments until only one application is defined for this repository / branch.'
};
}
await db.setApplicationSettings({ id, debug, previews, dualCerts, autodeploy });
return { status: 201 }; return { status: 201 };
} catch (error) { } catch (error) {
return ErrorHandler(error); return ErrorHandler(error);

View File

@@ -25,7 +25,7 @@
define('WP_ALLOW_MULTISITE', true); define('WP_ALLOW_MULTISITE', true);
define('MULTISITE', true); define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', false);` define('SUBDOMAIN_INSTALL', false);`
: null}>{service.wordpress.extraConfig || 'N/A'}</textarea : 'N/A'}>{service.wordpress.extraConfig}</textarea
> >
</div> </div>
<div class="flex space-x-1 py-5 font-bold"> <div class="flex space-x-1 py-5 font-bold">

View File

@@ -57,13 +57,13 @@
</script> </script>
<script> <script>
import { session } from '$app/stores'; import { page, session } from '$app/stores';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import DeleteIcon from '$lib/components/DeleteIcon.svelte'; import DeleteIcon from '$lib/components/DeleteIcon.svelte';
import Loading from '$lib/components/Loading.svelte'; import Loading from '$lib/components/Loading.svelte';
import { del, post } from '$lib/api'; import { del, post } from '$lib/api';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { onMount } from 'svelte'; const { id } = $page.params;
export let service; export let service;
export let isRunning; export let isRunning;
@@ -168,6 +168,76 @@
</svg> </svg>
</button> </button>
{/if} {/if}
<div class="border border-stone-700 h-8" />
{/if}
{#if service.type && service.destinationDockerId && service.version}
<a
href="/services/{id}"
sveltekit:prefetch
class="hover:text-yellow-500 rounded"
class:text-yellow-500={$page.url.pathname === `/services/${id}`}
class:bg-coolgray-500={$page.url.pathname === `/services/${id}`}
>
<button
title="Configurations"
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
data-tooltip="Configurations"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg></button
></a
>
<a
href="/services/{id}/secrets"
sveltekit:prefetch
class="hover:text-pink-500 rounded"
class:text-pink-500={$page.url.pathname === `/services/${id}/secrets`}
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/secrets`}
>
<button
title="Secrets"
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
data-tooltip="Secrets"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
/>
<circle cx="12" cy="11" r="1" />
<line x1="12" y1="12" x2="12" y2="14.5" />
</svg></button
></a
>
<div class="border border-stone-700 h-8" />
{/if} {/if}
<button <button
on:click={deleteService} on:click={deleteService}

View File

@@ -14,19 +14,32 @@ export const post: RequestHandler = async (event) => {
try { try {
const service = await db.getService({ id, teamId }); const service = await db.getService({ id, teamId });
const { type, version, destinationDockerId, destinationDocker } = service; const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type); const image = getServiceImage(type);
const config = {
image: `${image}:${version}`,
volume: `${id}-ngrams:/ngrams`,
environmentVariables: {}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = { const composeFile = {
version: '3.8', version: '3.8',
services: { services: {
[id]: { [id]: {
container_name: id, container_name: id,
image: `${image}:${version}`, image: config.image,
networks: [network], networks: [network],
environment: config.environmentVariables,
restart: 'always', restart: 'always',
volumes: [`${id}-ngrams:/ngrams`], volumes: [`${id}-ngrams:/ngrams`],
labels: makeLabelForServices('languagetool') labels: makeLabelForServices('languagetool')

View File

@@ -6,7 +6,7 @@ import type { RequestHandler } from '@sveltejs/kit';
import { startHttpProxy } from '$lib/haproxy'; import { startHttpProxy } from '$lib/haproxy';
import getPort, { portNumbers } from 'get-port'; import getPort, { portNumbers } from 'get-port';
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common'; import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -23,7 +23,8 @@ export const post: RequestHandler = async (event) => {
fqdn, fqdn,
destinationDockerId, destinationDockerId,
destinationDocker, destinationDocker,
minio: { rootUser, rootUserPassword } minio: { rootUser, rootUserPassword },
serviceSecret
} = service; } = service;
const data = await db.prisma.setting.findFirst(); const data = await db.prisma.setting.findFirst();
@@ -38,9 +39,10 @@ export const post: RequestHandler = async (event) => {
const apiPort = 9000; const apiPort = 9000;
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = { const config = {
image: `minio/minio:${version}`, image: `${image}:${version}`,
volume: `${id}-minio-data:/data`, volume: `${id}-minio-data:/data`,
environmentVariables: { environmentVariables: {
MINIO_ROOT_USER: rootUser, MINIO_ROOT_USER: rootUser,
@@ -48,12 +50,17 @@ export const post: RequestHandler = async (event) => {
MINIO_BROWSER_REDIRECT_URL: fqdn MINIO_BROWSER_REDIRECT_URL: fqdn
} }
}; };
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = { const composeFile = {
version: '3.8', version: '3.8',
services: { services: {
[id]: { [id]: {
container_name: id, container_name: id,
image: `minio/minio:${version}`, image: config.image,
command: `server /data --console-address ":${consolePort}"`, command: `server /data --console-address ":${consolePort}"`,
environment: config.environmentVariables, environment: config.environmentVariables,
networks: [network], networks: [network],

View File

@@ -3,7 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common'; import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -14,19 +14,30 @@ export const post: RequestHandler = async (event) => {
try { try {
const service = await db.getService({ id, teamId }); const service = await db.getService({ id, teamId });
const { type, version, destinationDockerId, destinationDocker } = service; const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = {
image: `${image}:${version}`,
environmentVariables: {}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = { const composeFile = {
version: '3.8', version: '3.8',
services: { services: {
[id]: { [id]: {
container_name: id, container_name: id,
image: `nocodb/nocodb:${version}`, image: config.image,
networks: [network], networks: [network],
environment: config.environmentVariables,
restart: 'always', restart: 'always',
labels: makeLabelForServices('nocodb') labels: makeLabelForServices('nocodb')
} }

View File

@@ -3,7 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common'; import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -20,6 +20,7 @@ export const post: RequestHandler = async (event) => {
fqdn, fqdn,
destinationDockerId, destinationDockerId,
destinationDocker, destinationDocker,
serviceSecret,
plausibleAnalytics: { plausibleAnalytics: {
id: plausibleDbId, id: plausibleDbId,
username, username,
@@ -31,10 +32,11 @@ export const post: RequestHandler = async (event) => {
secretKeyBase secretKeyBase
} }
} = service; } = service;
const image = getServiceImage(type);
const config = { const config = {
plausibleAnalytics: { plausibleAnalytics: {
image: `plausible/analytics:${version}`, image: `${image}:${version}`,
environmentVariables: { environmentVariables: {
ADMIN_USER_EMAIL: email, ADMIN_USER_EMAIL: email,
ADMIN_USER_NAME: username, ADMIN_USER_NAME: username,
@@ -68,6 +70,11 @@ export const post: RequestHandler = async (event) => {
} }
} }
}; };
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.plausibleAnalytics.environmentVariables[secret.name] = secret.value;
});
}
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);

View File

@@ -0,0 +1,87 @@
<script>
export let name = '';
export let value = '';
export let isNewSecret = false;
import { page } from '$app/stores';
import { del, post } from '$lib/api';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
const { id } = $page.params;
async function removeSecret() {
try {
await del(`/services/${id}/secrets.json`, { name });
dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
}
} catch ({ error }) {
return errorNotification(error);
}
}
async function saveSecret(isNew = false) {
if (!name) return errorNotification('Name is required.');
if (!value) return errorNotification('Value is required.');
try {
await post(`/services/${id}/secrets.json`, {
name,
value,
isNew
});
dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
}
toast.push('Secret saved.');
} catch ({ error }) {
return errorNotification(error);
}
}
</script>
<td>
<input
id={isNewSecret ? 'secretName' : 'secretNameNew'}
bind:value={name}
required
placeholder="EXAMPLE_VARIABLE"
class=" border border-dashed border-coolgray-300"
readonly={!isNewSecret}
class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
/>
</td>
<td>
<CopyPasswordField
id={isNewSecret ? 'secretValue' : 'secretValueNew'}
name={isNewSecret ? 'secretValue' : 'secretValueNew'}
isPasswordField={true}
bind:value
required
placeholder="J$#@UIO%HO#$U%H"
/>
</td>
<td>
{#if isNewSecret}
<div class="flex items-center justify-center">
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveSecret(true)}>Add</button>
</div>
{:else}
<div class="flex flex-row justify-center space-x-2">
<div class="flex items-center justify-center">
<button class="" on:click={() => saveSecret(false)}>Set</button>
</div>
<div class="flex justify-center items-end">
<button class="bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
</div>
{/if}
</td>

View File

@@ -0,0 +1,70 @@
import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const secrets = await db.listServiceSecrets(id);
return {
status: 200,
body: {
secrets: secrets.sort((a, b) => {
return ('' + a.name).localeCompare(b.name);
})
}
};
} catch (error) {
return ErrorHandler(error);
}
};
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { name, value, isBuildSecret, isPRMRSecret, isNew } = await event.request.json();
try {
if (isNew) {
const found = await db.isServiceSecretExists({ id, name });
if (found) {
throw {
error: `Secret ${name} already exists.`
};
} else {
await db.createServiceSecret({ id, name, value });
return {
status: 201
};
}
} else {
await db.updateServiceSecret({ id, name, value });
return {
status: 201
};
}
} catch (error) {
return ErrorHandler(error);
}
};
export const del: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { name } = await event.request.json();
try {
await db.removeServiceSecret({ id, name });
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
};

View File

@@ -0,0 +1,67 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, stuff }) => {
let endpoint = `/services/${params.id}/secrets.json`;
const res = await fetch(endpoint);
if (res.ok) {
return {
props: {
service: stuff.service,
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${endpoint}`)
};
};
</script>
<script lang="ts">
export let secrets;
export let service;
import Secret from './_Secret.svelte';
import { getDomain } from '$lib/components/common';
import { page } from '$app/stores';
import { get } from '$lib/api';
const { id } = $page.params;
async function refreshSecrets() {
const data = await get(`/services/${id}/secrets.json`);
secrets = [...data.secrets];
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">
Secrets {#if service.fqdn}
<a href={service.fqdn} target="_blank">{getDomain(service.fqdn)}</a>
{/if}
</div>
</div>
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col" class="w-96 text-center">Action</th>
</tr>
</thead>
<tbody>
{#each secrets as secret}
{#key secret.id}
<tr>
<Secret name={secret.name} value={secret.value} on:refresh={refreshSecrets} />
</tr>
{/key}
{/each}
<tr>
<Secret isNewSecret on:refresh={refreshSecrets} />
</tr>
</tbody>
</table>
</div>

View File

@@ -14,25 +14,31 @@ export const post: RequestHandler = async (event) => {
try { try {
const service = await db.getService({ id, teamId }); const service = await db.getService({ id, teamId });
const { type, version, destinationDockerId, destinationDocker } = service; const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const baseImage = getServiceImage(type); const image = getServiceImage(type);
const config = { const config = {
image: `${baseImage}:${version}`, image: `${image}:${version}`,
volume: `${id}-vaultwarden-data:/data/` volume: `${id}-vaultwarden-data:/data/`,
environmentVariables: {}
}; };
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = { const composeFile = {
version: '3.8', version: '3.8',
services: { services: {
[id]: { [id]: {
container_name: id, container_name: id,
image: config.image, image: config.image,
environment: config.environmentVariables,
networks: [network], networks: [network],
volumes: [config.volume], volumes: [config.volume],
restart: 'always', restart: 'always',

View File

@@ -3,7 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common'; import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -19,6 +19,7 @@ export const post: RequestHandler = async (event) => {
version, version,
destinationDockerId, destinationDockerId,
destinationDocker, destinationDocker,
serviceSecret,
vscodeserver: { password } vscodeserver: { password }
} = service; } = service;
@@ -26,13 +27,20 @@ export const post: RequestHandler = async (event) => {
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = { const config = {
image: `codercom/code-server:${version}`, image: `${image}:${version}`,
volume: `${id}-vscodeserver-data:/home/coder`, volume: `${id}-vscodeserver-data:/home/coder`,
environmentVariables: { environmentVariables: {
PASSWORD: password PASSWORD: password
} }
}; };
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = { const composeFile = {
version: '3.8', version: '3.8',
services: { services: {

View File

@@ -3,7 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common'; import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@@ -19,6 +19,7 @@ export const post: RequestHandler = async (event) => {
version, version,
fqdn, fqdn,
destinationDockerId, destinationDockerId,
serviceSecret,
destinationDocker, destinationDocker,
wordpress: { wordpress: {
mysqlDatabase, mysqlDatabase,
@@ -32,11 +33,12 @@ export const post: RequestHandler = async (event) => {
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
const image = getServiceImage(type);
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const config = { const config = {
wordpress: { wordpress: {
image: `wordpress:${version}`, image: `${image}:${version}`,
volume: `${id}-wordpress-data:/var/www/html`, volume: `${id}-wordpress-data:/var/www/html`,
environmentVariables: { environmentVariables: {
WORDPRESS_DB_HOST: `${id}-mysql`, WORDPRESS_DB_HOST: `${id}-mysql`,
@@ -58,6 +60,11 @@ export const post: RequestHandler = async (event) => {
} }
} }
}; };
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.wordpress.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = { const composeFile = {
version: '3.8', version: '3.8',
services: { services: {

View File

@@ -9,7 +9,7 @@ import { dev } from '$app/env';
export const options: RequestHandler = async () => { export const options: RequestHandler = async () => {
return { return {
status: 200, status: 204,
headers: { headers: {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Allow-Headers': 'Content-Type, Authorization',

View File

@@ -9,7 +9,7 @@ import { dev } from '$app/env';
export const options: RequestHandler = async () => { export const options: RequestHandler = async () => {
return { return {
status: 200, status: 204,
headers: { headers: {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Allow-Headers': 'Content-Type, Authorization',

View File

@@ -7,7 +7,7 @@ import cookie from 'cookie';
export const options: RequestHandler = async () => { export const options: RequestHandler = async () => {
return { return {
status: 200, status: 204,
headers: { headers: {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Allow-Headers': 'Content-Type, Authorization',

View File

@@ -1,5 +1,5 @@
const defaultTheme = require('tailwindcss/defaultTheme'); const defaultTheme = require('tailwindcss/defaultTheme');
const colors = require('tailwindcss/colors'); // const colors = require('tailwindcss/colors');
module.exports = { module.exports = {
content: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}'], content: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}'],
important: true, important: true,
@@ -18,7 +18,6 @@ module.exports = {
sans: ['Poppins', ...defaultTheme.fontFamily.sans] sans: ['Poppins', ...defaultTheme.fontFamily.sans]
}, },
colors: { colors: {
...colors,
coollabs: '#6B16ED', coollabs: '#6B16ED',
'coollabs-100': '#7317FF', 'coollabs-100': '#7317FF',
coolblack: '#161616', coolblack: '#161616',