From 62ccab22d6bee2ca4be66b0a110a459797109149 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Apr 2022 23:08:08 +0200 Subject: [PATCH 01/32] fix: Packagemanager finder --- src/lib/components/templates.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/components/templates.ts b/src/lib/components/templates.ts index 2336afb7d..3ad83a3ba 100644 --- a/src/lib/components/templates.ts +++ b/src/lib/components/templates.ts @@ -15,7 +15,6 @@ export function findBuildPack(pack, packageManager = 'npm') { ...metaData, ...defaultBuildAndDeploy(packageManager), buildCommand: null, - startCommand: null, publishDirectory: null, port: null }; From 5998212b8223433ace577e0fbe16ca1486f0a993 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Apr 2022 23:44:06 +0200 Subject: [PATCH 02/32] WIP: Base image selector --- prisma/schema.prisma | 1 + src/lib/database/applications.ts | 36 +++++++++++++++++-- src/lib/docker.ts | 3 +- src/lib/locales/en.json | 3 +- .../[id]/configuration/baseimage.svelte | 0 .../[id]/configuration/buildpack.svelte | 2 +- src/routes/applications/[id]/index.json.ts | 4 ++- src/routes/applications/[id]/index.svelte | 8 +++++ 8 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 src/routes/applications/[id]/configuration/baseimage.svelte diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 82ba9baa8..77f2e1297 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -105,6 +105,7 @@ model Application { gitSource GitSource? @relation(fields: [gitSourceId], references: [id]) secrets Secret[] persistentStorage ApplicationPersistentStorage[] + baseImage String? } model ApplicationSettings { diff --git a/src/lib/database/applications.ts b/src/lib/database/applications.ts index d57e3f3e5..f02300bd2 100644 --- a/src/lib/database/applications.ts +++ b/src/lib/database/applications.ts @@ -12,6 +12,17 @@ import type { Application, ApplicationPersistentStorage } from '@prisma/client'; +const nodeBased = [ + 'react', + 'vuejs', + 'static', + 'svelte', + 'gatsby', + 'php', + 'astro', + 'eleventy', + 'node' +]; export async function listApplications(teamId: string): Promise { if (teamId === '0') { @@ -195,6 +206,24 @@ export async function getApplication({ id, teamId }: { id: string; teamId: strin return s; }); } + // Set default build images + if (!body.baseImage) { + if (nodeBased.includes(body.buildPack)) { + body.baseImage = 'node:lts'; + } + if (body.buildPack === 'python') { + body.baseImage = 'python:3-alpine'; + } + if (body.buildPack === 'rust') { + body.baseImage = 'rust:latest'; + } + if (body.buildPack === 'deno') { + body.baseImage = 'denoland/deno:latest'; + } + if (body.buildPack === 'php') { + body.baseImage = 'webdevops/php-apache:8.0-alpine'; + } + } return { ...body }; } @@ -266,7 +295,8 @@ export async function configureApplication({ pythonVariable, dockerFileLocation, denoMainFile, - denoOptions + denoOptions, + baseImage }: { id: string; buildPack: string; @@ -284,6 +314,7 @@ export async function configureApplication({ dockerFileLocation: string; denoMainFile: string; denoOptions: string; + baseImage: string; }): Promise { return await prisma.application.update({ where: { id }, @@ -302,7 +333,8 @@ export async function configureApplication({ pythonVariable, dockerFileLocation, denoMainFile, - denoOptions + denoOptions, + baseImage } }); } diff --git a/src/lib/docker.ts b/src/lib/docker.ts index d3c040c6a..196ee3340 100644 --- a/src/lib/docker.ts +++ b/src/lib/docker.ts @@ -41,10 +41,11 @@ export async function buildCacheImageWithNode(data, imageForBuild) { 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 || ''} ./`); if (installCommand) { + Dockerfile.push(`COPY .${baseDirectory || ''}/package.json ./`); Dockerfile.push(`RUN ${installCommand}`); } + Dockerfile.push(`COPY .${baseDirectory || ''} ./`); Dockerfile.push(`RUN ${buildCommand}`); await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json index d77d2a1c1..17f210b52 100644 --- a/src/lib/locales/en.json +++ b/src/lib/locales/en.json @@ -124,7 +124,7 @@ "no_branches_found": "No branches found", "configure_build_pack": "Configure Build Pack", "scanning_repository_suggest_build_pack": "Scanning repository to suggest a build pack for you...", - "found_lock_file": "Found lock file for {{packageManager}}. Using it for predefined commands commands.", + "found_lock_file": "Found lock file for {{packageManager}}.Using it for predefined commands commands.", "configure_destination": "Configure Destination", "no_configurable_destination": "No configurable Destination found", "select_a_repository_project": "Select a Repository / Project", @@ -184,6 +184,7 @@ "git_source": "Git Source", "git_repository": "Git Repository", "build_pack": "Build Pack", + "base_image": "Base Image", "destination": "Destination", "application": "Application", "url_fqdn": "URL (FQDN)", diff --git a/src/routes/applications/[id]/configuration/baseimage.svelte b/src/routes/applications/[id]/configuration/baseimage.svelte new file mode 100644 index 000000000..e69de29bb diff --git a/src/routes/applications/[id]/configuration/buildpack.svelte b/src/routes/applications/[id]/configuration/buildpack.svelte index 437c65cfa..13e6f8e10 100644 --- a/src/routes/applications/[id]/configuration/buildpack.svelte +++ b/src/routes/applications/[id]/configuration/buildpack.svelte @@ -225,7 +225,7 @@
{#each buildPacks as buildPack}
- +
{/each}
diff --git a/src/routes/applications/[id]/index.json.ts b/src/routes/applications/[id]/index.json.ts index ef9f57dcb..ed7dc9b07 100644 --- a/src/routes/applications/[id]/index.json.ts +++ b/src/routes/applications/[id]/index.json.ts @@ -62,7 +62,8 @@ export const post: RequestHandler = async (event) => { pythonVariable, dockerFileLocation, denoMainFile, - denoOptions + denoOptions, + baseImage } = await event.request.json(); if (port) port = Number(port); if (denoOptions) denoOptions = denoOptions.trim(); @@ -96,6 +97,7 @@ export const post: RequestHandler = async (event) => { dockerFileLocation, denoMainFile, denoOptions, + baseImage, ...defaultConfiguration }); return { status: 201 }; diff --git a/src/routes/applications/[id]/index.svelte b/src/routes/applications/[id]/index.svelte index 359cdc5a3..f5d9f8c97 100644 --- a/src/routes/applications/[id]/index.svelte +++ b/src/routes/applications/[id]/index.svelte @@ -310,6 +310,14 @@ /> +
+ +
+ +
+
{$t('application.application')}
From d03fbd9224c60e5e40882c81d3b4146d1fb229ec Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 26 Apr 2022 14:51:08 +0200 Subject: [PATCH 03/32] feat: select base image for buildpacks --- README.md | 10 +- package.json | 2 +- .../migration.sql | 3 + prisma/schema.prisma | 1 + src/lib/buildPacks/common.ts | 222 +++++++++++++++++- src/lib/buildPacks/deno.ts | 4 +- src/lib/buildPacks/gatsby.ts | 14 +- src/lib/buildPacks/nestjs.ts | 8 +- src/lib/buildPacks/nextjs.ts | 4 +- src/lib/buildPacks/node.ts | 4 +- src/lib/buildPacks/nuxtjs.ts | 4 +- src/lib/buildPacks/php.ts | 7 +- src/lib/buildPacks/python.ts | 4 +- src/lib/buildPacks/react.ts | 13 +- src/lib/buildPacks/rust.ts | 8 +- src/lib/buildPacks/static.ts | 14 +- src/lib/buildPacks/svelte.ts | 14 +- src/lib/buildPacks/vuejs.ts | 13 +- src/lib/database/applications.ts | 45 ++-- src/lib/locales/en.json | 1 + src/lib/queues/builder.ts | 10 +- src/lib/types/builderJob.ts | 2 + .../[id]/configuration/baseimage.svelte | 0 src/routes/applications/[id]/index.json.ts | 4 +- src/routes/applications/[id]/index.svelte | 51 +++- src/tailwind.css | 6 +- 26 files changed, 359 insertions(+), 109 deletions(-) create mode 100644 prisma/migrations/20220426125053_select_base_image/migration.sql delete mode 100644 src/routes/applications/[id]/configuration/baseimage.svelte diff --git a/README.md b/README.md index d214d42b7..ef747034f 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,16 @@ https://demo.coolify.io/ Installation is automated with the following command: ```bash -/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)" +wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh ``` -If you would like no questions during installation +If you would like no questions during installation: + +```bash +wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh -f +``` + +For more details goto the [docs](https://docs.coollabs.io/coolify/installation). ## Features diff --git a/package.json b/package.json index 9f38331ad..bb6f7d86c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "2.5.2", + "version": "2.5.3", "license": "AGPL-3.0", "scripts": { "dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0", diff --git a/prisma/migrations/20220426125053_select_base_image/migration.sql b/prisma/migrations/20220426125053_select_base_image/migration.sql new file mode 100644 index 000000000..37209d82f --- /dev/null +++ b/prisma/migrations/20220426125053_select_base_image/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Application" ADD COLUMN "baseBuildImage" TEXT; +ALTER TABLE "Application" ADD COLUMN "baseImage" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 77f2e1297..267dd1991 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -106,6 +106,7 @@ model Application { secrets Secret[] persistentStorage ApplicationPersistentStorage[] baseImage String? + baseBuildImage String? } model ApplicationSettings { diff --git a/src/lib/buildPacks/common.ts b/src/lib/buildPacks/common.ts index 05313a3d2..eb67a73bd 100644 --- a/src/lib/buildPacks/common.ts +++ b/src/lib/buildPacks/common.ts @@ -5,6 +5,9 @@ import { scanningTemplates } from '$lib/components/templates'; import { promises as fs } from 'fs'; import { staticDeployments } from '$lib/components/common'; +const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy']; +const nodeBased = ['react', 'vuejs', 'svelte', 'gatsby', 'php', 'astro', 'eleventy', 'node']; + export function makeLabelForStandaloneApplication({ applicationId, fqdn, @@ -137,7 +140,13 @@ export const setDefaultConfiguration = async (data) => { }; }; -export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId) { +export async function copyBaseConfigurationFiles( + buildPack, + workdir, + buildId, + applicationId, + baseImage +) { try { if (buildPack === 'php') { await fs.writeFile(`${workdir}/entrypoint.sh`, `chown -R 1000 /app`); @@ -146,7 +155,7 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap buildId, applicationId }); - } else if (staticDeployments.includes(buildPack)) { + } else if (staticDeployments.includes(buildPack) && baseImage.includes('nginx')) { await fs.writeFile( `${workdir}/nginx.conf`, `user nginx; @@ -199,11 +208,6 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap } ` ); - await saveBuildLog({ - line: 'Copied default configuration file for Nginx.', - buildId, - applicationId - }); } } catch (error) { console.log(error); @@ -218,3 +222,207 @@ export function checkPnpm(installCommand = null, buildCommand = null, startComma startCommand?.includes('pnpm') ); } + +export function setDefaultBaseImage(buildPack) { + const nodeVersions = [ + { + value: 'node:lts', + label: 'node:lts' + }, + { + value: 'node:18', + label: 'node:18' + }, + { + value: 'node:17', + label: 'node:17' + }, + { + value: 'node:16', + label: 'node:16' + }, + { + value: 'node:14', + label: 'node:14' + }, + { + value: 'node:12', + label: 'node:12' + } + ]; + const staticVersions = [ + { + value: 'webdevops/nginx:alpine', + label: 'webdevops/nginx:alpine' + }, + { + value: 'webdevops/apache:alpine', + label: 'webdevops/apache:alpine' + } + ]; + const rustVersions = [ + { + value: 'rust:latest', + label: 'rust:latest' + }, + { + value: 'rust:1.60', + label: 'rust:1.60' + }, + { + value: 'rust:1.60-buster', + label: 'rust:1.60-buster' + }, + { + value: 'rust:1.60-bullseye', + label: 'rust:1.60-bullseye' + }, + { + value: 'rust:1.60-slim-buster', + label: 'rust:1.60-slim-buster' + }, + { + value: 'rust:1.60-slim-bullseye', + label: 'rust:1.60-slim-bullseye' + }, + { + value: 'rust:1.60-alpine3.14', + label: 'rust:1.60-alpine3.14' + }, + { + value: 'rust:1.60-alpine3.15', + label: 'rust:1.60-alpine3.15' + } + ]; + const phpVersions = [ + { + value: 'webdevops/php-apache:8.0', + label: 'webdevops/php-apache:8.0' + }, + { + value: 'webdevops/php-nginx:8.0', + label: 'webdevops/php-nginx:8.0' + }, + { + value: 'webdevops/php-apache:7.4', + label: 'webdevops/php-apache:7.4' + }, + { + value: 'webdevops/php-nginx:7.4', + label: 'webdevops/php-nginx:7.4' + }, + { + value: 'webdevops/php-apache:7.3', + label: 'webdevops/php-apache:7.3' + }, + { + value: 'webdevops/php-nginx:7.3', + label: 'webdevops/php-nginx:7.3' + }, + { + value: 'webdevops/php-apache:7.2', + label: 'webdevops/php-apache:7.2' + }, + { + value: 'webdevops/php-nginx:7.2', + label: 'webdevops/php-nginx:7.2' + }, + { + value: 'webdevops/php-apache:7.1', + label: 'webdevops/php-apache:7.1' + }, + { + value: 'webdevops/php-nginx:7.1', + label: 'webdevops/php-nginx:7.1' + }, + { + value: 'webdevops/php-apache:7.0', + label: 'webdevops/php-apache:7.0' + }, + { + value: 'webdevops/php-nginx:7.0', + label: 'webdevops/php-nginx:7.0' + }, + { + value: 'webdevops/php-apache:5.6', + label: 'webdevops/php-apache:5.6' + }, + { + value: 'webdevops/php-nginx:5.6', + label: 'webdevops/php-nginx:5.6' + }, + { + value: 'webdevops/php-apache:8.0-alpine', + label: 'webdevops/php-apache:8.0-alpine' + }, + { + value: 'webdevops/php-nginx:8.0-alpine', + label: 'webdevops/php-nginx:8.0-alpine' + }, + { + value: 'webdevops/php-apache:7.4-alpine', + label: 'webdevops/php-apache:7.4-alpine' + }, + { + value: 'webdevops/php-nginx:7.4-alpine', + label: 'webdevops/php-nginx:7.4-alpine' + }, + { + value: 'webdevops/php-apache:7.3-alpine', + label: 'webdevops/php-apache:7.3-alpine' + }, + { + value: 'webdevops/php-nginx:7.3-alpine', + label: 'webdevops/php-nginx:7.3-alpine' + }, + { + value: 'webdevops/php-apache:7.2-alpine', + label: 'webdevops/php-apache:7.2-alpine' + }, + { + value: 'webdevops/php-nginx:7.2-alpine', + label: 'webdevops/php-nginx:7.2-alpine' + }, + { + value: 'webdevops/php-apache:7.1-alpine', + label: 'webdevops/php-apache:7.1-alpine' + }, + { + value: 'webdevops/php-nginx:7.1-alpine', + label: 'webdevops/php-nginx:7.1-alpine' + } + ]; + let payload = { + baseImage: null, + baseBuildImage: null, + baseImages: [], + baseBuildImages: [] + }; + if (nodeBased.includes(buildPack)) { + payload.baseImage = 'node:lts'; + payload.baseImages = nodeVersions; + } + if (staticApps.includes(buildPack)) { + payload.baseImage = 'webdevops/nginx:alpine'; + payload.baseImages = staticVersions; + payload.baseBuildImage = 'node:lts'; + payload.baseBuildImages = nodeVersions; + } + if (buildPack === 'python') { + payload.baseImage = 'python:3-alpine'; + } + if (buildPack === 'rust') { + payload.baseImage = 'rust:latest'; + payload.baseBuildImage = 'rust:latest'; + payload.baseImages = rustVersions; + payload.baseBuildImages = rustVersions; + } + if (buildPack === 'deno') { + payload.baseImage = 'denoland/deno:latest'; + } + if (buildPack === 'php') { + payload.baseImage = 'webdevops/php-apache:8.0-alpine'; + payload.baseImages = phpVersions; + } + return payload; +} diff --git a/src/lib/buildPacks/deno.ts b/src/lib/buildPacks/deno.ts index 2e5569438..8b7e30b5a 100644 --- a/src/lib/buildPacks/deno.ts +++ b/src/lib/buildPacks/deno.ts @@ -45,8 +45,8 @@ const createDockerfile = async (data, image): Promise => { export default async function (data) { try { - const image = 'denoland/deno:latest'; - await createDockerfile(data, image); + const { baseImage, baseBuildImage } = data; + await createDockerfile(data, baseImage); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/gatsby.ts b/src/lib/buildPacks/gatsby.ts index 38989626d..0cba2a48d 100644 --- a/src/lib/buildPacks/gatsby.ts +++ b/src/lib/buildPacks/gatsby.ts @@ -2,25 +2,25 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, imageforBuild): Promise => { - const { applicationId, tag, workdir, publishDirectory } = data; + const { applicationId, tag, workdir, publishDirectory, baseImage } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${imageforBuild}`); Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); - Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + if (baseImage.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } Dockerfile.push(`EXPOSE 80`); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); }; export default async function (data) { try { - const image = 'webdevops/nginx:alpine'; - const imageForBuild = 'node:lts'; - - await buildCacheImageWithNode(data, imageForBuild); - await createDockerfile(data, image); + const { baseImage, baseBuildImage } = data; + await buildCacheImageWithNode(data, baseImage); + await createDockerfile(data, baseBuildImage); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/nestjs.ts b/src/lib/buildPacks/nestjs.ts index 915bdd3d7..f529d7645 100644 --- a/src/lib/buildPacks/nestjs.ts +++ b/src/lib/buildPacks/nestjs.ts @@ -22,11 +22,9 @@ const createDockerfile = async (data, image): Promise => { export default async function (data) { try { - const image = 'node:lts'; - const imageForBuild = 'node:lts'; - - await buildCacheImageWithNode(data, imageForBuild); - await createDockerfile(data, image); + const { baseImage, baseBuildImage } = data; + await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/nextjs.ts b/src/lib/buildPacks/nextjs.ts index 0f58b3b84..59c50d7f1 100644 --- a/src/lib/buildPacks/nextjs.ts +++ b/src/lib/buildPacks/nextjs.ts @@ -50,8 +50,8 @@ const createDockerfile = async (data, image): Promise => { export default async function (data) { try { - const image = 'node:lts'; - await createDockerfile(data, image); + const { baseImage, baseBuildImage } = data; + await createDockerfile(data, baseImage); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/node.ts b/src/lib/buildPacks/node.ts index 869e28b5d..1231892b2 100644 --- a/src/lib/buildPacks/node.ts +++ b/src/lib/buildPacks/node.ts @@ -50,8 +50,8 @@ const createDockerfile = async (data, image): Promise => { export default async function (data) { try { - const image = 'node:lts'; - await createDockerfile(data, image); + const { baseImage, baseBuildImage } = data; + await createDockerfile(data, baseImage); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/nuxtjs.ts b/src/lib/buildPacks/nuxtjs.ts index bfa48bf73..9120b7d5c 100644 --- a/src/lib/buildPacks/nuxtjs.ts +++ b/src/lib/buildPacks/nuxtjs.ts @@ -49,8 +49,8 @@ const createDockerfile = async (data, image): Promise => { export default async function (data) { try { - const image = 'node:lts'; - await createDockerfile(data, image); + const { baseImage, baseBuildImage } = data; + await createDockerfile(data, baseImage); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/php.ts b/src/lib/buildPacks/php.ts index cfb39d20a..4bdfaf923 100644 --- a/src/lib/buildPacks/php.ts +++ b/src/lib/buildPacks/php.ts @@ -27,7 +27,7 @@ const createDockerfile = async (data, image, htaccessFound): Promise => { }; export default async function (data) { - const { workdir, baseDirectory } = data; + const { workdir, baseDirectory, baseImage } = data; try { let htaccessFound = false; try { @@ -36,10 +36,7 @@ export default async function (data) { } catch (e) { // } - const image = htaccessFound - ? 'webdevops/php-apache:8.0-alpine' - : 'webdevops/php-nginx:8.0-alpine'; - await createDockerfile(data, image, htaccessFound); + await createDockerfile(data, baseImage, htaccessFound); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/python.ts b/src/lib/buildPacks/python.ts index 1c6bdf6bf..f13965cf0 100644 --- a/src/lib/buildPacks/python.ts +++ b/src/lib/buildPacks/python.ts @@ -62,8 +62,8 @@ const createDockerfile = async (data, image): Promise => { export default async function (data) { try { - const image = 'python:3-alpine'; - await createDockerfile(data, image); + const { baseImage, baseBuildImage } = data; + await createDockerfile(data, baseImage); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/react.ts b/src/lib/buildPacks/react.ts index 719f782eb..4aca45621 100644 --- a/src/lib/buildPacks/react.ts +++ b/src/lib/buildPacks/react.ts @@ -2,24 +2,25 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { applicationId, tag, workdir, publishDirectory } = data; + const { applicationId, tag, workdir, publishDirectory, baseImage } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push('WORKDIR /app'); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); - Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + if (baseImage.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } Dockerfile.push(`EXPOSE 80`); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); }; export default async function (data) { try { - const image = 'webdevops/nginx:alpine'; - const imageForBuild = 'node:lts'; - await buildCacheImageWithNode(data, imageForBuild); - await createDockerfile(data, image); + const { baseImage, baseBuildImage } = data; + await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/rust.ts b/src/lib/buildPacks/rust.ts index 586d63140..5db5ad9b5 100644 --- a/src/lib/buildPacks/rust.ts +++ b/src/lib/buildPacks/rust.ts @@ -27,14 +27,12 @@ const createDockerfile = async (data, image, name): Promise => { export default async function (data) { try { - const { workdir } = data; - const image = 'rust:latest'; - const imageForBuild = 'rust:latest'; + const { workdir, baseImage, baseBuildImage } = data; const { stdout: cargoToml } = await asyncExecShell(`cat ${workdir}/Cargo.toml`); const parsedToml: any = TOML.parse(cargoToml); const name = parsedToml.package.name; - await buildCacheImageWithCargo(data, imageForBuild); - await createDockerfile(data, image, name); + await buildCacheImageWithCargo(data, baseBuildImage); + await createDockerfile(data, baseImage, name); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/static.ts b/src/lib/buildPacks/static.ts index e9e7179d5..20afefbba 100644 --- a/src/lib/buildPacks/static.ts +++ b/src/lib/buildPacks/static.ts @@ -10,7 +10,8 @@ const createDockerfile = async (data, image): Promise => { baseDirectory, publishDirectory, secrets, - pullmergeRequestId + pullmergeRequestId, + baseImage } = data; const Dockerfile: Array = []; @@ -37,17 +38,18 @@ const createDockerfile = async (data, image): Promise => { } else { Dockerfile.push(`COPY .${baseDirectory || ''} ./`); } - Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + if (baseImage.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } Dockerfile.push(`EXPOSE 80`); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); }; export default async function (data) { try { - const image = 'webdevops/nginx:alpine'; - const imageForBuild = 'node:lts'; - if (data.buildCommand) await buildCacheImageWithNode(data, imageForBuild); - await createDockerfile(data, image); + const { baseImage, baseBuildImage } = data; + if (data.buildCommand) await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/svelte.ts b/src/lib/buildPacks/svelte.ts index fbcf36abb..b63560a5d 100644 --- a/src/lib/buildPacks/svelte.ts +++ b/src/lib/buildPacks/svelte.ts @@ -2,25 +2,25 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { applicationId, tag, workdir, publishDirectory } = data; + const { applicationId, tag, workdir, publishDirectory, baseImage } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); - Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + if (baseImage.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } Dockerfile.push(`EXPOSE 80`); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); }; export default async function (data) { try { - const image = 'webdevops/nginx:alpine'; - const imageForBuild = 'node:lts'; - - await buildCacheImageWithNode(data, imageForBuild); - await createDockerfile(data, image); + const { baseImage, baseBuildImage } = data; + await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/buildPacks/vuejs.ts b/src/lib/buildPacks/vuejs.ts index fa80ac435..b63560a5d 100644 --- a/src/lib/buildPacks/vuejs.ts +++ b/src/lib/buildPacks/vuejs.ts @@ -2,24 +2,25 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { applicationId, tag, workdir, publishDirectory } = data; + const { applicationId, tag, workdir, publishDirectory, baseImage } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); - Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + if (baseImage.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } Dockerfile.push(`EXPOSE 80`); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); }; export default async function (data) { try { - const image = 'webdevops/nginx:alpine'; - const imageForBuild = 'node:lts'; - await buildCacheImageWithNode(data, imageForBuild); - await createDockerfile(data, image); + const { baseImage, baseBuildImage } = data; + await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); await buildImage(data); } catch (error) { throw error; diff --git a/src/lib/database/applications.ts b/src/lib/database/applications.ts index f02300bd2..d64537b69 100644 --- a/src/lib/database/applications.ts +++ b/src/lib/database/applications.ts @@ -12,17 +12,7 @@ import type { Application, ApplicationPersistentStorage } from '@prisma/client'; -const nodeBased = [ - 'react', - 'vuejs', - 'static', - 'svelte', - 'gatsby', - 'php', - 'astro', - 'eleventy', - 'node' -]; +import { setDefaultBaseImage } from '$lib/buildPacks/common'; export async function listApplications(teamId: string): Promise { if (teamId === '0') { @@ -206,26 +196,18 @@ export async function getApplication({ id, teamId }: { id: string; teamId: strin return s; }); } + const { baseImage, baseBuildImage, baseBuildImages, baseImages } = setDefaultBaseImage( + body.buildPack + ); + // Set default build images if (!body.baseImage) { - if (nodeBased.includes(body.buildPack)) { - body.baseImage = 'node:lts'; - } - if (body.buildPack === 'python') { - body.baseImage = 'python:3-alpine'; - } - if (body.buildPack === 'rust') { - body.baseImage = 'rust:latest'; - } - if (body.buildPack === 'deno') { - body.baseImage = 'denoland/deno:latest'; - } - if (body.buildPack === 'php') { - body.baseImage = 'webdevops/php-apache:8.0-alpine'; - } + body.baseImage = baseImage; } - - return { ...body }; + if (!body.baseBuildImage) { + body.baseBuildImage = baseBuildImage; + } + return { ...body, baseBuildImages, baseImages }; } export async function configureGitRepository({ @@ -296,7 +278,8 @@ export async function configureApplication({ dockerFileLocation, denoMainFile, denoOptions, - baseImage + baseImage, + baseBuildImage }: { id: string; buildPack: string; @@ -315,6 +298,7 @@ export async function configureApplication({ denoMainFile: string; denoOptions: string; baseImage: string; + baseBuildImage: string; }): Promise { return await prisma.application.update({ where: { id }, @@ -334,7 +318,8 @@ export async function configureApplication({ dockerFileLocation, denoMainFile, denoOptions, - baseImage + baseImage, + baseBuildImage } }); } diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json index 17f210b52..a5d6cbd50 100644 --- a/src/lib/locales/en.json +++ b/src/lib/locales/en.json @@ -185,6 +185,7 @@ "git_repository": "Git Repository", "build_pack": "Build Pack", "base_image": "Base Image", + "base_build_image": "Base Build Image", "destination": "Destination", "application": "Application", "url_fqdn": "URL (FQDN)", diff --git a/src/lib/queues/builder.ts b/src/lib/queues/builder.ts index 8e1650106..3a74320ef 100644 --- a/src/lib/queues/builder.ts +++ b/src/lib/queues/builder.ts @@ -47,7 +47,9 @@ export default async function (job: Job): Promise): Promise): Promise { dockerFileLocation, denoMainFile, denoOptions, - baseImage + baseImage, + baseBuildImage } = await event.request.json(); if (port) port = Number(port); if (denoOptions) denoOptions = denoOptions.trim(); @@ -98,6 +99,7 @@ export const post: RequestHandler = async (event) => { denoMainFile, denoOptions, baseImage, + baseBuildImage, ...defaultConfiguration }); return { status: 201 }; diff --git a/src/routes/applications/[id]/index.svelte b/src/routes/applications/[id]/index.svelte index f5d9f8c97..f2c5aa97c 100644 --- a/src/routes/applications/[id]/index.svelte +++ b/src/routes/applications/[id]/index.svelte @@ -33,6 +33,8 @@ gitlabApp: Prisma.GitlabApp; gitSource: Prisma.GitSource; destinationDocker: Prisma.DestinationDocker; + baseImages: Array<{ value: string; label: string }>; + baseBuildImages: Array<{ value: string; label: string }>; }; export let isRunning; import { page, session } from '$app/stores'; @@ -71,11 +73,14 @@ label: 'Gunicorn' } ]; - + function containerClass() { + if (!$session.isAdmin || isRunning) { + return 'text-white border border-dashed border-coolgray-300 bg-transparent font-thin px-0'; + } + } if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) { application.fqdn = `http://${cuid()}.demo.coolify.io`; } - onMount(() => { domainEl.focus(); }); @@ -138,6 +143,14 @@ async function selectWSGI(event) { application.pythonWSGI = event.detail.value; } + async function selectBaseImage(event) { + application.baseImage = event.detail.value; + await handleSubmit(); + } + async function selectBaseBuildImage(event) { + application.baseBuildImage = event.detail.value; + await handleSubmit(); + }
@@ -310,14 +323,42 @@ />
-
+
-
- +
+ +
+
+ {/if}
{$t('application.application')}
diff --git a/src/tailwind.css b/src/tailwind.css index 1e48bb81b..529c91cb9 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -46,14 +46,14 @@ textarea { } #svelte .custom-select-wrapper .selectContainer { - @apply h-12 w-96 rounded border-none bg-coolgray-200 p-2 text-xs font-bold tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm; + @apply h-12 w-96 rounded border-none bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm; } #svelte .listContainer { @apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-green-500 scrollbar-track-coolgray-200; } #svelte .selectedItem { - @apply pl-3; + @apply pl-2; } #svelte .item.hover { @@ -64,7 +64,7 @@ textarea { } select { - @apply h-12 w-96 rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm; + @apply h-12 w-96 rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm; } .svelte-select { --background: rgb(32 32 32); From c4833c3cc234b18f7a3eaa91df54904b9449a8e0 Mon Sep 17 00:00:00 2001 From: Burak Date: Tue, 26 Apr 2022 22:45:59 +0300 Subject: [PATCH 04/32] update readme for newly added services --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d214d42b7..a1b125a56 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ You can host cool open-source services as well: - [LanguageTool](https://languagetool.org) - [n8n](https://n8n.io) - [Uptime Kuma](https://github.com/louislam/uptime-kuma) +- [MeiliSearch](https://github.com/meilisearch/meilisearch) +- [Umami](https://github.com/mikecao/umami) ## Migration from v1 From 2cda0b22c2af37116d466ddf4688a5ba75167557 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 27 Apr 2022 14:56:24 +0200 Subject: [PATCH 05/32] chore: version++ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bb6f7d86c..934d8e6d1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "2.5.3", + "version": "2.6.0", "license": "AGPL-3.0", "scripts": { "dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0", From d75d2880e5255a4eb316f99f5294c420e9449ae1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 27 Apr 2022 15:19:07 +0200 Subject: [PATCH 06/32] fix: Unami svg size --- src/lib/components/svg/services/Umami.svelte | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/components/svg/services/Umami.svelte b/src/lib/components/svg/services/Umami.svelte index ac0df85af..e1c9b1e67 100644 --- a/src/lib/components/svg/services/Umami.svelte +++ b/src/lib/components/svg/services/Umami.svelte @@ -5,8 +5,6 @@ Date: Wed, 27 Apr 2022 15:37:50 +0200 Subject: [PATCH 07/32] feat: Hasura as a service --- .../20220427133656_hasura/migration.sql | 16 +++ prisma/schema.prisma | 14 ++ src/lib/components/ServiceLinks.svelte | 5 + src/lib/components/common.ts | 11 ++ src/lib/components/svg/services/Hasura.svelte | 26 ++++ src/lib/database/services.ts | 28 +++- .../services/[id]/_Services/_Hasura.svelte | 58 +++++++++ .../services/[id]/_Services/_Services.svelte | 3 + .../services/[id]/configuration/type.svelte | 3 + src/routes/services/[id]/hasura/index.json.ts | 21 +++ src/routes/services/[id]/hasura/start.json.ts | 122 ++++++++++++++++++ src/routes/services/[id]/hasura/stop.json.ts | 42 ++++++ src/routes/services/index.svelte | 5 + 13 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/20220427133656_hasura/migration.sql create mode 100644 src/lib/components/svg/services/Hasura.svelte create mode 100644 src/routes/services/[id]/_Services/_Hasura.svelte create mode 100644 src/routes/services/[id]/hasura/index.json.ts create mode 100644 src/routes/services/[id]/hasura/start.json.ts create mode 100644 src/routes/services/[id]/hasura/stop.json.ts diff --git a/prisma/migrations/20220427133656_hasura/migration.sql b/prisma/migrations/20220427133656_hasura/migration.sql new file mode 100644 index 000000000..c679ad0fb --- /dev/null +++ b/prisma/migrations/20220427133656_hasura/migration.sql @@ -0,0 +1,16 @@ +-- CreateTable +CREATE TABLE "Hasura" ( + "id" TEXT NOT NULL PRIMARY KEY, + "serviceId" TEXT NOT NULL, + "postgresqlUser" TEXT NOT NULL, + "postgresqlPassword" TEXT NOT NULL, + "postgresqlDatabase" TEXT NOT NULL, + "postgresqlPublicPort" INTEGER, + "graphQLAdminPassword" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Hasura_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "Hasura_serviceId_key" ON "Hasura"("serviceId"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 267dd1991..1c10822f7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -305,6 +305,7 @@ model Service { meiliSearch MeiliSearch? persistentStorage ServicePersistentStorage[] umami Umami? + hasura Hasura? } model PlausibleAnalytics { @@ -403,3 +404,16 @@ model Umami { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +model Hasura { + id String @id @default(cuid()) + serviceId String @unique + postgresqlUser String + postgresqlPassword String + postgresqlDatabase String + postgresqlPublicPort Int? + graphQLAdminPassword String + service Service @relation(fields: [serviceId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/src/lib/components/ServiceLinks.svelte b/src/lib/components/ServiceLinks.svelte index 980505119..6097abbd2 100644 --- a/src/lib/components/ServiceLinks.svelte +++ b/src/lib/components/ServiceLinks.svelte @@ -1,6 +1,7 @@
+
Hasura
+
+ +
+ + +
+ +
+
PostgreSQL
+
+ +
+ + +
+
+ + +
+
+ + +
diff --git a/src/routes/services/[id]/_Services/_Services.svelte b/src/routes/services/[id]/_Services/_Services.svelte index 85c1c5abf..21db6a598 100644 --- a/src/routes/services/[id]/_Services/_Services.svelte +++ b/src/routes/services/[id]/_Services/_Services.svelte @@ -13,6 +13,7 @@ import { t } from '$lib/translations'; import { toast } from '@zerodevx/svelte-toast'; import Ghost from './_Ghost.svelte'; + import Hasura from './_Hasura.svelte'; import MeiliSearch from './_MeiliSearch.svelte'; import MinIo from './_MinIO.svelte'; import PlausibleAnalytics from './_PlausibleAnalytics.svelte'; @@ -172,6 +173,8 @@ {:else if service.type === 'umami'} + {:else if service.type === 'hasura'} + {/if}
diff --git a/src/routes/services/[id]/configuration/type.svelte b/src/routes/services/[id]/configuration/type.svelte index 6e4b3628f..788a53c9d 100644 --- a/src/routes/services/[id]/configuration/type.svelte +++ b/src/routes/services/[id]/configuration/type.svelte @@ -44,6 +44,7 @@ import { t } from '$lib/translations'; import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte'; import Umami from '$lib/components/svg/services/Umami.svelte'; + import Hasura from '$lib/components/svg/services/Hasura.svelte'; const { id } = $page.params; const from = $page.url.searchParams.get('from'); @@ -93,6 +94,8 @@ {:else if type.name === 'umami'} + {:else if type.name === 'hasura'} + {/if}{type.fancyName} diff --git a/src/routes/services/[id]/hasura/index.json.ts b/src/routes/services/[id]/hasura/index.json.ts new file mode 100644 index 000000000..d717502c5 --- /dev/null +++ b/src/routes/services/[id]/hasura/index.json.ts @@ -0,0 +1,21 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { ErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + let { name, fqdn } = await event.request.json(); + if (fqdn) fqdn = fqdn.toLowerCase(); + + try { + await db.updateService({ id, fqdn, name }); + return { status: 201 }; + } catch (error) { + return ErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/hasura/start.json.ts b/src/routes/services/[id]/hasura/start.json.ts new file mode 100644 index 000000000..325d6e33c --- /dev/null +++ b/src/routes/services/[id]/hasura/start.json.ts @@ -0,0 +1,122 @@ +import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { promises as fs } from 'fs'; +import yaml from 'js-yaml'; +import type { RequestHandler } from '@sveltejs/kit'; +import { ErrorHandler, getServiceImage } from '$lib/database'; +import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; +import type { Service, DestinationDocker, Prisma } from '@prisma/client'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service: Service & Prisma.ServiceInclude & { destinationDocker: DestinationDocker } = + await db.getService({ id, teamId }); + const { + type, + version, + destinationDockerId, + destinationDocker, + serviceSecret, + hasura: { postgresqlUser, postgresqlPassword, postgresqlDatabase } + } = service; + const network = destinationDockerId && destinationDocker.network; + const host = getEngine(destinationDocker.engine); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + const image = getServiceImage(type); + + const config = { + hasura: { + image: `${image}:${version}`, + environmentVariables: { + HASURA_GRAPHQL_METADATA_DATABASE_URL: `postgresql://${postgresqlUser}:${postgresqlPassword}@${id}-postgresql:5432/${postgresqlDatabase}` + } + }, + postgresql: { + image: 'postgres:12-alpine', + volume: `${id}-postgresql-data:/var/lib/postgresql/data`, + environmentVariables: { + POSTGRES_USER: postgresqlUser, + POSTGRES_PASSWORD: postgresqlPassword, + POSTGRES_DB: postgresqlDatabase + } + } + }; + if (serviceSecret.length > 0) { + serviceSecret.forEach((secret) => { + config.hasura.environmentVariables[secret.name] = secret.value; + }); + } + + const composeFile: ComposeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image: config.hasura.image, + environment: config.hasura.environmentVariables, + networks: [network], + volumes: [], + restart: 'always', + labels: makeLabelForServices('hasura'), + deploy: { + restart_policy: { + condition: 'on-failure', + delay: '5s', + max_attempts: 3, + window: '120s' + } + }, + depends_on: [`${id}-postgresql`] + }, + [`${id}-postgresql`]: { + image: config.postgresql.image, + container_name: `${id}-postgresql`, + environment: config.postgresql.environmentVariables, + networks: [network], + volumes: [config.postgresql.volume], + restart: 'always', + deploy: { + restart_policy: { + condition: 'on-failure', + delay: '5s', + max_attempts: 3, + window: '120s' + } + } + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [config.postgresql.volume.split(':')[0]]: { + name: config.postgresql.volume.split(':')[0] + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + + try { + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + return { + status: 200 + }; + } catch (error) { + console.log(error); + return ErrorHandler(error); + } + } catch (error) { + return ErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/hasura/stop.json.ts b/src/routes/services/[id]/hasura/stop.json.ts new file mode 100644 index 000000000..67dd96d04 --- /dev/null +++ b/src/routes/services/[id]/hasura/stop.json.ts @@ -0,0 +1,42 @@ +import { getUserDetails, removeDestinationDocker } from '$lib/common'; +import * as db from '$lib/database'; +import { ErrorHandler } from '$lib/database'; +import { checkContainer, stopTcpHttpProxy } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { destinationDockerId, destinationDocker } = service; + if (destinationDockerId) { + const engine = destinationDocker.engine; + + try { + const found = await checkContainer(engine, id); + if (found) { + await removeDestinationDocker({ id, engine }); + } + } catch (error) { + console.error(error); + } + try { + const found = await checkContainer(engine, `${id}-postgresql`); + if (found) { + await removeDestinationDocker({ id: `${id}-postgresql`, engine }); + } + } catch (error) { + console.error(error); + } + } + return { + status: 200 + }; + } catch (error) { + return ErrorHandler(error); + } +}; diff --git a/src/routes/services/index.svelte b/src/routes/services/index.svelte index fd784d049..587037c86 100644 --- a/src/routes/services/index.svelte +++ b/src/routes/services/index.svelte @@ -16,6 +16,7 @@ import { session } from '$app/stores'; import { getDomain } from '$lib/components/common'; import Umami from '$lib/components/svg/services/Umami.svelte'; + import Hasura from '$lib/components/svg/services/Hasura.svelte'; export let services; async function newService() { @@ -89,6 +90,8 @@ {:else if service.type === 'umami'} + {:else if service.type === 'hasura'} + {/if}
{service.name} @@ -138,6 +141,8 @@ {:else if service.type === 'umami'} + {:else if service.type === 'hasura'} + {/if}
{service.name} From 106aee31bd54b3148de1fce6d418f398c2631e8a Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 28 Apr 2022 14:12:19 +0200 Subject: [PATCH 08/32] fix: Team switching moved to IAM menu --- src/routes/__layout.svelte | 28 +---------------- src/routes/iam/index.svelte | 62 +++++++++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index 87d55c9a3..c5c5daddf 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -25,7 +25,6 @@ if (res.ok) { return { props: { - selectedTeamId: session.teamId, ...(await res.json()) } }; @@ -35,9 +34,6 @@
@@ -175,20 +187,39 @@
{#each ownTeams as team} -
-
- {team.name} + From caaf030517e976f97145f63880a0c2400240216d Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 28 Apr 2022 15:10:45 +0200 Subject: [PATCH 09/32] WIP: Laravel --- src/lib/buildPacks/common.ts | 88 ++++++++++++++++++- src/lib/buildPacks/index.ts | 4 +- src/lib/buildPacks/laravel.ts | 36 ++++++++ src/lib/components/common.ts | 2 +- src/lib/components/templates.ts | 29 ++++-- src/lib/docker.ts | 28 ++++++ .../[id]/configuration/buildpack.svelte | 14 ++- src/routes/applications/[id]/index.svelte | 32 +++---- 8 files changed, 203 insertions(+), 30 deletions(-) create mode 100644 src/lib/buildPacks/laravel.ts diff --git a/src/lib/buildPacks/common.ts b/src/lib/buildPacks/common.ts index eb67a73bd..4c1b6053b 100644 --- a/src/lib/buildPacks/common.ts +++ b/src/lib/buildPacks/common.ts @@ -107,11 +107,12 @@ export const setDefaultConfiguration = async (data) => { else if (buildPack === 'php') port = 80; else if (buildPack === 'python') port = 8000; } - if (!installCommand && buildPack !== 'static') + if (!installCommand && buildPack !== 'static' && buildPack !== 'laravel') installCommand = template?.installCommand || 'yarn install'; - if (!startCommand && buildPack !== 'static') + if (!startCommand && buildPack !== 'static' && buildPack !== 'laravel') startCommand = template?.startCommand || 'yarn start'; - if (!buildCommand && buildPack !== 'static') buildCommand = template?.buildCommand || null; + if (!buildCommand && buildPack !== 'static' && buildPack !== 'laravel') + buildCommand = template?.buildCommand || null; if (!publishDirectory) publishDirectory = template?.publishDirectory || null; if (baseDirectory) { if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`; @@ -206,6 +207,71 @@ export async function copyBaseConfigurationFiles( } } + ` + ); + } else if (buildPack === 'laravel') { + await fs.writeFile( + `${workdir}/nginx.conf`, + `user nginx; + worker_processes auto; + + error_log /docker.stdout; + pid /run/nginx.pid; + + events { + worker_connections 1024; + } + + http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /docker.stdout main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + server { + listen 80; + server_name localhost; + + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options "nosniff"; + + index index.html index.htm index.php; + + charset utf-8; + + location / { + root /app/public; + try_files $uri $uri/ /index.php?$query_string; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + error_page 404 /index.php; + + location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.(?!well-known).* { + deny all; + } + } + } ` ); } @@ -392,6 +458,16 @@ export function setDefaultBaseImage(buildPack) { label: 'webdevops/php-nginx:7.1-alpine' } ]; + const laravelVersions = [ + { + value: 'webdevops/php-nginx:8.0-alpine', + label: 'webdevops/php-nginx:8.0-alpine' + }, + { + value: 'webdevops/php-apache:8.0-alpine', + label: 'webdevops/php-apache:8.0-alpine' + } + ]; let payload = { baseImage: null, baseBuildImage: null, @@ -424,5 +500,11 @@ export function setDefaultBaseImage(buildPack) { payload.baseImage = 'webdevops/php-apache:8.0-alpine'; payload.baseImages = phpVersions; } + if (buildPack === 'laravel') { + payload.baseImage = 'webdevops/php-nginx:8.0-alpine'; + payload.baseBuildImage = 'node:18'; + payload.baseImages = laravelVersions; + payload.baseBuildImages = nodeVersions; + } return payload; } diff --git a/src/lib/buildPacks/index.ts b/src/lib/buildPacks/index.ts index ac31afc16..163aa3a7d 100644 --- a/src/lib/buildPacks/index.ts +++ b/src/lib/buildPacks/index.ts @@ -14,6 +14,7 @@ import astro from './static'; import eleventy from './static'; import python from './python'; import deno from './deno'; +import laravel from './laravel'; export { node, @@ -31,5 +32,6 @@ export { astro, eleventy, python, - deno + deno, + laravel }; diff --git a/src/lib/buildPacks/laravel.ts b/src/lib/buildPacks/laravel.ts new file mode 100644 index 000000000..ce44fa7fe --- /dev/null +++ b/src/lib/buildPacks/laravel.ts @@ -0,0 +1,36 @@ +import { buildCacheImageForLaravel, buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, image): Promise => { + const { workdir, applicationId, tag } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + Dockerfile.push(`COPY composer.* ./`); + Dockerfile.push(`COPY database/ database/`); + Dockerfile.push( + `RUN composer install --ignore-platform-reqs --no-interaction --no-plugins --no-scripts --prefer-dist` + ); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/public/js/ /app/public/js/`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/public/css/ /app/public/css/`); + Dockerfile.push( + `COPY --from=${applicationId}:${tag}-cache /app/mix-manifest.json /app/public/mix-manifest.json` + ); + Dockerfile.push(`COPY . ./`); + Dockerfile.push(`EXPOSE 80`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + const { baseImage, baseBuildImage } = data; + try { + await buildCacheImageForLaravel(data, baseBuildImage); + await createDockerfile(data, baseImage); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/components/common.ts b/src/lib/components/common.ts index 34a7b4067..9f0d16263 100644 --- a/src/lib/components/common.ts +++ b/src/lib/components/common.ts @@ -19,7 +19,7 @@ export const staticDeployments = [ 'astro', 'eleventy' ]; -export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno']; +export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel']; export function getDomain(domain) { return domain?.replace('https://', '').replace('http://', ''); diff --git a/src/lib/components/templates.ts b/src/lib/components/templates.ts index 3ad83a3ba..5166baabc 100644 --- a/src/lib/components/templates.ts +++ b/src/lib/components/templates.ts @@ -162,6 +162,16 @@ export function findBuildPack(pack, packageManager = 'npm') { port: 8000 }; } + if (pack === 'laravel') { + return { + ...metaData, + installCommand: null, + buildCommand: null, + startCommand: null, + publishDirectory: null, + port: 80 + }; + } return { name: 'node', fancyName: 'Node.js', @@ -187,18 +197,25 @@ export const buildPacks = [ hoverColor: 'hover:bg-orange-700', color: 'bg-orange-700' }, - { - name: 'docker', - fancyName: 'Docker', - hoverColor: 'hover:bg-sky-700', - color: 'bg-sky-700' - }, + { name: 'php', fancyName: 'PHP', hoverColor: 'hover:bg-indigo-700', color: 'bg-indigo-700' }, + { + name: 'laravel', + fancyName: 'Laravel', + hoverColor: 'hover:bg-indigo-700', + color: 'bg-indigo-700' + }, + { + name: 'docker', + fancyName: 'Docker', + hoverColor: 'hover:bg-sky-700', + color: 'bg-sky-700' + }, { name: 'svelte', fancyName: 'Svelte', diff --git a/src/lib/docker.ts b/src/lib/docker.ts index 196ee3340..478ae54fb 100644 --- a/src/lib/docker.ts +++ b/src/lib/docker.ts @@ -3,6 +3,34 @@ import { promises as fs } from 'fs'; import { checkPnpm } from './buildPacks/common'; import { saveBuildLog } from './common'; +export async function buildCacheImageForLaravel(data, imageForBuild) { + const { applicationId, tag, workdir, docker, buildId, debug, secrets, pullmergeRequestId } = data; + const Dockerfile: Array = []; + Dockerfile.push(`FROM ${imageForBuild}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.image=true`); + if (secrets.length > 0) { + secrets.forEach((secret) => { + if (secret.isBuildSecret) { + if (pullmergeRequestId) { + if (secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name}=${secret.value}`); + } + } else { + if (!secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name}=${secret.value}`); + } + } + } + }); + } + Dockerfile.push(`COPY *.json *.mix.js /app/`); + Dockerfile.push(`COPY resources /app/resources`); + Dockerfile.push(`RUN yarn install && yarn production`); + await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); + await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); +} + export async function buildCacheImageWithNode(data, imageForBuild) { const { applicationId, diff --git a/src/routes/applications/[id]/configuration/buildpack.svelte b/src/routes/applications/[id]/configuration/buildpack.svelte index 13e6f8e10..7fe2c95ad 100644 --- a/src/routes/applications/[id]/configuration/buildpack.svelte +++ b/src/routes/applications/[id]/configuration/buildpack.svelte @@ -85,13 +85,14 @@ const composerPHP = files.find( (file) => file.name === 'composer.json' && file.type === 'blob' ); + const laravel = files.find((file) => file.name === 'artisan' && file.type === 'blob'); if (yarnLock) packageManager = 'yarn'; if (pnpmLock) packageManager = 'pnpm'; if (dockerfile) { foundConfig = findBuildPack('docker', packageManager); - } else if (packageJson) { + } else if (packageJson && !laravel) { const path = packageJson.path; const data = await get( `${apiUrl}/v4/projects/${projectId}/repository/files/${path}/raw?ref=${branch}`, @@ -107,8 +108,10 @@ foundConfig = findBuildPack('python'); } else if (indexHtml) { foundConfig = findBuildPack('static', packageManager); - } else if (indexPHP || composerPHP) { + } else if ((indexPHP || composerPHP) && !laravel) { foundConfig = findBuildPack('php'); + } else if (laravel) { + foundConfig = findBuildPack('laravel'); } else { foundConfig = findBuildPack('node', packageManager); } @@ -134,13 +137,14 @@ const composerPHP = files.find( (file) => file.name === 'composer.json' && file.type === 'file' ); + const laravel = files.find((file) => file.name === 'artisan' && file.type === 'file'); if (yarnLock) packageManager = 'yarn'; if (pnpmLock) packageManager = 'pnpm'; if (dockerfile) { foundConfig = findBuildPack('docker', packageManager); - } else if (packageJson) { + } else if (packageJson && !laravel) { const data = await get(`${packageJson.git_url}`, { Authorization: `Bearer ${$gitTokens.githubToken}`, Accept: 'application/vnd.github.v2.raw' @@ -153,8 +157,10 @@ foundConfig = findBuildPack('python'); } else if (indexHtml) { foundConfig = findBuildPack('static', packageManager); - } else if (indexPHP || composerPHP) { + } else if ((indexPHP || composerPHP) && !laravel) { foundConfig = findBuildPack('php'); + } else if (laravel) { + foundConfig = findBuildPack('laravel'); } else { foundConfig = findBuildPack('node', packageManager); } diff --git a/src/routes/applications/[id]/index.svelte b/src/routes/applications/[id]/index.svelte index f2c5aa97c..5f801e05b 100644 --- a/src/routes/applications/[id]/index.svelte +++ b/src/routes/applications/[id]/index.svelte @@ -340,7 +340,7 @@ />
- {#if application.buildCommand || application.buildPack === 'rust'} + {#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'}
{/if} -
-
- - + {#if application.buildPack !== 'laravel'} +
+
+ + +
+
- -
+ {/if} {#if !notNodeDeployments.includes(application.buildPack)}
From f8f17832de35e18408851964302b0545d9d7c85c Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 28 Apr 2022 16:31:46 +0200 Subject: [PATCH 10/32] WIP Laravel --- src/lib/buildPacks/common.ts | 17 +++++++++++------ src/lib/buildPacks/laravel.ts | 16 ++++++++++------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/lib/buildPacks/common.ts b/src/lib/buildPacks/common.ts index 4c1b6053b..89aa67f99 100644 --- a/src/lib/buildPacks/common.ts +++ b/src/lib/buildPacks/common.ts @@ -184,7 +184,7 @@ export async function copyBaseConfigurationFiles( include /etc/nginx/mime.types; default_type application/octet-stream; - + server { listen 80; server_name localhost; @@ -237,21 +237,22 @@ export async function copyBaseConfigurationFiles( include /etc/nginx/mime.types; default_type application/octet-stream; - + server { listen 80; - server_name localhost; + server_name _; + disable_symlinks off; add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; add_header X-Content-Type-Options "nosniff"; - + + root /app/public; index index.html index.htm index.php; charset utf-8; location / { - root /app/public; try_files $uri $uri/ /index.php?$query_string; } @@ -260,7 +261,11 @@ export async function copyBaseConfigurationFiles( error_page 404 /index.php; - location ~ \.php$ { + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + if (!-f $document_root$fastcgi_script_name) { + return 404; + } fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; diff --git a/src/lib/buildPacks/laravel.ts b/src/lib/buildPacks/laravel.ts index ce44fa7fe..61c2d7f6e 100644 --- a/src/lib/buildPacks/laravel.ts +++ b/src/lib/buildPacks/laravel.ts @@ -9,17 +9,21 @@ const createDockerfile = async (data, image): Promise => { Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push('WORKDIR /app'); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); - Dockerfile.push(`COPY composer.* ./`); - Dockerfile.push(`COPY database/ database/`); + Dockerfile.push(`COPY --chown=application:application composer.* ./`); + Dockerfile.push(`COPY --chown=application:application database/ database/`); Dockerfile.push( `RUN composer install --ignore-platform-reqs --no-interaction --no-plugins --no-scripts --prefer-dist` ); - Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/public/js/ /app/public/js/`); - Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/public/css/ /app/public/css/`); Dockerfile.push( - `COPY --from=${applicationId}:${tag}-cache /app/mix-manifest.json /app/public/mix-manifest.json` + `COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/public/js/ /app/public/js/` ); - Dockerfile.push(`COPY . ./`); + Dockerfile.push( + `COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/public/css/ /app/public/css/` + ); + Dockerfile.push( + `COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/mix-manifest.json /app/public/mix-manifest.json` + ); + Dockerfile.push(`COPY --chown=application:application . ./`); Dockerfile.push(`EXPOSE 80`); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); }; From 1140afe2c93e74f475e6ce9061642f730bf8dd02 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 28 Apr 2022 16:40:23 +0200 Subject: [PATCH 11/32] feat: gzip compression --- src/lib/haproxy/configuration.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/haproxy/configuration.ts b/src/lib/haproxy/configuration.ts index f2f9f500b..745e8f4af 100644 --- a/src/lib/haproxy/configuration.ts +++ b/src/lib/haproxy/configuration.ts @@ -95,6 +95,8 @@ backend {{domain}} {{/isHttps}} http-request add-header X-Forwarded-Host %[req.hdr(host),lower] server {{id}} {{id}}:{{port}} + compression algo gzip + compression type text/html text/css text/plain text/xml text/x-component text/javascript application/x-javascript application/javascript application/json application/manifest+json application/vnd.api+json application/xml application/xhtml+xml application/rss+xml application/atom+xml application/vnd.ms-fontobject application/x-font-ttf application/x-font-opentype application/x-font-truetype image/svg+xml image/x-icon image/vnd.microsoft.icon font/ttf font/eot font/otf font/opentype {{/isRunning}} {{/applications}} @@ -111,6 +113,8 @@ backend {{domain}} {{/isHttps}} http-request add-header X-Forwarded-Host %[req.hdr(host),lower] server {{id}} {{id}}:{{port}} + compression algo gzip + compression type text/html text/css text/plain text/xml text/x-component text/javascript application/x-javascript application/javascript application/json application/manifest+json application/vnd.api+json application/xml application/xhtml+xml application/rss+xml application/atom+xml application/vnd.ms-fontobject application/x-font-ttf application/x-font-opentype application/x-font-truetype image/svg+xml image/x-icon image/vnd.microsoft.icon font/ttf font/eot font/otf font/opentype {{/isRunning}} {{/services}} @@ -126,6 +130,8 @@ backend {{domain}} {{/isHttps}} http-request add-header X-Forwarded-Host %[req.hdr(host),lower] server {{id}} {{id}}:{{port}} check fall 10 + compression algo gzip + compression type text/html text/css text/plain text/xml text/x-component text/javascript application/x-javascript application/javascript application/json application/manifest+json application/vnd.api+json application/xml application/xhtml+xml application/rss+xml application/atom+xml application/vnd.ms-fontobject application/x-font-ttf application/x-font-opentype application/x-font-truetype image/svg+xml image/x-icon image/vnd.microsoft.icon font/ttf font/eot font/otf font/opentype {{/coolify}} `; From b6e6a1ccf1ba8ba01ff6f516ddce6db85da103f8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 28 Apr 2022 16:40:32 +0200 Subject: [PATCH 12/32] WIP laravel --- src/lib/buildPacks/common.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/buildPacks/common.ts b/src/lib/buildPacks/common.ts index 89aa67f99..4e2ccf539 100644 --- a/src/lib/buildPacks/common.ts +++ b/src/lib/buildPacks/common.ts @@ -237,7 +237,6 @@ export async function copyBaseConfigurationFiles( include /etc/nginx/mime.types; default_type application/octet-stream; - server { listen 80; server_name _; From 531973baabd8202db35844b396a45d9b4c0c048b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 29 Apr 2022 11:26:31 +0200 Subject: [PATCH 13/32] feat: Laravel buildpack is working! --- src/lib/buildPacks/common.ts | 69 ------------------- src/lib/buildPacks/laravel.ts | 4 +- .../svg/applications/Laravel.svelte | 10 +++ src/routes/applications/index.svelte | 5 ++ 4 files changed, 17 insertions(+), 71 deletions(-) create mode 100644 src/lib/components/svg/applications/Laravel.svelte diff --git a/src/lib/buildPacks/common.ts b/src/lib/buildPacks/common.ts index 4e2ccf539..1bdf75656 100644 --- a/src/lib/buildPacks/common.ts +++ b/src/lib/buildPacks/common.ts @@ -207,75 +207,6 @@ export async function copyBaseConfigurationFiles( } } - ` - ); - } else if (buildPack === 'laravel') { - await fs.writeFile( - `${workdir}/nginx.conf`, - `user nginx; - worker_processes auto; - - error_log /docker.stdout; - pid /run/nginx.pid; - - events { - worker_connections 1024; - } - - http { - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /docker.stdout main; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - server { - listen 80; - server_name _; - disable_symlinks off; - - add_header X-Frame-Options "SAMEORIGIN"; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Content-Type-Options "nosniff"; - - root /app/public; - index index.html index.htm index.php; - - charset utf-8; - - location / { - try_files $uri $uri/ /index.php?$query_string; - } - - location = /favicon.ico { access_log off; log_not_found off; } - location = /robots.txt { access_log off; log_not_found off; } - - error_page 404 /index.php; - - location ~ \.php$ { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - if (!-f $document_root$fastcgi_script_name) { - return 404; - } - fastcgi_pass 127.0.0.1:9000; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - include fastcgi_params; - } - - location ~ /\.(?!well-known).* { - deny all; - } - } - } ` ); } diff --git a/src/lib/buildPacks/laravel.ts b/src/lib/buildPacks/laravel.ts index 61c2d7f6e..bf5fcc6a2 100644 --- a/src/lib/buildPacks/laravel.ts +++ b/src/lib/buildPacks/laravel.ts @@ -2,13 +2,13 @@ import { buildCacheImageForLaravel, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { workdir, applicationId, tag } = data; + const { workdir, applicationId, tag, baseImage } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + Dockerfile.push(`ENV WEB_DOCUMENT_ROOT /app/public`); Dockerfile.push(`COPY --chown=application:application composer.* ./`); Dockerfile.push(`COPY --chown=application:application database/ database/`); Dockerfile.push( diff --git a/src/lib/components/svg/applications/Laravel.svelte b/src/lib/components/svg/applications/Laravel.svelte new file mode 100644 index 000000000..ab544a596 --- /dev/null +++ b/src/lib/components/svg/applications/Laravel.svelte @@ -0,0 +1,10 @@ +Logomark diff --git a/src/routes/applications/index.svelte b/src/routes/applications/index.svelte index 3295b8eeb..07e4564c5 100644 --- a/src/routes/applications/index.svelte +++ b/src/routes/applications/index.svelte @@ -22,6 +22,7 @@ import Astro from '$lib/components/svg/applications/Astro.svelte'; import Eleventy from '$lib/components/svg/applications/Eleventy.svelte'; import Deno from '$lib/components/svg/applications/Deno.svelte'; + import Laravel from '$lib/components/svg/applications/Laravel.svelte'; async function newApplication() { const { id } = await post('/applications/new', {}); @@ -104,6 +105,8 @@ {:else if application.buildPack.toLowerCase() === 'deno'} + {:else if application.buildPack.toLowerCase() === 'laravel'} + {/if} {/if} @@ -162,6 +165,8 @@ {:else if application.buildPack.toLowerCase() === 'deno'} + {:else if application.buildPack.toLowerCase() === 'laravel'} + {/if} {/if} From e5b1ce4eefd1b8645a6b2ea8081e3e1aee8def5f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 29 Apr 2022 22:24:14 +0200 Subject: [PATCH 14/32] feat: Laravel --- src/lib/buildPacks/common.ts | 7 +---- .../[id]/configuration/buildpack.json.ts | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/lib/buildPacks/common.ts b/src/lib/buildPacks/common.ts index 1bdf75656..d9f3416b9 100644 --- a/src/lib/buildPacks/common.ts +++ b/src/lib/buildPacks/common.ts @@ -394,10 +394,6 @@ export function setDefaultBaseImage(buildPack) { } ]; const laravelVersions = [ - { - value: 'webdevops/php-nginx:8.0-alpine', - label: 'webdevops/php-nginx:8.0-alpine' - }, { value: 'webdevops/php-apache:8.0-alpine', label: 'webdevops/php-apache:8.0-alpine' @@ -436,9 +432,8 @@ export function setDefaultBaseImage(buildPack) { payload.baseImages = phpVersions; } if (buildPack === 'laravel') { - payload.baseImage = 'webdevops/php-nginx:8.0-alpine'; + payload.baseImage = 'webdevops/php-apache:8.0-alpine'; payload.baseBuildImage = 'node:18'; - payload.baseImages = laravelVersions; payload.baseBuildImages = nodeVersions; } return payload; diff --git a/src/routes/applications/[id]/configuration/buildpack.json.ts b/src/routes/applications/[id]/configuration/buildpack.json.ts index 3f1374f17..6e06ba44b 100644 --- a/src/routes/applications/[id]/configuration/buildpack.json.ts +++ b/src/routes/applications/[id]/configuration/buildpack.json.ts @@ -1,7 +1,7 @@ import { getUserDetails } from '$lib/common'; import * as db from '$lib/database'; import type { RequestHandler } from '@sveltejs/kit'; -import { ErrorHandler } from '$lib/database'; +import { ErrorHandler, generatePassword } from '$lib/database'; export const get: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -34,6 +34,30 @@ export const post: RequestHandler = async (event) => { try { await db.configureBuildPack({ id, buildPack }); + + // Generate default secrets + if (buildPack === 'laravel') { + let found = await db.isSecretExists({ id, name: 'APP_ENV', isPRMRSecret: false }); + if (!found) { + await db.createSecret({ + id, + name: 'APP_ENV', + value: 'production', + isBuildSecret: false, + isPRMRSecret: false + }); + } + found = await db.isSecretExists({ id, name: 'APP_KEY', isPRMRSecret: false }); + if (!found) { + await db.createSecret({ + id, + name: 'APP_KEY', + value: generatePassword(32), + isBuildSecret: false, + isPRMRSecret: false + }); + } + } return { status: 201 }; } catch (error) { return ErrorHandler(error); From a3fd95020d09a8b7ad18f193c7698bfb4a9c2b51 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 29 Apr 2022 22:25:04 +0200 Subject: [PATCH 15/32] feat: Fider service --- src/lib/components/ServiceLinks.svelte | 5 + src/lib/components/common.ts | 11 ++ .../services/[id]/_Services/_Fider.svelte | 183 ++++++++++++++++++ .../services/[id]/_Services/_Services.svelte | 3 + .../services/[id]/configuration/type.svelte | 3 + src/routes/services/[id]/fider/index.json.ts | 57 ++++++ src/routes/services/[id]/fider/start.json.ts | 147 ++++++++++++++ src/routes/services/[id]/fider/stop.json.ts | 42 ++++ src/routes/services/index.svelte | 5 + 9 files changed, 456 insertions(+) create mode 100644 src/routes/services/[id]/_Services/_Fider.svelte create mode 100644 src/routes/services/[id]/fider/index.json.ts create mode 100644 src/routes/services/[id]/fider/start.json.ts create mode 100644 src/routes/services/[id]/fider/stop.json.ts diff --git a/src/lib/components/ServiceLinks.svelte b/src/lib/components/ServiceLinks.svelte index 6097abbd2..b8bafc369 100644 --- a/src/lib/components/ServiceLinks.svelte +++ b/src/lib/components/ServiceLinks.svelte @@ -12,6 +12,7 @@ import VaultWarden from './svg/services/VaultWarden.svelte'; import VsCodeServer from './svg/services/VSCodeServer.svelte'; import Wordpress from './svg/services/Wordpress.svelte'; + import Fider from './svg/services/Fider.svelte'; {#if service.type === 'plausibleanalytics'} @@ -62,4 +63,8 @@ +{:else if service.type === 'fider'} + + + {/if} diff --git a/src/lib/components/common.ts b/src/lib/components/common.ts index 9f0d16263..d6ff2f8ea 100644 --- a/src/lib/components/common.ts +++ b/src/lib/components/common.ts @@ -202,5 +202,16 @@ export const supportedServiceTypesAndVersions = [ ports: { main: 8080 } + }, + { + name: 'fider', + fancyName: 'Fider', + baseImage: 'getfider/fider', + images: ['postgres:12-alpine'], + versions: ['stable'], + recommendedVersion: 'stable', + ports: { + main: 3000 + } } ]; diff --git a/src/routes/services/[id]/_Services/_Fider.svelte b/src/routes/services/[id]/_Services/_Fider.svelte new file mode 100644 index 000000000..df494de94 --- /dev/null +++ b/src/routes/services/[id]/_Services/_Fider.svelte @@ -0,0 +1,183 @@ + + +
+
Fider
+
+ +
+ + +
+ +
+ + +
+
+
Email
+
+
+ + +
+ +
+ + +
+
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
PostgreSQL
+
+ +
+ + +
+
+ + +
+
+ + +
diff --git a/src/routes/services/[id]/_Services/_Services.svelte b/src/routes/services/[id]/_Services/_Services.svelte index 21db6a598..a2a2effac 100644 --- a/src/routes/services/[id]/_Services/_Services.svelte +++ b/src/routes/services/[id]/_Services/_Services.svelte @@ -12,6 +12,7 @@ import { errorNotification } from '$lib/form'; import { t } from '$lib/translations'; import { toast } from '@zerodevx/svelte-toast'; + import Fider from './_Fider.svelte'; import Ghost from './_Ghost.svelte'; import Hasura from './_Hasura.svelte'; import MeiliSearch from './_MeiliSearch.svelte'; @@ -175,6 +176,8 @@ {:else if service.type === 'hasura'} + {:else if service.type === 'fider'} + {/if}
diff --git a/src/routes/services/[id]/configuration/type.svelte b/src/routes/services/[id]/configuration/type.svelte index 788a53c9d..a736c7f15 100644 --- a/src/routes/services/[id]/configuration/type.svelte +++ b/src/routes/services/[id]/configuration/type.svelte @@ -45,6 +45,7 @@ import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte'; import Umami from '$lib/components/svg/services/Umami.svelte'; import Hasura from '$lib/components/svg/services/Hasura.svelte'; + import Fider from '$lib/components/svg/services/Fider.svelte'; const { id } = $page.params; const from = $page.url.searchParams.get('from'); @@ -96,6 +97,8 @@ {:else if type.name === 'hasura'} + {:else if type.name === 'fider'} + {/if}{type.fancyName} diff --git a/src/routes/services/[id]/fider/index.json.ts b/src/routes/services/[id]/fider/index.json.ts new file mode 100644 index 000000000..b561e3392 --- /dev/null +++ b/src/routes/services/[id]/fider/index.json.ts @@ -0,0 +1,57 @@ +import { getUserDetails } from '$lib/common'; +import { encrypt } from '$lib/crypto'; +import * as db from '$lib/database'; +import { ErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + let { + name, + fqdn, + fider: { + emailNoreply, + emailMailgunApiKey, + emailMailgunDomain, + emailMailgunRegion, + emailSmtpHost, + emailSmtpPort, + emailSmtpUser, + emailSmtpPassword, + emailSmtpEnableStartTls + } + } = await event.request.json(); + + if (fqdn) fqdn = fqdn.toLowerCase(); + if (emailNoreply) emailNoreply = emailNoreply.toLowerCase(); + if (emailSmtpHost) emailSmtpHost = emailSmtpHost.toLowerCase(); + if (emailSmtpPassword) { + emailSmtpPassword = encrypt(emailSmtpPassword); + } + if (emailSmtpPort) emailSmtpPort = Number(emailSmtpPort); + if (emailSmtpEnableStartTls) emailSmtpEnableStartTls = Boolean(emailSmtpEnableStartTls); + + try { + await db.updateFiderService({ + id, + fqdn, + name, + emailNoreply, + emailMailgunApiKey, + emailMailgunDomain, + emailMailgunRegion, + emailSmtpHost, + emailSmtpPort, + emailSmtpUser, + emailSmtpPassword, + emailSmtpEnableStartTls + }); + return { status: 201 }; + } catch (error) { + return ErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/fider/start.json.ts b/src/routes/services/[id]/fider/start.json.ts new file mode 100644 index 000000000..465b4195b --- /dev/null +++ b/src/routes/services/[id]/fider/start.json.ts @@ -0,0 +1,147 @@ +import { + asyncExecShell, + createDirectories, + getDomain, + getEngine, + getUserDetails +} from '$lib/common'; +import * as db from '$lib/database'; +import { promises as fs } from 'fs'; +import yaml from 'js-yaml'; +import type { RequestHandler } from '@sveltejs/kit'; +import { ErrorHandler, getServiceImage } from '$lib/database'; +import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; +import type { Service, DestinationDocker, Prisma } from '@prisma/client'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service: Service & Prisma.ServiceInclude & { destinationDocker: DestinationDocker } = + await db.getService({ id, teamId }); + const { + type, + version, + fqdn, + destinationDockerId, + destinationDocker, + serviceSecret, + fider: { + postgresqlUser, + postgresqlPassword, + postgresqlDatabase, + jwtSecret, + emailNoreply, + emailMailgunApiKey, + emailMailgunDomain, + emailSmtpHost, + emailSmtpPort, + emailSmtpUser, + emailSmtpPassword, + emailSmtpEnableStartTls + } + } = service; + const network = destinationDockerId && destinationDocker.network; + const host = getEngine(destinationDocker.engine); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + const image = getServiceImage(type); + const domain = getDomain(fqdn); + const config = { + fider: { + image: `${image}:${version}`, + environmentVariables: { + HOST_DOMAIN: domain, + DATABASE_URL: `postgresql://${postgresqlUser}:${postgresqlPassword}@${id}-postgresql:5432/${postgresqlDatabase}?sslmode=disable`, + JWT_SECRET: `${jwtSecret.replace(/\$/g, '$$$')}`, + EMAIL_NOREPLY: emailNoreply, + EMAIL_MAILGUN_API: emailMailgunApiKey || null, + EMAIL_MAILGUN_DOMAIN: emailMailgunDomain || null + } + }, + postgresql: { + image: 'postgres:12-alpine', + volume: `${id}-postgresql-data:/var/lib/postgresql/data`, + environmentVariables: { + POSTGRES_USER: postgresqlUser, + POSTGRES_PASSWORD: postgresqlPassword, + POSTGRES_DB: postgresqlDatabase + } + } + }; + if (serviceSecret.length > 0) { + serviceSecret.forEach((secret) => { + config.fider.environmentVariables[secret.name] = secret.value; + }); + } + + const composeFile: ComposeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image: config.fider.image, + environment: config.fider.environmentVariables, + networks: [network], + volumes: [], + restart: 'always', + labels: makeLabelForServices('fider'), + deploy: { + restart_policy: { + condition: 'on-failure', + delay: '5s', + max_attempts: 3, + window: '120s' + } + }, + depends_on: [`${id}-postgresql`] + }, + [`${id}-postgresql`]: { + image: config.postgresql.image, + container_name: `${id}-postgresql`, + environment: config.postgresql.environmentVariables, + networks: [network], + volumes: [config.postgresql.volume], + restart: 'always', + deploy: { + restart_policy: { + condition: 'on-failure', + delay: '5s', + max_attempts: 3, + window: '120s' + } + } + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [config.postgresql.volume.split(':')[0]]: { + name: config.postgresql.volume.split(':')[0] + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + + try { + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + return { + status: 200 + }; + } catch (error) { + console.log(error); + return ErrorHandler(error); + } + } catch (error) { + return ErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/fider/stop.json.ts b/src/routes/services/[id]/fider/stop.json.ts new file mode 100644 index 000000000..67dd96d04 --- /dev/null +++ b/src/routes/services/[id]/fider/stop.json.ts @@ -0,0 +1,42 @@ +import { getUserDetails, removeDestinationDocker } from '$lib/common'; +import * as db from '$lib/database'; +import { ErrorHandler } from '$lib/database'; +import { checkContainer, stopTcpHttpProxy } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { destinationDockerId, destinationDocker } = service; + if (destinationDockerId) { + const engine = destinationDocker.engine; + + try { + const found = await checkContainer(engine, id); + if (found) { + await removeDestinationDocker({ id, engine }); + } + } catch (error) { + console.error(error); + } + try { + const found = await checkContainer(engine, `${id}-postgresql`); + if (found) { + await removeDestinationDocker({ id: `${id}-postgresql`, engine }); + } + } catch (error) { + console.error(error); + } + } + return { + status: 200 + }; + } catch (error) { + return ErrorHandler(error); + } +}; diff --git a/src/routes/services/index.svelte b/src/routes/services/index.svelte index 587037c86..bab3a4322 100644 --- a/src/routes/services/index.svelte +++ b/src/routes/services/index.svelte @@ -17,6 +17,7 @@ import { getDomain } from '$lib/components/common'; import Umami from '$lib/components/svg/services/Umami.svelte'; import Hasura from '$lib/components/svg/services/Hasura.svelte'; + import Fider from '$lib/components/svg/services/Fider.svelte'; export let services; async function newService() { @@ -92,6 +93,8 @@ {:else if service.type === 'hasura'} + {:else if service.type === 'fider'} + {/if}
{service.name} @@ -143,6 +146,8 @@ {:else if service.type === 'hasura'} + {:else if service.type === 'fider'} + {/if}
{service.name} From 2bd91fa97090ed52d7b0cf956509c51cbadf7b7f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 29 Apr 2022 22:25:27 +0200 Subject: [PATCH 16/32] migration for fider --- .../20220429202516_fider/migration.sql | 25 +++++++++++++++++++ prisma/schema.prisma | 23 +++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 prisma/migrations/20220429202516_fider/migration.sql diff --git a/prisma/migrations/20220429202516_fider/migration.sql b/prisma/migrations/20220429202516_fider/migration.sql new file mode 100644 index 000000000..a6d31a24d --- /dev/null +++ b/prisma/migrations/20220429202516_fider/migration.sql @@ -0,0 +1,25 @@ +-- CreateTable +CREATE TABLE "Fider" ( + "id" TEXT NOT NULL PRIMARY KEY, + "serviceId" TEXT NOT NULL, + "postgresqlUser" TEXT NOT NULL, + "postgresqlPassword" TEXT NOT NULL, + "postgresqlDatabase" TEXT NOT NULL, + "postgresqlPublicPort" INTEGER, + "jwtSecret" TEXT NOT NULL, + "emailNoreply" TEXT, + "emailMailgunApiKey" TEXT, + "emailMailgunDomain" TEXT, + "emailMailgunRegion" TEXT, + "emailSmtpHost" TEXT, + "emailSmtpPort" INTEGER, + "emailSmtpUser" TEXT, + "emailSmtpPassword" TEXT, + "emailSmtpEnableStartTls" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Fider_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "Fider_serviceId_key" ON "Fider"("serviceId"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1c10822f7..9930b8505 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -306,6 +306,7 @@ model Service { persistentStorage ServicePersistentStorage[] umami Umami? hasura Hasura? + fider Fider? } model PlausibleAnalytics { @@ -417,3 +418,25 @@ model Hasura { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +model Fider { + id String @id @default(cuid()) + serviceId String @unique + postgresqlUser String + postgresqlPassword String + postgresqlDatabase String + postgresqlPublicPort Int? + jwtSecret String + emailNoreply String? + emailMailgunApiKey String? + emailMailgunDomain String? + emailMailgunRegion String? + emailSmtpHost String? + emailSmtpPort Int? + emailSmtpUser String? + emailSmtpPassword String? + emailSmtpEnableStartTls Boolean @default(false) + service Service @relation(fields: [serviceId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} From 8e42203b897af8b34782e0b7052337eeef5161c8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 29 Apr 2022 22:25:37 +0200 Subject: [PATCH 17/32] feat: Database and services logs --- src/lib/components/svg/databases/Redis.svelte | 2 - src/lib/components/svg/services/Fider.svelte | 121 ++++++++++++ src/lib/database/common.ts | 5 +- src/lib/database/services.ts | 108 +++++++---- src/lib/locales/en.json | 6 +- src/routes/applications/[id]/__layout.svelte | 4 +- .../configuration/_GithubRepositories.svelte | 4 +- src/routes/applications/[id]/index.svelte | 4 + .../applications/[id]/logs/index.svelte | 4 +- src/routes/databases/[id]/__layout.svelte | 73 ++++++- .../databases/[id]/logs/_Loading.svelte | 41 ++++ src/routes/databases/[id]/logs/index.json.ts | 66 +++++++ src/routes/databases/[id]/logs/index.svelte | 179 ++++++++++++++++++ src/routes/services/[id]/__layout.svelte | 32 ++++ src/routes/services/[id]/logs/_Loading.svelte | 41 ++++ src/routes/services/[id]/logs/index.json.ts | 66 +++++++ src/routes/services/[id]/logs/index.svelte | 179 ++++++++++++++++++ 17 files changed, 882 insertions(+), 53 deletions(-) create mode 100644 src/lib/components/svg/services/Fider.svelte create mode 100644 src/routes/databases/[id]/logs/_Loading.svelte create mode 100644 src/routes/databases/[id]/logs/index.json.ts create mode 100644 src/routes/databases/[id]/logs/index.svelte create mode 100644 src/routes/services/[id]/logs/_Loading.svelte create mode 100644 src/routes/services/[id]/logs/index.json.ts create mode 100644 src/routes/services/[id]/logs/index.svelte diff --git a/src/lib/components/svg/databases/Redis.svelte b/src/lib/components/svg/databases/Redis.svelte index 2571e601d..24a7dc797 100644 --- a/src/lib/components/svg/databases/Redis.svelte +++ b/src/lib/components/svg/databases/Redis.svelte @@ -4,9 +4,7 @@ + export let isAbsolute = false; + + + diff --git a/src/lib/database/common.ts b/src/lib/database/common.ts index 968208f55..314f77b08 100644 --- a/src/lib/database/common.ts +++ b/src/lib/database/common.ts @@ -11,11 +11,12 @@ import generator from 'generate-password'; import forge from 'node-forge'; import getPort, { portNumbers } from 'get-port'; -export function generatePassword(length = 24): string { +export function generatePassword(length = 24, symbols = false): string { return generator.generate({ length, numbers: true, - strict: true + strict: true, + symbols }); } diff --git a/src/lib/database/services.ts b/src/lib/database/services.ts index 9e3d1b467..df760cf76 100644 --- a/src/lib/database/services.ts +++ b/src/lib/database/services.ts @@ -15,7 +15,8 @@ const include: Prisma.ServiceInclude = { ghost: true, meiliSearch: true, umami: true, - hasura: true + hasura: true, + fider: true }; export async function listServicesWithIncludes() { return await prisma.service.findMany({ @@ -103,6 +104,12 @@ export async function getService({ id, teamId }: { id: string; teamId: string }) if (body.hasura?.graphQLAdminPassword) body.hasura.graphQLAdminPassword = decrypt(body.hasura.graphQLAdminPassword); + if (body.fider?.postgresqlPassword) + body.fider.postgresqlPassword = decrypt(body.fider.postgresqlPassword); + if (body.fider?.jwtSecret) body.fider.jwtSecret = decrypt(body.fider.jwtSecret); + if (body.fider?.emailSmtpPassword) + body.fider.emailSmtpPassword = decrypt(body.fider.emailSmtpPassword); + const settings = await prisma.setting.findFirst(); return { ...body, settings }; @@ -268,6 +275,25 @@ export async function configureServiceType({ } } }); + } else if (type === 'fider') { + const postgresqlUser = cuid(); + const postgresqlPassword = encrypt(generatePassword()); + const postgresqlDatabase = 'fider'; + const jwtSecret = encrypt(generatePassword(64, true)); + await prisma.service.update({ + where: { id }, + data: { + type, + fider: { + create: { + postgresqlDatabase, + postgresqlPassword, + postgresqlUser, + jwtSecret + } + } + } + }); } } @@ -326,52 +352,53 @@ export async function updateService({ return await prisma.service.update({ where: { id }, data: { fqdn, name } }); } -export async function updateLanguageToolService({ +export async function updateFiderService({ id, fqdn, - name + name, + emailNoreply, + emailMailgunApiKey, + emailMailgunDomain, + emailMailgunRegion, + emailSmtpHost, + emailSmtpPort, + emailSmtpUser, + emailSmtpPassword, + emailSmtpEnableStartTls }: { id: string; fqdn: string; name: string; + emailNoreply: string; + emailMailgunApiKey: string; + emailMailgunDomain: string; + emailMailgunRegion: string; + emailSmtpHost: string; + emailSmtpPort: number; + emailSmtpUser: string; + emailSmtpPassword: string; + emailSmtpEnableStartTls: boolean; }): Promise { - return await prisma.service.update({ where: { id }, data: { fqdn, name } }); -} - -export async function updateMeiliSearchService({ - id, - fqdn, - name -}: { - id: string; - fqdn: string; - name: string; -}): Promise { - return await prisma.service.update({ where: { id }, data: { fqdn, name } }); -} - -export async function updateVaultWardenService({ - id, - fqdn, - name -}: { - id: string; - fqdn: string; - name: string; -}): Promise { - return await prisma.service.update({ where: { id }, data: { fqdn, name } }); -} - -export async function updateVsCodeServer({ - id, - fqdn, - name -}: { - id: string; - fqdn: string; - name: string; -}): Promise { - return await prisma.service.update({ where: { id }, data: { fqdn, name } }); + return await prisma.service.update({ + where: { id }, + data: { + fqdn, + name, + fider: { + update: { + emailNoreply, + emailMailgunApiKey, + emailMailgunDomain, + emailMailgunRegion, + emailSmtpHost, + emailSmtpPort, + emailSmtpUser, + emailSmtpPassword, + emailSmtpEnableStartTls + } + } + } + }); } export async function updateWordpress({ @@ -423,6 +450,7 @@ export async function updateGhostService({ export async function removeService({ id }: { id: string }): Promise { await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } }); await prisma.meiliSearch.deleteMany({ where: { serviceId: id } }); + await prisma.fider.deleteMany({ where: { serviceId: id } }); await prisma.ghost.deleteMany({ where: { serviceId: id } }); await prisma.umami.deleteMany({ where: { serviceId: id } }); await prisma.hasura.deleteMany({ where: { serviceId: id } }); diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json index a5d6cbd50..22c7adf38 100644 --- a/src/lib/locales/en.json +++ b/src/lib/locales/en.json @@ -228,7 +228,8 @@ "permission_denied_start_database": "You do not have permission to start the database.", "delete_database": "Delete Database", "permission_denied_delete_database": "You do not have permission to delete a Database", - "no_databases_found": "No databases found" + "no_databases_found": "No databases found", + "logs": "Database Logs" }, "destination": { "delete_destination": "Delete Destination", @@ -293,7 +294,8 @@ "permission_denied_start_service": "You do not have permission to start the service.", "delete_service": "Delete Service", "permission_denied_delete_service": "You do not have permission to delete a service.", - "no_service": "No services found" + "no_service": "No services found", + "logs": "Service Logs" }, "setting": { "change_language": "Change Language", diff --git a/src/routes/applications/[id]/__layout.svelte b/src/routes/applications/[id]/__layout.svelte index bf634edde..cc8c19c19 100644 --- a/src/routes/applications/[id]/__layout.svelte +++ b/src/routes/applications/[id]/__layout.svelte @@ -394,7 +394,7 @@ >
+
+ changeSettings('isDNSCheckEnabled')} + /> +
!isFqdnSet && changeSettings('dualCerts')} />
From 3fd50ebb1241488563e277d22e95628ef7d81f37 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Sat, 30 Apr 2022 13:54:19 +0200 Subject: [PATCH 26/32] fix: checking low disk space --- package.json | 2 +- pnpm-lock.yaml | 14 +++---- src/lib/queues/cleanup.ts | 83 ++++++++++++++++++++++++++++----------- 3 files changed, 69 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 934d8e6d1..1cffb1afb 100644 --- a/package.json +++ b/package.json @@ -55,8 +55,8 @@ "svelte-check": "2.7.0", "svelte-preprocess": "4.10.6", "svelte-select": "4.4.7", - "tailwindcss": "3.0.24", "sveltekit-i18n": "2.1.2", + "tailwindcss": "3.0.24", "ts-node": "10.7.0", "tslib": "2.3.1", "typescript": "4.6.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c608add7..8af565eeb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,8 +48,8 @@ specifiers: svelte-kit-cookie-session: 2.1.3 svelte-preprocess: 4.10.6 svelte-select: 4.4.7 - tailwindcss: 3.0.24 sveltekit-i18n: 2.1.2 + tailwindcss: 3.0.24 tailwindcss-scrollbar: 0.1.0 ts-node: 10.7.0 tslib: 2.3.1 @@ -108,9 +108,9 @@ devDependencies: svelte-check: 2.7.0_postcss@8.4.12+svelte@3.47.0 svelte-preprocess: 4.10.6_41810887ae6c6d59323116f47e33fa38 svelte-select: 4.4.7 + sveltekit-i18n: 2.1.2_svelte@3.47.0 tailwindcss: 3.0.24_ts-node@10.7.0 ts-node: 10.7.0_de7c86b0cde507c63a0402da5b982bd3 - sveltekit-i18n: 2.1.2_svelte@3.46.4 tslib: 2.3.1 typescript: 4.6.3 @@ -424,7 +424,7 @@ packages: - supports-color dev: true - /@sveltekit-i18n/base/1.1.1_svelte@3.46.4: + /@sveltekit-i18n/base/1.1.1_svelte@3.47.0: resolution: { integrity: sha512-J/sMU0OwS3dCLOuilHMBqu8vZHuuXiNV9vFJx8Nb4/b5BlR/KCZ4bCXI8wZR02GHeCOYKZxWus07CM1scxa/jw== @@ -432,7 +432,7 @@ packages: peerDependencies: svelte: ^3.x dependencies: - svelte: 3.46.4 + svelte: 3.47.0 optionalDependencies: '@sveltekit-i18n/parser-default': 1.0.3 dev: true @@ -4977,7 +4977,7 @@ packages: engines: { node: '>= 8' } dev: true - /sveltekit-i18n/2.1.2_svelte@3.46.4: + /sveltekit-i18n/2.1.2_svelte@3.47.0: resolution: { integrity: sha512-s5YxcbNd2EWNZaZR1A4Drt8s53E4fpUkN4XIWd3VRpw1pihZVWssqmBW1qkjQ6AB0kiu1Qwule+vt1HkbQOjrg== @@ -4985,9 +4985,9 @@ packages: peerDependencies: svelte: ^3.x dependencies: - '@sveltekit-i18n/base': 1.1.1_svelte@3.46.4 + '@sveltekit-i18n/base': 1.1.1_svelte@3.47.0 '@sveltekit-i18n/parser-default': 1.0.3 - svelte: 3.46.4 + svelte: 3.47.0 dev: true /table/6.7.2: diff --git a/src/lib/queues/cleanup.ts b/src/lib/queues/cleanup.ts index d5dc49381..e5a65e0a5 100644 --- a/src/lib/queues/cleanup.ts +++ b/src/lib/queues/cleanup.ts @@ -4,34 +4,73 @@ export default async function (): Promise { const destinationDockers = await prisma.destinationDocker.findMany(); const engines = [...new Set(destinationDockers.map(({ engine }) => engine))]; for (const engine of engines) { + let lowDiskSpace = false; const host = getEngine(engine); - // Cleanup old coolify images try { - let { stdout: images } = await asyncExecShell( - `DOCKER_HOST=${host} docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs ` + const { stdout } = await asyncExecShell( + `DOCKER_HOST=${host} docker exec coolify sh -c 'df -kPT /'` ); - images = images.trim(); - if (images) { - await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`); + let lines = stdout.trim().split('\n'); + let header = lines[0]; + let regex = + /^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g; + const boundaries = []; + let match; + + while ((match = regex.exec(header))) { + boundaries.push(match[0].length); + } + + boundaries[boundaries.length - 1] = -1; + const data = lines.slice(1).map((line) => { + const cl = boundaries.map((boundary) => { + const column = boundary > 0 ? line.slice(0, boundary) : line; + line = line.slice(boundary); + return column.trim(); + }); + return { + capacity: Number.parseInt(cl[5], 10) / 100 + }; + }); + if (data.length > 0) { + const { capacity } = data[0]; + if (capacity > 0.8) { + lowDiskSpace = true; + } } } catch (error) { - //console.log(error); + console.log(error); } - try { - await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`); - } catch (error) { - //console.log(error); - } - try { - await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`); - } catch (error) { - //console.log(error); - } - // Cleanup old images older than a day - try { - await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`); - } catch (error) { - //console.log(error); + console.log(`Is LowDiskSpace detected? ${lowDiskSpace}`); + if (lowDiskSpace) { + // Cleanup old coolify images + try { + let { stdout: images } = await asyncExecShell( + `DOCKER_HOST=${host} docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs ` + ); + images = images.trim(); + if (images) { + await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`); + } + } catch (error) { + //console.log(error); + } + try { + await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`); + } catch (error) { + //console.log(error); + } + try { + await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`); + } catch (error) { + //console.log(error); + } + // Cleanup old images older than a day + try { + await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`); + } catch (error) { + //console.log(error); + } } } } From 6fdbc572feeae5235aee54088d2c5468cf1290b9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 2 May 2022 09:13:56 +0200 Subject: [PATCH 27/32] update locale --- src/lib/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json index d89db86ef..8b29987e9 100644 --- a/src/lib/locales/en.json +++ b/src/lib/locales/en.json @@ -311,7 +311,7 @@ "auto_update_enabled_explainer": "Enable automatic updates for Coolify. It will be done automatically behind the scenes, if there is no build process running.", "generate_www_non_www_ssl": "It will generate certificates for both www and non-www.
You need to have both DNS entries set in advance.

Service needs to be restarted.", "is_dns_check_enabled": "DNS check enabled?", - "is_dns_check_enabled_explainer": "Enable DNS check during SSL certificate generation.
It will check if the DNS entries are set correctly, before trying to get a new cert from Let's Encrypt." + "is_dns_check_enabled_explainer": "You can disable DNS check before creating SSL certificates.

Useful when Coolify is behind a reverse proxy or tunnel." }, "team": { "pending_invitations": "Pending invitations", From 3e9cf7285b672856d6a92b56c06b055deb82fc65 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 2 May 2022 09:21:01 +0200 Subject: [PATCH 28/32] fix locale --- src/lib/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json index 8b29987e9..f0107ba72 100644 --- a/src/lib/locales/en.json +++ b/src/lib/locales/en.json @@ -311,7 +311,7 @@ "auto_update_enabled_explainer": "Enable automatic updates for Coolify. It will be done automatically behind the scenes, if there is no build process running.", "generate_www_non_www_ssl": "It will generate certificates for both www and non-www.
You need to have both DNS entries set in advance.

Service needs to be restarted.", "is_dns_check_enabled": "DNS check enabled?", - "is_dns_check_enabled_explainer": "You can disable DNS check before creating SSL certificates.

Useful when Coolify is behind a reverse proxy or tunnel." + "is_dns_check_enabled_explainer": "You can disable DNS check before creating SSL certificates.

Turning it off is useful when Coolify is behind a reverse proxy or tunnel." }, "team": { "pending_invitations": "Pending invitations", From 1905db16e838181be6f704e36a465b2bd62db8c6 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 2 May 2022 09:43:38 +0200 Subject: [PATCH 29/32] beta features --- src/lib/store.ts | 7 ++++++- src/routes/__layout.svelte | 5 +++-- src/routes/settings/index.svelte | 5 ++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/lib/store.ts b/src/lib/store.ts index 1473d93b2..094dd756a 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -1,4 +1,4 @@ -import { writable, type Writable } from 'svelte/store'; +import { writable, type Writable, type Readable, readable } from 'svelte/store'; export const gitTokens: Writable<{ githubToken: string | null; gitlabToken: string | null }> = writable({ @@ -6,3 +6,8 @@ export const gitTokens: Writable<{ githubToken: string | null; gitlabToken: stri gitlabToken: null }); export const disabledButton: Writable = writable(false); + +export const features: Readable<{ latestVersion: string; beta: boolean }> = readable({ + beta: window.localStorage.getItem('beta') === 'true', + latestVersion: window.localStorage.getItem('latestVersion') +}); diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index c5c5daddf..5952073e0 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -41,7 +41,8 @@ import { errorNotification } from '$lib/form'; import { asyncSleep } from '$lib/components/common'; import { del, get, post } from '$lib/api'; - import { browser, dev } from '$app/env'; + import { dev } from '$app/env'; + import { features } from '$lib/store'; let isUpdateAvailable = false; let updateStatus = { @@ -52,7 +53,7 @@ let latestVersion = 'latest'; onMount(async () => { if ($session.userId) { - const overrideVersion = browser && window.localStorage.getItem('latestVersion'); + const overrideVersion = $features.latestVersion; try { await get(`/login.json`); } catch ({ error }) { diff --git a/src/routes/settings/index.svelte b/src/routes/settings/index.svelte index 8bfefbed3..6a9b6ba5a 100644 --- a/src/routes/settings/index.svelte +++ b/src/routes/settings/index.svelte @@ -28,8 +28,6 @@ import { session } from '$app/stores'; export let settings; - import Cookies from 'js-cookie'; - import langs from '$lib/lang.json'; import Setting from '$lib/components/Setting.svelte'; import Explainer from '$lib/components/Explainer.svelte'; import { errorNotification } from '$lib/form'; @@ -39,6 +37,7 @@ import { getDomain } from '$lib/components/common'; import { toast } from '@zerodevx/svelte-toast'; import { t } from '$lib/translations'; + import { features } from '$lib/store'; let isRegistrationEnabled = settings.isRegistrationEnabled; let dualCerts = settings.dualCerts; @@ -211,7 +210,7 @@ on:click={() => changeSettings('isRegistrationEnabled')} />
- {#if browser && (window.location.hostname === 'staging.coolify.io' || window.location.hostname === 'localhost')} + {#if browser && $features.beta}
Date: Mon, 2 May 2022 13:00:13 +0200 Subject: [PATCH 30/32] fix: build image --- src/lib/buildPacks/common.ts | 21 ++++++++++++++------- src/lib/store.ts | 5 +++-- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/lib/buildPacks/common.ts b/src/lib/buildPacks/common.ts index d9f3416b9..6ac518136 100644 --- a/src/lib/buildPacks/common.ts +++ b/src/lib/buildPacks/common.ts @@ -6,7 +6,17 @@ import { promises as fs } from 'fs'; import { staticDeployments } from '$lib/components/common'; const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy']; -const nodeBased = ['react', 'vuejs', 'svelte', 'gatsby', 'php', 'astro', 'eleventy', 'node']; +const nodeBased = [ + 'react', + 'vuejs', + 'svelte', + 'gatsby', + 'php', + 'astro', + 'eleventy', + 'node', + 'nestjs' +]; export function makeLabelForStandaloneApplication({ applicationId, @@ -393,12 +403,7 @@ export function setDefaultBaseImage(buildPack) { label: 'webdevops/php-nginx:7.1-alpine' } ]; - const laravelVersions = [ - { - value: 'webdevops/php-apache:8.0-alpine', - label: 'webdevops/php-apache:8.0-alpine' - } - ]; + let payload = { baseImage: null, baseBuildImage: null, @@ -408,6 +413,8 @@ export function setDefaultBaseImage(buildPack) { if (nodeBased.includes(buildPack)) { payload.baseImage = 'node:lts'; payload.baseImages = nodeVersions; + payload.baseBuildImage = 'node:lts'; + payload.baseBuildImages = nodeVersions; } if (staticApps.includes(buildPack)) { payload.baseImage = 'webdevops/nginx:alpine'; diff --git a/src/lib/store.ts b/src/lib/store.ts index 094dd756a..684922bfb 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -1,3 +1,4 @@ +import { browser } from '$app/env'; import { writable, type Writable, type Readable, readable } from 'svelte/store'; export const gitTokens: Writable<{ githubToken: string | null; gitlabToken: string | null }> = @@ -8,6 +9,6 @@ export const gitTokens: Writable<{ githubToken: string | null; gitlabToken: stri export const disabledButton: Writable = writable(false); export const features: Readable<{ latestVersion: string; beta: boolean }> = readable({ - beta: window.localStorage.getItem('beta') === 'true', - latestVersion: window.localStorage.getItem('latestVersion') + beta: browser && window.localStorage.getItem('beta') === 'true', + latestVersion: browser && window.localStorage.getItem('latestVersion') }); From 00cab67e7363da40c1488e562084f0b5c6ed06fd Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 2 May 2022 14:15:50 +0200 Subject: [PATCH 31/32] feat: Cancel builds! --- src/lib/buildPacks/deno.ts | 14 +++- src/lib/buildPacks/docker.ts | 2 + src/lib/buildPacks/gatsby.ts | 4 +- src/lib/buildPacks/laravel.ts | 4 +- src/lib/buildPacks/nestjs.ts | 4 +- src/lib/buildPacks/nextjs.ts | 3 +- src/lib/buildPacks/node.ts | 5 +- src/lib/buildPacks/nuxtjs.ts | 5 +- src/lib/buildPacks/php.ts | 4 +- src/lib/buildPacks/python.ts | 5 +- src/lib/buildPacks/react.ts | 4 +- src/lib/buildPacks/rust.ts | 4 +- src/lib/buildPacks/static.ts | 5 +- src/lib/buildPacks/svelte.ts | 4 +- src/lib/buildPacks/vuejs.ts | 4 +- src/lib/docker.ts | 6 +- src/lib/locales/en.json | 6 +- src/routes/applications/[id]/cancel.json.ts | 71 +++++++++++++++++++ src/routes/applications/[id]/deploy.json.ts | 24 ++++--- src/routes/applications/[id]/index.svelte | 3 + .../[id]/logs/build/_BuildLog.svelte | 27 ++++++- src/routes/webhooks/github/events.ts | 32 +++++---- src/routes/webhooks/gitlab/events.ts | 32 +++++---- 23 files changed, 207 insertions(+), 65 deletions(-) create mode 100644 src/routes/applications/[id]/cancel.json.ts diff --git a/src/lib/buildPacks/deno.ts b/src/lib/buildPacks/deno.ts index 8b7e30b5a..b593596f7 100644 --- a/src/lib/buildPacks/deno.ts +++ b/src/lib/buildPacks/deno.ts @@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { workdir, port, baseDirectory, secrets, pullmergeRequestId, denoMainFile, denoOptions } = - data; + const { + workdir, + port, + baseDirectory, + secrets, + pullmergeRequestId, + denoMainFile, + denoOptions, + buildId + } = data; const Dockerfile: Array = []; let depsFound = false; @@ -14,7 +22,7 @@ const createDockerfile = async (data, image): Promise => { Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { diff --git a/src/lib/buildPacks/docker.ts b/src/lib/buildPacks/docker.ts index 1ddf81a60..3b032fc4b 100644 --- a/src/lib/buildPacks/docker.ts +++ b/src/lib/buildPacks/docker.ts @@ -24,6 +24,7 @@ export default async function ({ .toString() .trim() .split('\n'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { @@ -42,6 +43,7 @@ export default async function ({ } }); } + await fs.writeFile(`${dockerFileOut}${dockerFileLocation}`, Dockerfile.join('\n')); await buildImage({ applicationId, tag, workdir, docker, buildId, debug, dockerFileLocation }); } catch (error) { diff --git a/src/lib/buildPacks/gatsby.ts b/src/lib/buildPacks/gatsby.ts index 0cba2a48d..cdf95f1dd 100644 --- a/src/lib/buildPacks/gatsby.ts +++ b/src/lib/buildPacks/gatsby.ts @@ -2,12 +2,12 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, imageforBuild): Promise => { - const { applicationId, tag, workdir, publishDirectory, baseImage } = data; + const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${imageforBuild}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); if (baseImage.includes('nginx')) { Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); diff --git a/src/lib/buildPacks/laravel.ts b/src/lib/buildPacks/laravel.ts index bf5fcc6a2..a83363dc0 100644 --- a/src/lib/buildPacks/laravel.ts +++ b/src/lib/buildPacks/laravel.ts @@ -2,11 +2,11 @@ import { buildCacheImageForLaravel, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { workdir, applicationId, tag, baseImage } = data; + const { workdir, applicationId, tag, buildId } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push('WORKDIR /app'); Dockerfile.push(`ENV WEB_DOCUMENT_ROOT /app/public`); Dockerfile.push(`COPY --chown=application:application composer.* ./`); diff --git a/src/lib/buildPacks/nestjs.ts b/src/lib/buildPacks/nestjs.ts index f529d7645..b0bb6ba89 100644 --- a/src/lib/buildPacks/nestjs.ts +++ b/src/lib/buildPacks/nestjs.ts @@ -2,13 +2,13 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { applicationId, tag, port, startCommand, workdir, baseDirectory } = data; + const { buildId, applicationId, tag, port, startCommand, workdir, baseDirectory } = data; const Dockerfile: Array = []; const isPnpm = startCommand.includes('pnpm'); Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); 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'); diff --git a/src/lib/buildPacks/nextjs.ts b/src/lib/buildPacks/nextjs.ts index 59c50d7f1..77f5bc5f8 100644 --- a/src/lib/buildPacks/nextjs.ts +++ b/src/lib/buildPacks/nextjs.ts @@ -4,6 +4,7 @@ import { checkPnpm } from './common'; const createDockerfile = async (data, image): Promise => { const { + buildId, workdir, port, installCommand, @@ -17,7 +18,7 @@ const createDockerfile = async (data, image): Promise => { const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { diff --git a/src/lib/buildPacks/node.ts b/src/lib/buildPacks/node.ts index 1231892b2..9d8e643ef 100644 --- a/src/lib/buildPacks/node.ts +++ b/src/lib/buildPacks/node.ts @@ -11,14 +11,15 @@ const createDockerfile = async (data, image): Promise => { startCommand, baseDirectory, secrets, - pullmergeRequestId + pullmergeRequestId, + buildId } = data; const Dockerfile: Array = []; const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { diff --git a/src/lib/buildPacks/nuxtjs.ts b/src/lib/buildPacks/nuxtjs.ts index 9120b7d5c..9cd7e9674 100644 --- a/src/lib/buildPacks/nuxtjs.ts +++ b/src/lib/buildPacks/nuxtjs.ts @@ -11,13 +11,14 @@ const createDockerfile = async (data, image): Promise => { startCommand, baseDirectory, secrets, - pullmergeRequestId + pullmergeRequestId, + buildId } = data; const Dockerfile: Array = []; const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { diff --git a/src/lib/buildPacks/php.ts b/src/lib/buildPacks/php.ts index 4bdfaf923..b3c9651ce 100644 --- a/src/lib/buildPacks/php.ts +++ b/src/lib/buildPacks/php.ts @@ -2,7 +2,7 @@ import { buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image, htaccessFound): Promise => { - const { workdir, baseDirectory } = data; + const { workdir, baseDirectory, buildId } = data; const Dockerfile: Array = []; let composerFound = false; try { @@ -11,7 +11,7 @@ const createDockerfile = async (data, image, htaccessFound): Promise => { } catch (error) {} Dockerfile.push(`FROM ${image}`); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push('WORKDIR /app'); Dockerfile.push(`COPY .${baseDirectory || ''} /app`); if (htaccessFound) { diff --git a/src/lib/buildPacks/python.ts b/src/lib/buildPacks/python.ts index f13965cf0..fc5e8738b 100644 --- a/src/lib/buildPacks/python.ts +++ b/src/lib/buildPacks/python.ts @@ -10,12 +10,13 @@ const createDockerfile = async (data, image): Promise => { pullmergeRequestId, pythonWSGI, pythonModule, - pythonVariable + pythonVariable, + buildId } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { diff --git a/src/lib/buildPacks/react.ts b/src/lib/buildPacks/react.ts index 4aca45621..3b55bfa23 100644 --- a/src/lib/buildPacks/react.ts +++ b/src/lib/buildPacks/react.ts @@ -2,11 +2,11 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { applicationId, tag, workdir, publishDirectory, baseImage } = data; + const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push('WORKDIR /app'); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); if (baseImage.includes('nginx')) { diff --git a/src/lib/buildPacks/rust.ts b/src/lib/buildPacks/rust.ts index 5db5ad9b5..72f0c6273 100644 --- a/src/lib/buildPacks/rust.ts +++ b/src/lib/buildPacks/rust.ts @@ -4,11 +4,11 @@ import { promises as fs } from 'fs'; import TOML from '@iarna/toml'; const createDockerfile = async (data, image, name): Promise => { - const { workdir, port, applicationId, tag } = data; + const { workdir, port, applicationId, tag, buildId } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target target`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/local/cargo /usr/local/cargo`); Dockerfile.push(`COPY . .`); diff --git a/src/lib/buildPacks/static.ts b/src/lib/buildPacks/static.ts index 20afefbba..8f3c2c7d4 100644 --- a/src/lib/buildPacks/static.ts +++ b/src/lib/buildPacks/static.ts @@ -11,13 +11,14 @@ const createDockerfile = async (data, image): Promise => { publishDirectory, secrets, pullmergeRequestId, - baseImage + baseImage, + buildId } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { diff --git a/src/lib/buildPacks/svelte.ts b/src/lib/buildPacks/svelte.ts index b63560a5d..5604e7ed6 100644 --- a/src/lib/buildPacks/svelte.ts +++ b/src/lib/buildPacks/svelte.ts @@ -2,12 +2,12 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { applicationId, tag, workdir, publishDirectory, baseImage } = data; + const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); if (baseImage.includes('nginx')) { Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); diff --git a/src/lib/buildPacks/vuejs.ts b/src/lib/buildPacks/vuejs.ts index b63560a5d..5604e7ed6 100644 --- a/src/lib/buildPacks/vuejs.ts +++ b/src/lib/buildPacks/vuejs.ts @@ -2,12 +2,12 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { applicationId, tag, workdir, publishDirectory, baseImage } = data; + const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); if (baseImage.includes('nginx')) { Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); diff --git a/src/lib/docker.ts b/src/lib/docker.ts index 478ae54fb..0b60a4ed9 100644 --- a/src/lib/docker.ts +++ b/src/lib/docker.ts @@ -8,7 +8,7 @@ export async function buildCacheImageForLaravel(data, imageForBuild) { const Dockerfile: Array = []; Dockerfile.push(`FROM ${imageForBuild}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { @@ -49,7 +49,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) { const Dockerfile: Array = []; Dockerfile.push(`FROM ${imageForBuild}`); Dockerfile.push('WORKDIR /app'); - Dockerfile.push(`LABEL coolify.image=true`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { @@ -94,11 +94,13 @@ export async function buildCacheImageWithCargo(data, imageForBuild) { } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push('WORKDIR /app'); Dockerfile.push('RUN cargo install cargo-chef'); Dockerfile.push('COPY . .'); Dockerfile.push('RUN cargo chef prepare --recipe-path recipe.json'); Dockerfile.push(`FROM ${imageForBuild}`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push('WORKDIR /app'); Dockerfile.push('RUN cargo install cargo-chef'); Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`); diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json index f0107ba72..a7adfcef0 100644 --- a/src/lib/locales/en.json +++ b/src/lib/locales/en.json @@ -184,8 +184,10 @@ "git_source": "Git Source", "git_repository": "Git Repository", "build_pack": "Build Pack", - "base_image": "Base Image", - "base_build_image": "Base Build Image", + "base_image": "Deplyoment Image", + "base_image_explainer": "Image that will be used for the deployment.", + "base_build_image": "Build Image", + "base_build_image_explainer": "Image that will be used during the build process.", "destination": "Destination", "application": "Application", "url_fqdn": "URL (FQDN)", diff --git a/src/routes/applications/[id]/cancel.json.ts b/src/routes/applications/[id]/cancel.json.ts new file mode 100644 index 000000000..ce1082a33 --- /dev/null +++ b/src/routes/applications/[id]/cancel.json.ts @@ -0,0 +1,71 @@ +import { asyncExecShell, getEngine, removeDestinationDocker, saveBuildLog } from '$lib/common'; +import { buildQueue } from '$lib/queues'; +import type { RequestHandler } from '@sveltejs/kit'; +import * as db from '$lib/database'; + +export const post: RequestHandler = async (event) => { + const { buildId, applicationId } = await event.request.json(); + if (!buildId) { + return { + status: 500, + body: { + message: 'Build ID not found.' + } + }; + } + try { + let count = 0; + await new Promise(async (resolve, reject) => { + const job = await buildQueue.getJob(buildId); + const { + destinationDocker: { engine } + } = job.data; + const host = getEngine(engine); + let interval = setInterval(async () => { + console.log(`Checking build ${buildId}, try ${count}`); + if (count > 100) { + clearInterval(interval); + reject(new Error('Could not cancel build.')); + } + try { + const { stdout: buildContainers } = await asyncExecShell( + `DOCKER_HOST=${host} docker container ls --filter "label=coolify.buildId=${buildId}" --format '{{json .}}'` + ); + if (buildContainers) { + const containersArray = buildContainers.trim().split('\n'); + for (const container of containersArray) { + const containerObj = JSON.parse(container); + const id = containerObj.ID; + if (!containerObj.Names.startsWith(`${applicationId}`)) { + await removeDestinationDocker({ id, engine }); + clearInterval(interval); + await saveBuildLog({ + line: 'Canceled by user!', + buildId: job.data.build_id, + applicationId: job.data.id + }); + } + } + } + count++; + } catch (error) {} + }, 100); + + resolve('Canceled'); + }); + + return { + status: 200, + body: { + message: 'Build canceled.' + } + }; + } catch (error) { + return { + status: 500, + body: { + message: error.message + } + }; + } +}; diff --git a/src/routes/applications/[id]/deploy.json.ts b/src/routes/applications/[id]/deploy.json.ts index 3f645deb2..92311ae1f 100644 --- a/src/routes/applications/[id]/deploy.json.ts +++ b/src/routes/applications/[id]/deploy.json.ts @@ -45,15 +45,23 @@ export const post: RequestHandler = async (event) => { } }); if (pullmergeRequestId) { - await buildQueue.add(buildId, { - build_id: buildId, - type: 'manual', - ...applicationFound, - sourceBranch: branch, - pullmergeRequestId - }); + await buildQueue.add( + buildId, + { + build_id: buildId, + type: 'manual', + ...applicationFound, + sourceBranch: branch, + pullmergeRequestId + }, + { jobId: buildId } + ); } else { - await buildQueue.add(buildId, { build_id: buildId, type: 'manual', ...applicationFound }); + await buildQueue.add( + buildId, + { build_id: buildId, type: 'manual', ...applicationFound }, + { jobId: buildId } + ); } return { status: 200, diff --git a/src/routes/applications/[id]/index.svelte b/src/routes/applications/[id]/index.svelte index e9c558706..719b95b40 100644 --- a/src/routes/applications/[id]/index.svelte +++ b/src/routes/applications/[id]/index.svelte @@ -339,6 +339,7 @@ isClearable={false} />
+
{#if application.buildCommand || application.buildPack === 'rust' || application.buildPack === 'laravel'}
@@ -360,6 +361,8 @@
{#if application.buildPack === 'laravel'} + {:else} + {/if}
{/if} diff --git a/src/routes/applications/[id]/logs/build/_BuildLog.svelte b/src/routes/applications/[id]/logs/build/_BuildLog.svelte index 9a8dea4cc..4a761857d 100644 --- a/src/routes/applications/[id]/logs/build/_BuildLog.svelte +++ b/src/routes/applications/[id]/logs/build/_BuildLog.svelte @@ -8,7 +8,7 @@ import Loading from '$lib/components/Loading.svelte'; import LoadingLogs from '../_Loading.svelte'; - import { get } from '$lib/api'; + import { get, post } from '$lib/api'; import { errorNotification } from '$lib/form'; import { t } from '$lib/translations'; @@ -67,6 +67,12 @@ return errorNotification(error); } } + async function cancelBuild() { + return await post(`/applications/${id}/cancel.json`, { + buildId, + applicationId: id + }); + } onDestroy(() => { clearInterval(streamInterval); clearInterval(followingInterval); @@ -111,7 +117,26 @@ + {#if currentStatus === 'running'} + + {/if}
+
{ type: 'webhook_commit' } }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_commit', - ...applicationFound - }); + await buildQueue.add( + buildId, + { + build_id: buildId, + type: 'webhook_commit', + ...applicationFound + }, + { jobId: buildId } + ); return { status: 200, body: { @@ -160,13 +164,17 @@ export const post: RequestHandler = async (event) => { type: 'webhook_pr' } }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_pr', - ...applicationFound, - sourceBranch, - pullmergeRequestId - }); + await buildQueue.add( + buildId, + { + build_id: buildId, + type: 'webhook_pr', + ...applicationFound, + sourceBranch, + pullmergeRequestId + }, + { jobId: buildId } + ); return { status: 200, body: { diff --git a/src/routes/webhooks/gitlab/events.ts b/src/routes/webhooks/gitlab/events.ts index f8bb54383..d84646088 100644 --- a/src/routes/webhooks/gitlab/events.ts +++ b/src/routes/webhooks/gitlab/events.ts @@ -73,11 +73,15 @@ export const post: RequestHandler = async (event) => { type: 'webhook_commit' } }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_commit', - ...applicationFound - }); + await buildQueue.add( + buildId, + { + build_id: buildId, + type: 'webhook_commit', + ...applicationFound + }, + { jobId: buildId } + ); return { status: 200, body: { @@ -156,13 +160,17 @@ export const post: RequestHandler = async (event) => { type: 'webhook_mr' } }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_mr', - ...applicationFound, - sourceBranch, - pullmergeRequestId - }); + await buildQueue.add( + buildId, + { + build_id: buildId, + type: 'webhook_mr', + ...applicationFound, + sourceBranch, + pullmergeRequestId + }, + { jobId: buildId } + ); return { status: 200, body: { From 0faa1540f448cd7610245877567979c4d221d856 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 2 May 2022 14:42:19 +0200 Subject: [PATCH 32/32] ui fixes --- src/routes/applications/[id]/cancel.json.ts | 12 +++++--- .../[id]/logs/build/_BuildLog.svelte | 29 ++++++++++++++----- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/routes/applications/[id]/cancel.json.ts b/src/routes/applications/[id]/cancel.json.ts index ce1082a33..11c83ae9c 100644 --- a/src/routes/applications/[id]/cancel.json.ts +++ b/src/routes/applications/[id]/cancel.json.ts @@ -15,15 +15,19 @@ export const post: RequestHandler = async (event) => { } try { let count = 0; - await new Promise(async (resolve, reject) => { + await new Promise(async (resolve, reject) => { const job = await buildQueue.getJob(buildId); const { destinationDocker: { engine } } = job.data; const host = getEngine(engine); let interval = setInterval(async () => { - console.log(`Checking build ${buildId}, try ${count}`); - if (count > 100) { + const { status } = await db.prisma.build.findUnique({ where: { id: buildId } }); + if (status === 'failed') { + clearInterval(interval); + return resolve(); + } + if (count > 1200) { clearInterval(interval); reject(new Error('Could not cancel build.')); } @@ -51,7 +55,7 @@ export const post: RequestHandler = async (event) => { } catch (error) {} }, 100); - resolve('Canceled'); + resolve(); }); return { diff --git a/src/routes/applications/[id]/logs/build/_BuildLog.svelte b/src/routes/applications/[id]/logs/build/_BuildLog.svelte index 4a761857d..378e58c2c 100644 --- a/src/routes/applications/[id]/logs/build/_BuildLog.svelte +++ b/src/routes/applications/[id]/logs/build/_BuildLog.svelte @@ -20,6 +20,8 @@ let followingInterval; let logsEl; + let cancelInprogress = false; + const { id } = $page.params; const cleanAnsiCodes = (str: string) => str.replace(/\x1B\[(\d+)m/g, ''); @@ -68,10 +70,17 @@ } } async function cancelBuild() { - return await post(`/applications/${id}/cancel.json`, { - buildId, - applicationId: id - }); + if (cancelInprogress) return; + try { + cancelInprogress = true; + await post(`/applications/${id}/cancel.json`, { + buildId, + applicationId: id + }); + } catch (error) { + console.log(error); + return errorNotification(error); + } } onDestroy(() => { clearInterval(streamInterval); @@ -96,7 +105,7 @@
{#if currentStatus === 'running'} - {/if}