Merge github.com:coollabsio/coolify into exposePort

This commit is contained in:
Aaron Styles
2022-05-03 16:14:58 +10:00
83 changed files with 2833 additions and 400 deletions

View File

@@ -5,6 +5,19 @@ 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',
'nestjs'
];
export function makeLabelForStandaloneApplication({
applicationId,
fqdn,
@@ -104,11 +117,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}`;
@@ -137,7 +151,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 +166,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;
@@ -174,7 +194,7 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
@@ -199,11 +219,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 +233,215 @@ 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;
payload.baseBuildImage = 'node:lts';
payload.baseBuildImages = 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;
}
if (buildPack === 'laravel') {
payload.baseImage = 'webdevops/php-apache:8.0-alpine';
payload.baseBuildImage = 'node:18';
payload.baseBuildImages = nodeVersions;
}
return payload;
}

View File

@@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => {
const { workdir, port, baseDirectory, secrets, pullmergeRequestId, denoMainFile, denoOptions } =
data;
const {
workdir,
port,
baseDirectory,
secrets,
pullmergeRequestId,
denoMainFile,
denoOptions,
buildId
} = data;
const Dockerfile: Array<string> = [];
let depsFound = false;
@@ -14,7 +22,7 @@ const createDockerfile = async (data, image): Promise<void> => {
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) {
@@ -45,8 +53,8 @@ const createDockerfile = async (data, image): Promise<void> => {
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;

View File

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

View File

@@ -2,25 +2,25 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
const createDockerfile = async (data, imageforBuild): Promise<void> => {
const { applicationId, tag, workdir, publishDirectory } = data;
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data;
const Dockerfile: Array<string> = [];
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} ./`);
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;

View File

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

View File

@@ -0,0 +1,40 @@
import { buildCacheImageForLaravel, buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => {
const { workdir, applicationId, tag, buildId } = data;
const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${image}`);
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.* ./`);
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 --chown=application:application --from=${applicationId}:${tag}-cache /app/public/js/ /app/public/js/`
);
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'));
};
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;
}
}

View File

@@ -2,13 +2,13 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => {
const { applicationId, tag, port, startCommand, workdir, baseDirectory } = data;
const { buildId, applicationId, tag, port, startCommand, workdir, baseDirectory } = data;
const Dockerfile: Array<string> = [];
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');
@@ -22,11 +22,9 @@ const createDockerfile = async (data, image): Promise<void> => {
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;

View File

@@ -4,6 +4,7 @@ import { checkPnpm } from './common';
const createDockerfile = async (data, image): Promise<void> => {
const {
buildId,
workdir,
port,
installCommand,
@@ -17,7 +18,7 @@ const createDockerfile = async (data, image): Promise<void> => {
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) {
@@ -50,8 +51,8 @@ const createDockerfile = async (data, image): Promise<void> => {
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;

View File

@@ -11,14 +11,15 @@ const createDockerfile = async (data, image): Promise<void> => {
startCommand,
baseDirectory,
secrets,
pullmergeRequestId
pullmergeRequestId,
buildId
} = data;
const Dockerfile: Array<string> = [];
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) {
@@ -50,8 +51,8 @@ const createDockerfile = async (data, image): Promise<void> => {
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;

View File

@@ -11,13 +11,14 @@ const createDockerfile = async (data, image): Promise<void> => {
startCommand,
baseDirectory,
secrets,
pullmergeRequestId
pullmergeRequestId,
buildId
} = data;
const Dockerfile: Array<string> = [];
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) {
@@ -49,8 +50,8 @@ const createDockerfile = async (data, image): Promise<void> => {
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;

View File

@@ -2,7 +2,7 @@ import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
const { workdir, baseDirectory } = data;
const { workdir, baseDirectory, buildId } = data;
const Dockerfile: Array<string> = [];
let composerFound = false;
try {
@@ -11,7 +11,7 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
} 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) {
@@ -27,7 +27,7 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
};
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;

View File

@@ -10,12 +10,13 @@ const createDockerfile = async (data, image): Promise<void> => {
pullmergeRequestId,
pythonWSGI,
pythonModule,
pythonVariable
pythonVariable,
buildId
} = data;
const Dockerfile: Array<string> = [];
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) {
@@ -62,8 +63,8 @@ const createDockerfile = async (data, image): Promise<void> => {
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;

View File

@@ -2,24 +2,25 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => {
const { applicationId, tag, workdir, publishDirectory } = data;
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data;
const Dockerfile: Array<string> = [];
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} ./`);
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;

View File

