Merge branch 'main' into restray_i18n

This commit is contained in:
Restray
2022-04-04 12:29:20 +02:00
committed by GitHub
52 changed files with 1188 additions and 258 deletions

View File

@@ -100,10 +100,14 @@ export const setDefaultConfiguration = async (data) => {
if (buildPack === 'static') port = 80;
else if (buildPack === 'node') port = 3000;
else if (buildPack === 'php') port = 80;
else if (buildPack === 'python') port = 8000;
}
if (!installCommand) installCommand = template?.installCommand || 'yarn install';
if (!startCommand) startCommand = template?.startCommand || 'yarn start';
if (!buildCommand) buildCommand = template?.buildCommand || null;
if (template) {
if (!installCommand) installCommand = template?.installCommand || 'yarn install';
if (!startCommand) startCommand = template?.startCommand || 'yarn start';
if (!buildCommand) buildCommand = template?.buildCommand || null;
}
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
if (baseDirectory) {
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
@@ -136,7 +140,11 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
`
);
await fs.writeFile(`${workdir}/entrypoint.sh`, `chown -R 1000 /app`);
saveBuildLog({ line: 'Copied default configuration file for PHP.', buildId, applicationId });
await saveBuildLog({
line: 'Copied default configuration file for PHP.',
buildId,
applicationId
});
} else if (staticDeployments.includes(buildPack)) {
await fs.writeFile(
`${workdir}/nginx.conf`,
@@ -190,7 +198,7 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
}
`
);
saveBuildLog({ line: 'Copied default configuration file.', buildId, applicationId });
await saveBuildLog({ line: 'Copied default configuration file.', buildId, applicationId });
}
} catch (error) {
console.log(error);

View File

@@ -12,6 +12,7 @@ import php from './php';
import rust from './rust';
import astro from './static';
import eleventy from './static';
import python from './python';
export {
node,
@@ -27,5 +28,6 @@ export {
php,
rust,
astro,
eleventy
eleventy,
python
};

View File

@@ -0,0 +1,71 @@
import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => {
const {
workdir,
port,
baseDirectory,
secrets,
pullmergeRequestId,
pythonWSGI,
pythonModule,
pythonVariable
} = data;
const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${image}`);
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}`);
}
}
}
});
}
if (pythonWSGI?.toLowerCase() === 'gunicorn') {
Dockerfile.push(`RUN pip install gunicorn`);
} else if (pythonWSGI?.toLowerCase() === 'uwsgi') {
Dockerfile.push(`RUN apk add --no-cache uwsgi-python3`);
// Dockerfile.push(`RUN pip install --no-cache-dir uwsgi`)
}
try {
await fs.stat(`${workdir}${baseDirectory || ''}/requirements.txt`);
Dockerfile.push(`COPY .${baseDirectory || ''}/requirements.txt ./`);
Dockerfile.push(`RUN pip install --no-cache-dir -r .${baseDirectory || ''}/requirements.txt`);
} catch (e) {
//
}
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`EXPOSE ${port}`);
if (pythonWSGI?.toLowerCase() === 'gunicorn') {
Dockerfile.push(`CMD gunicorn -w=4 -b=0.0.0.0:8000 ${pythonModule}:${pythonVariable}`);
} else if (pythonWSGI?.toLowerCase() === 'uwsgi') {
Dockerfile.push(
`CMD uwsgi --master -p 4 --http-socket 0.0.0.0:8000 --uid uwsgi --plugins python3 --protocol uwsgi --wsgi ${pythonModule}:${pythonVariable}`
);
} else {
Dockerfile.push(`CMD python ${pythonModule}`);
}
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
};
export default async function (data) {
try {
const image = 'python:3-alpine';
await createDockerfile(data, image);
await buildImage(data);
} catch (error) {
throw error;
}
}

View File