@@ -4,11 +4,11 @@ import { promises as fs } from 'fs';
import TOML from '@iarna/toml';
const createDockerfile = async (data, image, name): Promise<void> => {
const { workdir, port, applicationId, tag } = data;
const { workdir, port, applicationId, tag, buildId } = data;
const Dockerfile: Array<string> = [];
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 . .`);
@@ -27,14 +27,12 @@ const createDockerfile = async (data, image, name): Promise<void> => {
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;

View File

@@ -10,13 +10,15 @@ const createDockerfile = async (data, image): Promise<void> => {
baseDirectory,
publishDirectory,
secrets,
pullmergeRequestId
pullmergeRequestId,
baseImage,
buildId
} = data;
const Dockerfile: Array<string> = [];
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) {
@@ -37,17 +39,18 @@ const createDockerfile = async (data, image): Promise<void> => {
} 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;

View File

@@ -2,25 +2,25 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => {
const { applicationId, tag, workdir, publishDirectory } = data;
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data;
const Dockerfile: Array<string> = [];
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} ./`);
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;

View File

@@ -2,24 +2,25 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => {
const { applicationId, tag, workdir, publishDirectory } = data;
const { applicationId, tag, workdir, publishDirectory, baseImage, buildId } = data;
const Dockerfile: Array<string> = [];
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} ./`);
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;

View File

@@ -1,6 +1,7 @@
<script>
export let service;
import Ghost from './svg/services/Ghost.svelte';
import Hasura from './svg/services/Hasura.svelte';
import LanguageTool from './svg/services/LanguageTool.svelte';
import MinIo from './svg/services/MinIO.svelte';
import N8n from './svg/services/N8n.svelte';
@@ -11,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';
</script>
{#if service.type === 'plausibleanalytics'}
@@ -57,4 +59,12 @@
<a href="https://umami.is" target="_blank">
<Umami />
</a>
{:else if service.type === 'hasura'}
<a href="https://hasura.io" target="_blank">
<Hasura />
</a>
{:else if service.type === 'fider'}
<a href="https://fider.io" target="_blank">
<Fider />
</a>
{/if}

View File

@@ -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://', '');
@@ -191,6 +191,28 @@ export const supportedServiceTypesAndVersions = [
ports: {
main: 3000
}
},
{
name: 'hasura',
fancyName: 'Hasura',
baseImage: 'hasura/graphql-engine',
images: ['postgres:12-alpine'],
versions: ['latest', 'v2.5.1'],
recommendedVersion: 'v2.5.1',
ports: {
main: 8080
}
},
{
name: 'fider',
fancyName: 'Fider',
baseImage: 'getfider/fider',
images: ['postgres:12-alpine'],
versions: ['stable'],
recommendedVersion: 'stable',
ports: {
main: 3000
}
}
];

View File

@@ -0,0 +1,10 @@
<svg
class="absolute top-0 left-0 -m-4 h-10 w-10"
viewBox="0 0 50 52"
xmlns="http://www.w3.org/2000/svg"
><title>Logomark</title><path
d="M49.626 11.564a.809.809 0 0 1 .028.209v10.972a.8.8 0 0 1-.402.694l-9.209 5.302V39.25c0 .286-.152.55-.4.694L20.42 51.01c-.044.025-.092.041-.14.058-.018.006-.035.017-.054.022a.805.805 0 0 1-.41 0c-.022-.006-.042-.018-.063-.026-.044-.016-.09-.03-.132-.054L.402 39.944A.801.801 0 0 1 0 39.25V6.334c0-.072.01-.142.028-.21.006-.023.02-.044.028-.067.015-.042.029-.085.051-.124.015-.026.037-.047.055-.071.023-.032.044-.065.071-.093.023-.023.053-.04.079-.06.029-.024.055-.05.088-.069h.001l9.61-5.533a.802.802 0 0 1 .8 0l9.61 5.533h.002c.032.02.059.045.088.068.026.02.055.038.078.06.028.029.048.062.072.094.017.024.04.045.054.071.023.04.036.082.052.124.008.023.022.044.028.068a.809.809 0 0 1 .028.209v20.559l8.008-4.611v-10.51c0-.07.01-.141.028-.208.007-.024.02-.045.028-.068.016-.042.03-.085.052-.124.015-.026.037-.047.054-.071.024-.032.044-.065.072-.093.023-.023.052-.04.078-.06.03-.024.056-.05.088-.069h.001l9.611-5.533a.801.801 0 0 1 .8 0l9.61 5.533c.034.02.06.045.09.068.025.02.054.038.077.06.028.029.048.062.072.094.018.024.04.045.054.071.023.039.036.082.052.124.009.023.022.044.028.068zm-1.574 10.718v-9.124l-3.363 1.936-4.646 2.675v9.124l8.01-4.611zm-9.61 16.505v-9.13l-4.57 2.61-13.05 7.448v9.216l17.62-10.144zM1.602 7.719v31.068L19.22 48.93v-9.214l-9.204-5.209-.003-.002-.004-.002c-.031-.018-.057-.044-.086-.066-.025-.02-.054-.036-.076-.058l-.002-.003c-.026-.025-.044-.056-.066-.084-.02-.027-.044-.05-.06-.078l-.001-.003c-.018-.03-.029-.066-.042-.1-.013-.03-.03-.058-.038-.09v-.001c-.01-.038-.012-.078-.016-.117-.004-.03-.012-.06-.012-.09v-.002-21.481L4.965 9.654 1.602 7.72zm8.81-5.994L2.405 6.334l8.005 4.609 8.006-4.61-8.006-4.608zm4.164 28.764l4.645-2.674V7.719l-3.363 1.936-4.646 2.675v20.096l3.364-1.937zM39.243 7.164l-8.006 4.609 8.006 4.609 8.005-4.61-8.005-4.608zm-.801 10.605l-4.646-2.675-3.363-1.936v9.124l4.645 2.674 3.364 1.937v-9.124zM20.02 38.33l11.743-6.704 5.87-3.35-8-4.606-9.211 5.303-8.395 4.833 7.993 4.524z"
fill="#FF2D20"
fill-rule="evenodd"
/></svg
>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -4,9 +4,7 @@
<svg
class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-10 w-10' : 'mx-auto w-8 h-8'}
height="64"
viewBox="0 0 32 32"
width="64"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
><defs

View File

@@ -0,0 +1,121 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
viewBox="0 0 700 240"
xmlns="http://www.w3.org/2000/svg"
class={isAbsolute ? 'w-36 absolute top-0 left-0 -m-3 -mt-5' : 'w-28 mx-auto'}
><path fill="#FDBC3D" d="m90.694 107.498-.981.39-20.608 8.23 6.332 6.547z" /><path
fill="#8EC63F"
d="M61.139 77.914 46.632 93 56.9 103.547c8.649-7.169 17.832-10.502 18.653-10.789L61.139 77.914z"
/><path fill="#208ECB" d="M61.139 77.914 46.367 63.247l-14.228 14.8 14.493 14.952z" /><path
fill="#273C8B"
d="m40.767 57.48-6.943 2.79a38.381 38.381 0 0 0-11.742 7.418L32.14 78.047l14.228-14.8-5.601-5.768z"
/><path
fill="#EE4649"
d="m119.074 138.128-.243-.25-5.653 5.675c1.897-1.516 4.287-3.66 5.896-5.425z"
/><path
fill="#F6944E"
d="m102.088 150.087 3.709-1.875a46.26 46.26 0 0 0 7.381-4.659l5.653-5.676-14.311-15.285-14.493 15.072 12.061 12.423z"
/><path fill="#FFC951" d="m90.279 107.926-14.842 14.74 14.589 14.998 14.493-15.072z" /><path
fill="#F6CC18"
d="m69.087 116.125-11.256 4.493c-3.301.973-6.096 2.843-8.434 5.081l11.548 11.892 14.493-14.926-6.35-6.54z"
/><path
fill="#C5D82D"
d="m56.886 103.559-10.253-10.56L32 107.926l11.784 11.991c3.304-6.888 8.174-12.272 13.103-16.358z"
/><path fill="#0D77B3" d="m32.14 78.047-14.507 14.94 14.365 14.939 14.634-14.927z" /><path
fill="#2A377E"
d="M32.14 78.047 22.08 67.688a38.573 38.573 0 0 0-11.093 18.455l6.645 6.843 14.506-14.94z"
/><path
fill="#DA2128"
d="m94.826 162.454-4.87 5.017 14.808 15.397c-.632-1.942-1.606-4.438-2.58-6.307l-7.357-14.107z"
/><path
fill="#F8A561"
d="m91.24 155.575 10.832-5.48-12.046-12.43-14.506 14.939 14.436 14.867 4.87-5.017z"
/><path fill="#FDBC3D" d="m75.437 122.665-14.493 14.926 14.576 15.013 14.506-14.94z" /><path
fill="#FAD412"
d="M49.397 125.7c-6.71 6.472-9.664 16.047-9.664 16.047-.3-4.606.06-8.83.907-12.698l-8.513 8.742 14.311 14.74 14.506-14.94-11.547-11.892z"
/><path
fill="#C4D52D"
d="m43.783 119.917-11.785-11.991-13.29 13.687 3.708 6.178 9.71 10 8.52-8.775a42.699 42.699 0 0 1 3.137-9.099z"
/><path
fill="#1B80C1"
d="m17.633 92.986-7.638 7.72c.65 5.1 2.35 10.3 5.193 15.04l3.52 5.867 13.29-13.687-14.365-14.94z"
/><path
fill="#1A4685"
d="M10.989 86.143c-1.22 4.667-1.597 9.683-.993 14.563l7.638-7.72-6.645-6.843z"
/><path
fill="#B12026"
d="m89.956 197.35 12.502 13.022c4.143-8.355 5.148-18.255 2.307-27.504l-.302-.311-14.507 14.793z"
/><path fill="#E42028" d="M89.956 167.47 75.52 182.484l14.436 14.867 14.506-14.793z" /><path
fill="#F16B4E"
d="m75.52 152.604-14.576 14.867 14.576 15.012 14.436-15.012z"
/><path fill="#FAD412" d="m60.944 137.591-14.506 14.94 14.506 14.94 14.576-14.867z" /><path
fill="#FFC951"
d="m32.127 137.792-2.293 2.36 10.933 18.22 5.671-5.841z"
/><path fill="#FFC951" d="m22.416 127.79 7.418 12.363 2.293-2.361z" /><path
fill="#981C20"
d="M102.458 210.371 89.955 197.35 75.45 212.29l12.918 13.304a36.951 36.951 0 0 0 14.09-15.222z"
/><path
fill="#C92039"
d="m75.52 182.483-12.59 12.823 6.423 10.704 6.097 6.28 14.506-14.94z"
/><path fill="#F05B41" d="m60.944 167.47-9.096 9.369 11.081 18.467 12.59-12.823z" /><path
fill="#F6CC18"
d="m46.438 152.53-5.671 5.842 11.081 18.467 9.096-9.368z"
/><path
fill="#7A1319"
d="m74.01 213.772 8.904 14.838 4.104-2.237c.429-.233.934-.533 1.35-.78L75.45 212.29l-1.44 1.482z"
/><path fill="#981C20" d="m69.353 206.01 4.658 7.762 1.44-1.482z" /><path
fill="#15796E"
d="m147.842 48.094 10.653-10.971a41.81 41.81 0 0 0 .943-6.94l-11.414-11.755-14.48 14.94 14.298 14.726z"
/><path fill="#29B364" d="m133.53 33.354 14.494-14.926-2.737-2.965-20.95 8.422z" /><path
fill="#21A29F"
d="M151.819 52.189c3.057-4.334 5.434-9.932 6.677-15.066l-10.653 10.971 3.976 4.095z"
/><path
fill="#12827F"
d="M159.438 30.183c.307-6.28-.783-12.862-3.488-19.006l-1.41.567-6.516 6.684 11.414 11.755zM154.54 11.744l-9.253 3.72 2.737 2.964z"
/><path fill="#0C6355" d="m133.336 63.034 14.506-14.94-14.311-14.713-14.493 14.926z" /><path
fill="#1B974D"
d="m104.532 33.368 14.506 14.94 14.48-14.94-9.2-9.476-17.363 6.98z"
/><path fill="#16669F" d="m106.955 30.872-3.485 1.401 1.062 1.095z" /><path
fill="#44BFBD"
d="M135.9 65.674A41.696 41.696 0 0 0 151.82 52.19l-3.977-4.095-14.506 14.94 2.564 2.64z"
/><path
fill="#0D5650"
d="m115.71 74.76 11.052-4.956 6.574-6.77-14.298-14.727-14.506 14.94z"
/><path fill="#3FAF49" d="m119.038 48.307-14.506-14.94-14.576 14.868 14.563 14.999z" /><path
fill="#0D77B3"
d="m104.532 33.368-1.062-1.095-20.97 8.43 7.456 7.532z"
/><path
fill="#0C6355"
d="M134.766 66.217c.352-.157.789-.376 1.134-.543l-2.564-2.64-6.574 6.77 8.004-3.587z"
/><path fill="#12827F" d="m115.71 74.76-11.178-11.513-14.506 14.94 5.47 5.633z" /><path
fill="#4EB648"
d="M104.532 63.247 89.956 48.235 75.52 63.247l14.493 14.927z"
/><path fill="#16669F" d="M89.956 48.235 82.5 40.703l-20.868 8.388L75.52 63.247z" /><path
fill="#FBB139"
d="M129.526 119.012c1.902-7.144 2.108-15.019.353-22.538l-11.048 11.379 10.695 11.16z"
/><path
fill="#E2B523"
d="m110.62 99.542 8.21 8.311 11.049-11.38a46.303 46.303 0 0 0-1.186-4.149l-18.074 7.218z"
/><path fill="#189590" d="M90.026 78.186 76.128 92.501l19.367-8.681z" /><path
fill="#8EC63F"
d="m76.083 92.521 13.943-14.335-14.506-14.94-14.381 14.668 14.413 14.844z"
/><path
fill="#0D77B3"
d="M75.52 63.247 61.633 49.09l-2.264.91-13.002 13.246L61.14 77.914z"
/><path fill="#1953A2" d="m59.37 50.002-18.603 7.477 5.6 5.768z" /><path
fill="#ED3551"
d="M119.324 137.84c.885-.988 2.15-2.59 2.942-3.646l-3.17 3.41.228.236z"
/><path
fill="#F8A561"
d="m118.83 137.877 3.437-3.683a46.268 46.268 0 0 0 7.259-15.182l-10.695-11.159-14.311 14.74 14.31 15.284z"
/><path
fill="#E9B520"
d="m90.279 107.926 14.24 14.666 14.312-14.739-8.212-8.311-19.925 7.956z"
/><path
fill="#EE4649"
d="m118.83 137.877.244.251c.085-.095.166-.193.25-.288l-.228-.235-.265.272z"
/></svg
>

View File

@@ -0,0 +1,26 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
viewBox="0 0 81 84"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_5273_21928)">
<path
d="M79.7186 28.6019C82.1218 21.073 80.6778 6.03601 76.0158 0.487861C75.4073 -0.238064 74.2624 -0.134361 73.757 0.664158L68.0121 9.72786C66.5887 11.5427 64.0308 11.9575 62.1124 10.6923C55.8827 6.59601 48.4359 4.21082 40.4322 4.21082C32.4285 4.21082 24.9817 6.59601 18.752 10.6923C16.8336 11.9575 14.2757 11.5323 12.8523 9.72786L7.10738 0.664158C6.60199 -0.134361 5.45712 -0.238064 4.84859 0.487861C0.186621 6.03601 -1.25735 21.073 1.14583 28.6019C1.94002 31.1012 2.16693 33.7456 1.69248 36.3279C1.22834 38.879 0.753897 41.9693 0.753897 44.1056C0.753897 66.1323 18.5251 84.0004 40.4322 84.0004C62.3497 84.0004 80.1105 66.1427 80.1105 44.1056C80.1105 41.959 79.6464 38.879 79.1719 36.3279C78.6975 33.7456 78.9244 31.1012 79.7186 28.6019ZM40.4322 75.0819C23.4965 75.0819 9.71684 61.2271 9.71684 44.199C9.71684 43.639 9.73747 43.0893 9.7581 42.5397C10.3769 30.9353 17.3802 21.0108 27.3024 16.2819C31.2836 14.3738 35.7393 13.316 40.4322 13.316C45.1251 13.316 49.5808 14.3842 53.5724 16.2923C63.4945 21.0212 70.4978 30.9456 71.1166 42.5397C71.1476 43.0893 71.1579 43.639 71.1579 44.199C71.1476 61.2271 57.3679 75.0819 40.4322 75.0819Z"
fill="#1EB4D4"
/>
<path
d="M53.7371 56.083L45.8881 42.4044L39.153 30.997C38.9983 30.7274 38.7095 30.5615 38.3898 30.5615H31.9538C31.634 30.5615 31.3452 30.7378 31.1905 31.0074C31.0358 31.2874 31.0358 31.6296 31.2008 31.8993L37.6368 42.7881L28.9936 56.0415C28.8183 56.3111 28.7977 56.6637 28.9524 56.9541C29.1071 57.2444 29.4062 57.4207 29.7259 57.4207H36.2032C36.5023 57.4207 36.7808 57.2652 36.9458 57.0163L41.6181 49.6741L45.8056 56.9748C45.9603 57.2548 46.2594 57.4207 46.5688 57.4207H52.9533C53.273 57.4207 53.5618 57.2548 53.7165 56.9748C53.9022 56.6948 53.9022 56.363 53.7371 56.083Z"
fill="#1EB4D4"
/>
</g>
<defs>
<clipPath id="clip0_5273_21928">
<rect width="81" height="84" fill="white" />
</clipPath>
</defs>
</svg>

View File

@@ -5,8 +5,6 @@
<svg
version="1.0"
xmlns="http://www.w3.org/2000/svg"
width="856.000000pt"
height="856.000000pt"
viewBox="0 0 856.000000 856.000000"
preserveAspectRatio="xMidYMid meet"
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}

View File

@@ -15,7 +15,6 @@ export function findBuildPack(pack, packageManager = 'npm') {
...metaData,
...defaultBuildAndDeploy(packageManager),
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: null
};
@@ -163,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',
@@ -188,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',

View File

@@ -12,6 +12,7 @@ import type {
Application,
ApplicationPersistentStorage
} from '@prisma/client';
import { setDefaultBaseImage } from '$lib/buildPacks/common';
export async function listApplications(teamId: string): Promise<Application[]> {
if (teamId === '0') {
@@ -195,8 +196,18 @@ export async function getApplication({ id, teamId }: { id: string; teamId: strin
return s;
});
}
const { baseImage, baseBuildImage, baseBuildImages, baseImages } = setDefaultBaseImage(
body.buildPack
);
return { ...body };
// Set default build images
if (!body.baseImage) {
body.baseImage = baseImage;
}
if (!body.baseBuildImage) {
body.baseBuildImage = baseBuildImage;
}
return { ...body, baseBuildImages, baseImages };
}
export async function configureGitRepository({
@@ -267,7 +278,9 @@ export async function configureApplication({
pythonVariable,
dockerFileLocation,
denoMainFile,
denoOptions
denoOptions,
baseImage,
baseBuildImage
}: {
id: string;
buildPack: string;
@@ -286,6 +299,8 @@ export async function configureApplication({
dockerFileLocation: string;
denoMainFile: string;
denoOptions: string;
baseImage: string;
baseBuildImage: string;
}): Promise<Application> {
return await prisma.application.update({
where: { id },
@@ -305,7 +320,9 @@ export async function configureApplication({
pythonVariable,
dockerFileLocation,
denoMainFile,
denoOptions
denoOptions,
baseImage,
baseBuildImage
}
});
}

View File

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

View File

@@ -14,7 +14,9 @@ const include: Prisma.ServiceInclude = {
wordpress: true,
ghost: true,
meiliSearch: true,
umami: true
umami: true,
hasura: true,
fider: true
};
export async function listServicesWithIncludes() {
return await prisma.service.findMany({
@@ -97,6 +99,17 @@ export async function getService({ id, teamId }: { id: string; teamId: string })
body.umami.umamiAdminPassword = decrypt(body.umami.umamiAdminPassword);
if (body.umami?.hashSalt) body.umami.hashSalt = decrypt(body.umami.hashSalt);
if (body.hasura?.postgresqlPassword)
body.hasura.postgresqlPassword = decrypt(body.hasura.postgresqlPassword);
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 };
@@ -243,6 +256,44 @@ export async function configureServiceType({
}
}
});
} else if (type === 'hasura') {
const postgresqlUser = cuid();
const postgresqlPassword = encrypt(generatePassword());
const postgresqlDatabase = 'hasura';
const graphQLAdminPassword = encrypt(generatePassword());
await prisma.service.update({
where: { id },
data: {
type,
hasura: {
create: {
postgresqlDatabase,
postgresqlPassword,
postgresqlUser,
graphQLAdminPassword
}
}
}
});
} 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
}
}
}
});
}
}
@@ -305,60 +356,56 @@ export async function updateService({
return await prisma.service.update({ where: { id }, data: { fqdn, name, exposePort } });
}
export async function updateLanguageToolService({
export async function updateFiderService({
id,
fqdn,
name,
exposePort,
name
emailNoreply,
emailMailgunApiKey,
emailMailgunDomain,
emailMailgunRegion,
emailSmtpHost,
emailSmtpPort,
emailSmtpUser,
emailSmtpPassword,
emailSmtpEnableStartTls
}: {
id: string;
fqdn: string;
exposePort?: number;
name: string;
emailNoreply: string;
emailMailgunApiKey: string;
emailMailgunDomain: string;
emailMailgunRegion: string;
emailSmtpHost: string;
emailSmtpPort: number;
emailSmtpUser: string;
emailSmtpPassword: string;
emailSmtpEnableStartTls: boolean;
}): Promise<Service> {
return await prisma.service.update({ where: { id }, data: { fqdn, name, exposePort } });
}
export async function updateMeiliSearchService({
id,
fqdn,
exposePort,
name
}: {
id: string;
fqdn: string;
exposePort?: number;
name: string;
}): Promise<Service> {
return await prisma.service.update({ where: { id }, data: { fqdn, name, exposePort } });
}
export async function updateVaultWardenService({
id,
fqdn,
exposePort,
name
}: {
id: string;
fqdn: string;
exposePort?: number;
name: string;
}): Promise<Service> {
return await prisma.service.update({ where: { id }, data: { fqdn, name, exposePort } });
}
export async function updateVsCodeServer({
id,
fqdn,
exposePort,
name
}: {
id: string;
fqdn: string;
exposePort?: number;
name: string;
}): Promise<Service> {
return await prisma.service.update({ where: { id }, data: { fqdn, name, exposePort } });
return await prisma.service.update({
where: { id },
data: {
fqdn,
name,
exposePort,
fider: {
update: {
emailNoreply,
emailMailgunApiKey,
emailMailgunDomain,
emailMailgunRegion,
emailSmtpHost,
emailSmtpPort,
emailSmtpUser,
emailSmtpPassword,
emailSmtpEnableStartTls
}
}
}
});
}
export async function updateWordpress({
@@ -414,8 +461,10 @@ export async function updateGhostService({
export async function removeService({ id }: { id: string }): Promise<void> {
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 } });
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
await prisma.minio.deleteMany({ where: { serviceId: id } });
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });

View File

@@ -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<string> = [];
Dockerfile.push(`FROM ${imageForBuild}`);
Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
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,
@@ -21,7 +49,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
const Dockerfile: Array<string> = [];
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) {
@@ -41,10 +69,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 });
@@ -65,11 +94,13 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
} = data;
const Dockerfile: Array<string> = [];
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`);