@@ -80,7 +80,7 @@ export const getTeam = (event) => {
export const getUserDetails = async (event, isAdminRequired = true) => {
const teamId = getTeam(event);
const userId = event.locals.session.data.userId || null;
const userId = event?.locals?.session?.data?.userId || null;
const { permission = 'read' } = await db.prisma.permission.findFirst({
where: { teamId, userId },
select: { permission: true },

View File

@@ -0,0 +1,25 @@
<script>
export let database;
import Clickhouse from './svg/databases/Clickhouse.svelte';
import CouchDb from './svg/databases/CouchDB.svelte';
import MongoDb from './svg/databases/MongoDB.svelte';
import MySql from './svg/databases/MySQL.svelte';
import PostgreSql from './svg/databases/PostgreSQL.svelte';
import Redis from './svg/databases/Redis.svelte';
</script>
<span class="relative">
{#if database.type === 'clickhouse'}
<Clickhouse />
{:else if database.type === 'couchdb'}
<CouchDb />
{:else if database.type === 'mongodb'}
<MongoDb />
{:else if database.type === 'mysql'}
<MySql />
{:else if database.type === 'postgresql'}
<PostgreSql />
{:else if database.type === 'redis'}
<Redis />
{/if}
</span>

View File

@@ -0,0 +1,55 @@
<script>
export let service;
import Ghost from './svg/services/Ghost.svelte';
import LanguageTool from './svg/services/LanguageTool.svelte';
import MinIo from './svg/services/MinIO.svelte';
import N8n from './svg/services/N8n.svelte';
import NocoDb from './svg/services/NocoDB.svelte';
import PlausibleAnalytics from './svg/services/PlausibleAnalytics.svelte';
import UptimeKuma from './svg/services/UptimeKuma.svelte';
import VaultWarden from './svg/services/VaultWarden.svelte';
import VsCodeServer from './svg/services/VSCodeServer.svelte';
import Wordpress from './svg/services/Wordpress.svelte';
</script>
{#if service.type === 'plausibleanalytics'}
<a href="https://plausible.io" target="_blank">
<PlausibleAnalytics />
</a>
{:else if service.type === 'nocodb'}
<a href="https://nocodb.com" target="_blank">
<NocoDb />
</a>
{:else if service.type === 'minio'}
<a href="https://min.io" target="_blank">
<MinIo />
</a>
{:else if service.type === 'vscodeserver'}
<a href="https://coder.com" target="_blank">
<VsCodeServer />
</a>
{:else if service.type === 'wordpress'}
<a href="https://wordpress.org" target="_blank">
<Wordpress />
</a>
{:else if service.type === 'vaultwarden'}
<a href="https://github.com/dani-garcia/vaultwarden" target="_blank">
<VaultWarden />
</a>
{:else if service.type === 'languagetool'}
<a href="https://languagetool.org/dev" target="_blank">
<LanguageTool />
</a>
{:else if service.type === 'n8n'}
<a href="https://n8n.io" target="_blank">
<N8n />
</a>
{:else if service.type === 'uptimekuma'}
<a href="https://github.com/louislam/uptime-kuma" target="_blank">
<UptimeKuma />
</a>
{:else if service.type === 'ghost'}
<a href="https://ghost.org" target="_blank">
<Ghost />
</a>
{/if}

View File

@@ -19,7 +19,7 @@ export const staticDeployments = [
'astro',
'eleventy'
];
export const notNodeDeployments = ['php', 'docker', 'rust'];
export const notNodeDeployments = ['php', 'docker', 'rust', 'python'];
export function getDomain(domain) {
return domain?.replace('https://', '').replace('http://', '');
@@ -37,3 +37,9 @@ export function dashify(str: string, options?: any): string {
.replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m))
.toLowerCase();
}
export function changeQueryParams(buildId) {
const queryParams = new URLSearchParams(window.location.search);
queryParams.set('buildId', buildId);
return history.pushState(null, null, '?' + queryParams.toString());
}

View File

@@ -0,0 +1,45 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
viewBox="0 0 127 74"
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
xmlns="http://www.w3.org/2000/svg"
><path
d="M.825 73.993l23.244-59.47A21.85 21.85 0 0144.42.625h14.014L35.19 60.096a21.85 21.85 0 01-20.352 13.897H.825z"
fill="url(#meilisearch_logo_svg__paint0_linear_0_6)"
/><path
d="M34.925 73.993l23.243-59.47A21.85 21.85 0 0178.52.626h14.013L69.29 60.096a21.85 21.85 0 01-20.351 13.897H34.925z"
fill="url(#meilisearch_logo_svg__paint1_linear_0_6)"
/><path
d="M69.026 73.993l23.244-59.47A21.85 21.85 0 01112.621.626h14.014l-23.244 59.47a21.851 21.851 0 01-20.352 13.897H69.026z"
fill="url(#meilisearch_logo_svg__paint2_linear_0_6)"
/><defs
><linearGradient
id="meilisearch_logo_svg__paint0_linear_0_6"
x1="126.635"
y1="-4.978"
x2="0.825"
y2="66.098"
gradientUnits="userSpaceOnUse"
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
><linearGradient
id="meilisearch_logo_svg__paint1_linear_0_6"
x1="126.635"
y1="-4.978"
x2="0.825"
y2="66.098"
gradientUnits="userSpaceOnUse"
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
><linearGradient
id="meilisearch_logo_svg__paint2_linear_0_6"
x1="126.635"
y1="-4.978"
x2="0.825"
y2="66.098"
gradientUnits="userSpaceOnUse"
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
></defs
></svg
>

View File

@@ -146,6 +146,13 @@ export function findBuildPack(pack, packageManager = 'npm') {
port: 80
};
}
if (pack === 'python') {
return {
...metaData,
startCommand: null,
port: 8000
};
}
return {
name: 'node',
fancyName: 'Node.js',
@@ -249,6 +256,12 @@ export const buildPacks = [
fancyName: 'Rust',
hoverColor: 'hover:bg-pink-700',
color: 'bg-pink-700'
},
{
name: 'python',
fancyName: 'Python',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
}
];
export const scanningTemplates = {

View File

@@ -214,11 +214,15 @@ export async function configureApplication({
buildCommand,
startCommand,
baseDirectory,
publishDirectory
publishDirectory,
pythonWSGI,
pythonModule,
pythonVariable
}) {
return await prisma.application.update({
where: { id },
data: {
name,
buildPack,
fqdn,
port,
@@ -227,7 +231,9 @@ export async function configureApplication({
startCommand,
baseDirectory,
publishDirectory,
name
pythonWSGI,
pythonModule,
pythonVariable
}
});
}

View File

@@ -46,7 +46,9 @@ export function ErrorHandler(e) {
if (e.message?.includes('git clone')) {
truncatedError.message = 'git clone failed';
}
sentry.captureException(truncatedError);
if (!e.message?.includes('Coolify Proxy is not running')) {
sentry.captureException(truncatedError);
}
const payload = {
status: truncatedError.status || 500,
body: {
@@ -195,6 +197,16 @@ export const supportedServiceTypesAndVersions = [
ports: {
main: 2368
}
},
{
name: 'meilisearch',
fancyName: 'Meilisearch',
baseImage: 'getmeili/meilisearch',
images: [],
versions: ['latest'],
ports: {
main: 7700
}
}
];

View File

@@ -22,7 +22,8 @@ export async function getService({ id, teamId }) {
vscodeserver: true,
wordpress: true,
ghost: true,
serviceSecret: true
serviceSecret: true,
meiliSearch: true
}
});
@@ -50,6 +51,8 @@ export async function getService({ id, teamId }) {
body.ghost.mariadbRootUserPassword = decrypt(body.ghost.mariadbRootUserPassword);
if (body.ghost?.defaultPassword) body.ghost.defaultPassword = decrypt(body.ghost.defaultPassword);
if (body.meiliSearch?.masterKey) body.meiliSearch.masterKey = decrypt(body.meiliSearch.masterKey);
if (body?.serviceSecret.length > 0) {
body.serviceSecret = body.serviceSecret.map((s) => {
s.value = decrypt(s.value);
@@ -165,6 +168,15 @@ export async function configureServiceType({ id, type }) {
}
}
});
} else if (type === 'meilisearch') {
const masterKey = encrypt(generatePassword(32));
await prisma.service.update({
where: { id },
data: {
type,
meiliSearch: { create: { masterKey } }
}
});
}
}
export async function setServiceVersion({ id, version }) {
@@ -191,6 +203,9 @@ export async function updateService({ id, fqdn, name }) {
export async function updateLanguageToolService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateMeiliSearchService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateVaultWardenService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
@@ -214,6 +229,7 @@ export async function updateGhostService({ id, fqdn, name, mariadbDatabase }) {
}
export async function removeService({ id }) {
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
await prisma.ghost.deleteMany({ where: { serviceId: id } });
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
await prisma.minio.deleteMany({ where: { serviceId: id } });

View File

@@ -88,12 +88,12 @@ export async function buildImage({
debug = false
}) {
if (isCache) {
saveBuildLog({ line: `Building cache image started.`, buildId, applicationId });
await saveBuildLog({ line: `Building cache image started.`, buildId, applicationId });
} else {
saveBuildLog({ line: `Building image started.`, buildId, applicationId });
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
}
if (!debug && isCache) {
saveBuildLog({
await saveBuildLog({
line: `Debug turned off. To see more details, allow it in the configuration.`,
buildId,
applicationId
@@ -126,13 +126,17 @@ export async function streamEvents({ stream, docker, buildId, applicationId, deb
if (err) reject(err);
resolve(res);
}
function onProgress(event) {
async function onProgress(event) {
if (event.error) {
reject(event.error);
} else if (event.stream) {
if (event.stream !== '\n') {
if (debug)
saveBuildLog({ line: `${event.stream.replace('\n', '')}`, buildId, applicationId });
await saveBuildLog({
line: `${event.stream.replace('\n', '')}`,
buildId,
applicationId
});
}
}
}

View File

@@ -175,7 +175,7 @@ export async function configureHAProxy() {
isRunning,
isHttps,
redirectValue,
redirectTo: isWWW ? domain : 'www.' + domain,
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
updatedAt: updatedAt.getTime()
});
}
@@ -199,7 +199,7 @@ export async function configureHAProxy() {
isRunning,
isHttps,
redirectValue,
redirectTo: isWWW ? previewDomain : 'www.' + previewDomain,
redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain,
updatedAt: updatedAt.getTime()
});
}
@@ -242,7 +242,7 @@ export async function configureHAProxy() {
isRunning,
isHttps,
redirectValue,
redirectTo: isWWW ? domain : 'www.' + domain,
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
updatedAt: updatedAt.getTime()
});
}
@@ -262,7 +262,7 @@ export async function configureHAProxy() {
domain,
isHttps,
redirectValue,
redirectTo: isWWW ? domain : 'www.' + domain
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain
});
}
const output = mustache.render(template, data);

View File

@@ -10,11 +10,14 @@ export default async function ({
workdir,
githubAppId,
repository,
apiUrl,
htmlUrl,
branch,
buildId
}): Promise<any> {
try {
saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
const url = htmlUrl.replace('https://', '').replace('http://', '');
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
const { privateKey, appId, installationId } = await db.getUniqueGithubApp({ githubAppId });
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
@@ -27,20 +30,20 @@ export default async function ({
algorithm: 'RS256'
});
const { token } = await got
.post(`https://api.github.com/app/installations/${installationId}/access_tokens`, {
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
headers: {
Authorization: `Bearer ${jwtToken}`,
Accept: 'application/vnd.github.machine-man-preview+json'
}
})
.json();
saveBuildLog({
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch.`,
buildId,
applicationId
});
await asyncExecShell(
`git clone -q -b ${branch} https://x-access-token:${token}@github.com/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && cd ..`
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && cd ..`
);
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
return commit.replace('\n', '');

View File

@@ -1,28 +1,28 @@
import { asyncExecShell, saveBuildLog } from '$lib/common';
import { ErrorHandler } from '$lib/database';
export default async function ({
applicationId,
debug,
workdir,
repodir,
htmlUrl,
repository,
branch,
buildId,
privateSshKey
}): Promise<any> {
saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
const url = htmlUrl.replace('https://', '').replace('http://', '');
await saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`);
await asyncExecShell(`chmod 600 ${repodir}/id.rsa`);
saveBuildLog({
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch.`,
buildId,
applicationId
});
await asyncExecShell(
`git clone -q -b ${branch} git@gitlab.com:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && cd ..`
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && cd ..`
);
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
return commit.replace('\n', '');

View File

@@ -51,7 +51,10 @@ export default async function (job) {
pullmergeRequestId = null,
sourceBranch = null,
settings,
persistentStorage
persistentStorage,
pythonWSGI,
pythonModule,
pythonVariable
} = job.data;
const { debug } = settings;
@@ -114,6 +117,7 @@ export default async function (job) {
branch,
buildId,
apiUrl: gitSource.apiUrl,
htmlUrl: gitSource.htmlUrl,
projectId,
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null
@@ -127,7 +131,7 @@ export default async function (job) {
}
try {
db.prisma.build.update({ where: { id: buildId }, data: { commit } });
await db.prisma.build.update({ where: { id: buildId }, data: { commit } });
} catch (err) {
console.log(err);
}
@@ -157,7 +161,7 @@ export default async function (job) {
});
deployNeeded = true;
if (configHash) {
saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
}
} else {
deployNeeded = false;
@@ -200,16 +204,19 @@ export default async function (job) {
startCommand,
baseDirectory,
secrets,
phpModules
phpModules,
pythonWSGI,
pythonModule,
pythonVariable
});
else {
saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
throw new Error(`Build pack ${buildPack} not found.`);
}
deployNeeded = true;
} else {
deployNeeded = false;
saveBuildLog({ line: 'Nothing changed.', buildId, applicationId });
await saveBuildLog({ line: 'Nothing changed.', buildId, applicationId });
}
// Deploy to Docker Engine
@@ -259,15 +266,7 @@ export default async function (job) {
//
}
try {
saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
// for await (const volume of volumes) {
// const id = volume.split(':')[0];
// try {
// await asyncExecShell(`DOCKER_HOST=${host} docker volume inspect ${id}`);
// } catch (error) {
// await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}`);
// }
// }
await saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
const composeVolumes = volumes.map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
@@ -300,19 +299,12 @@ export default async function (job) {
await asyncExecShell(
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
);
// const { stderr } = await asyncExecShell(
// `DOCKER_HOST=${host} docker run ${envFound && `--env-file=${workdir}/.env`} ${labels.join(
// ' '
// )} --name ${imageId} --network ${docker.network} --restart always ${volumes.length > 0 ? volumes : ''
// } -d ${applicationId}:${tag}`
// );
saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
} catch (error) {
saveBuildLog({ line: error, buildId, applicationId });
await saveBuildLog({ line: error, buildId, applicationId });
sentry.captureException(error);
throw new Error(error);
}
saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
}
}

View File

@@ -135,12 +135,12 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
const workdir = `/tmp/build-sources/${job.data.repository}`;
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
}
saveBuildLog({
await saveBuildLog({
line: 'Failed to deploy!',
buildId: job.data.build_id,
applicationId: job.data.id
});
saveBuildLog({
await saveBuildLog({
line: `Reason: ${failedReason.toString()}`,
buildId: job.data.build_id,
applicationId: job.data.id