View File

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

View File

@@ -109,6 +109,7 @@ export async function generateSSLCerts(): Promise<void> {
include: { destinationDocker: true, settings: true },
orderBy: { createdAt: 'desc' }
});
const { fqdn, isDNSCheckEnabled } = await db.prisma.setting.findFirst();
for (const application of applications) {
try {
if (application.fqdn && application.destinationDockerId) {
@@ -147,7 +148,6 @@ export async function generateSSLCerts(): Promise<void> {
}
}
const services = await listServicesWithIncludes();
for (const service of services) {
try {
if (service.fqdn && service.destinationDockerId) {
@@ -171,7 +171,6 @@ export async function generateSSLCerts(): Promise<void> {
console.log(`Error during generateSSLCerts with ${service.fqdn}: ${error}`);
}
}
const { fqdn } = await db.prisma.setting.findFirst();
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
@@ -193,73 +192,99 @@ export async function generateSSLCerts(): Promise<void> {
file.endsWith('.pem') && certificates.push(file.replace(/\.pem$/, ''));
}
}
const resolver = new dns.Resolver({ timeout: 2000 });
resolver.setServers(['8.8.8.8', '1.1.1.1']);
let ipv4, ipv6;
try {
ipv4 = await (await asyncExecShell(`curl -4s https://ifconfig.io`)).stdout;
} catch (error) {}
try {
ipv6 = await (await asyncExecShell(`curl -6s https://ifconfig.io`)).stdout;
} catch (error) {}
for (const ssl of ssls) {
if (!dev) {
if (
certificates.includes(ssl.domain) ||
certificates.includes(ssl.domain.replace('www.', ''))
) {
// console.log(`Certificate for ${ssl.domain} already exists`);
} else {
// Checking DNS entry before generating certificate
if (ipv4 || ipv6) {
let domains4 = [];
let domains6 = [];
try {
domains4 = await resolver.resolve4(ssl.domain);
} catch (error) {}
try {
domains6 = await resolver.resolve6(ssl.domain);
} catch (error) {}
if (domains4.length > 0 || domains6.length > 0) {
if (
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
) {
console.log('Generating SSL for', ssl.domain);
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
if (isDNSCheckEnabled) {
const resolver = new dns.Resolver({ timeout: 2000 });
resolver.setServers(['8.8.8.8', '1.1.1.1']);
let ipv4, ipv6;
try {
ipv4 = await (await asyncExecShell(`curl -4s https://ifconfig.io`)).stdout;
} catch (error) {}
try {
ipv6 = await (await asyncExecShell(`curl -6s https://ifconfig.io`)).stdout;
} catch (error) {}
for (const ssl of ssls) {
if (!dev) {
if (
certificates.includes(ssl.domain) ||
certificates.includes(ssl.domain.replace('www.', ''))
) {
// console.log(`Certificate for ${ssl.domain} already exists`);
} else {
// Checking DNS entry before generating certificate
if (ipv4 || ipv6) {
let domains4 = [];
let domains6 = [];
try {
domains4 = await resolver.resolve4(ssl.domain);
} catch (error) {}
try {
domains6 = await resolver.resolve6(ssl.domain);
} catch (error) {}
if (domains4.length > 0 || domains6.length > 0) {
if (
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
) {
console.log('Generating SSL for', ssl.domain);
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
}
}
}
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
}
} else {
if (
certificates.includes(ssl.domain) ||
certificates.includes(ssl.domain.replace('www.', ''))
) {
console.log(`Certificate for ${ssl.domain} already exists`);
} else {
// Checking DNS entry before generating certificate
if (ipv4 || ipv6) {
let domains4 = [];
let domains6 = [];
try {
domains4 = await resolver.resolve4(ssl.domain);
} catch (error) {}
try {
domains6 = await resolver.resolve6(ssl.domain);
} catch (error) {}
if (domains4.length > 0 || domains6.length > 0) {
if (
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
) {
console.log('Generating SSL for', ssl.domain);
return;
}
}
}
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
}
}
}
} else {
if (!dev) {
for (const ssl of ssls) {
if (
certificates.includes(ssl.domain) ||
certificates.includes(ssl.domain.replace('www.', ''))
) {
} else {
console.log('Generating SSL for', ssl.domain);
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
}
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
}
} else {
if (
certificates.includes(ssl.domain) ||
certificates.includes(ssl.domain.replace('www.', ''))
) {
console.log(`Certificate for ${ssl.domain} already exists`);
} else {
// Checking DNS entry before generating certificate
if (ipv4 || ipv6) {
let domains4 = [];
let domains6 = [];
try {
domains4 = await resolver.resolve4(ssl.domain);
} catch (error) {}
try {
domains6 = await resolver.resolve6(ssl.domain);
} catch (error) {}
if (domains4.length > 0 || domains6.length > 0) {
if (
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
) {
console.log('Generating SSL for', ssl.domain);
return;
}
}
for (const ssl of ssls) {
if (
certificates.includes(ssl.domain) ||
certificates.includes(ssl.domain.replace('www.', ''))
) {
console.log(`Certificate for ${ssl.domain} already exists`);
} else {
console.log('Generating SSL for', ssl.domain);
}
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
}
}
}

View File

@@ -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 <span class=\"font-bold text-orange-500 pl-1\">{{packageManager}}</span>. Using it for predefined commands commands.",
"found_lock_file": "Found lock file for <span class=\"font-bold text-orange-500 px-1\"> {{packageManager}}</span>.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,10 @@
"git_source": "Git Source",
"git_repository": "Git Repository",
"build_pack": "Build Pack",
"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)",
@@ -227,7 +231,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",
@@ -292,20 +297,24 @@
"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",
"permission_denied": "You do not have permission to do this. \\nAsk an admin to modify your permissions.",
"domain_removed": "Domain removed",
"ssl_explainer": "If you specify <span class='text-yellow-500 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-yellow-500 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa.",
"ssl_explainer": "If you specify <span class='text-yellow-500 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-yellow-500 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa.<br><br><span class='text-yellow-500 font-bold'>WARNING:</span> If you change an already set domain, it will brake webhooks and other integrations! You need to manually update them.",
"must_remove_domain_before_changing": "Must remove the domain before you can change this setting.",
"registration_allowed": "Registration allowed?",
"registration_allowed_explainer": "Allow further registrations to the application. <br>It's turned off after the first registration.",
"coolify_proxy_settings": "Coolify Proxy Settings",
"credential_stat_explainer": "Credentials for <a class=\"text-white font-bold\" href=\"{{link}}\" target=\"_blank\">stats</a> page.",
"auto_update_enabled": "Auto update enabled?",
"auto_update_enabled_explainer": "Enable automatic updates for Coolify. It will be done automatically behind the scenes, if there is no build process running."
"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. <br>You need to have <span class='font-bold text-yellow-500'>both DNS entries</span> set in advance.<br><br>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.<br><br>Turning it off is useful when Coolify is behind a reverse proxy or tunnel."
},
"team": {
"pending_invitations": "Pending invitations",

View File

@@ -48,7 +48,9 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
pythonModule,
pythonVariable,
denoOptions,
exposePort
exposePort,
baseImage,
baseBuildImage
} = job.data;
let {
branch,
@@ -188,8 +190,7 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
//
}
if (!imageFound || deployNeeded) {
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId);
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
if (buildpacks[buildPack])
await buildpacks[buildPack]({
buildId,
@@ -220,7 +221,9 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
pythonVariable,
dockerFileLocation,
denoMainFile,
denoOptions
denoOptions,
baseImage,
baseBuildImage
});
else {
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });

View File

@@ -4,34 +4,73 @@ export default async function (): Promise<void> {
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);
}
}
}
}

View File

@@ -1,4 +1,5 @@
import { writable, type Writable } from 'svelte/store';
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 }> =
writable({
@@ -6,3 +7,8 @@ export const gitTokens: Writable<{ githubToken: string | null; gitlabToken: stri
gitlabToken: null
});
export const disabledButton: Writable<boolean> = writable(false);
export const features: Readable<{ latestVersion: string; beta: boolean }> = readable({
beta: browser && window.localStorage.getItem('beta') === 'true',
latestVersion: browser && window.localStorage.getItem('latestVersion')
});

View File

@@ -35,6 +35,8 @@ export type BuilderJob = {
persistentStorage: { path: string }[];
pullmergeRequestId?: unknown;
sourceBranch?: string;
baseImage: string;
baseBuildImage: string;
};
// TODO: Add the other build types