Merge pull request #347 from coollabsio/v2.4.9

v2.4.9
This commit is contained in:
Andras Bacsai 2022-04-14 23:29:15 +02:00 committed by GitHub
commit 47aad15cd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 171 additions and 72 deletions

View File

@ -1,7 +1,7 @@
{
"name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "2.4.8",
"version": "2.4.9",
"license": "AGPL-3.0",
"scripts": {
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev",

View File

@ -9,6 +9,7 @@ import { default as ProdPrisma } from '@prisma/client';
import type { Database, DatabaseSettings } from '@prisma/client';
import generator from 'generate-password';
import forge from 'node-forge';
import getPort, { portNumbers } from 'get-port';
export function generatePassword(length = 24): string {
return generator.generate({
@ -251,3 +252,29 @@ export function generateDatabaseConfiguration(database: Database & { settings: D
};
}
}
export async function getFreePort() {
const data = await prisma.setting.findFirst();
const { minPort, maxPort } = data;
const dbUsed = await (
await prisma.database.findMany({
where: { publicPort: { not: null } },
select: { publicPort: true }
})
).map((a) => a.publicPort);
const wpFtpUsed = await (
await prisma.wordpress.findMany({
where: { ftpPublicPort: { not: null } },
select: { ftpPublicPort: true }
})
).map((a) => a.ftpPublicPort);
const wpUsed = await (
await prisma.wordpress.findMany({
where: { mysqlPublicPort: { not: null } },
select: { mysqlPublicPort: true }
})
).map((a) => a.mysqlPublicPort);
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed];
return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
}

View File

@ -127,10 +127,10 @@ export async function startTcpProxy(
const containerName = `haproxy-for-${publicPort}`;
const found = await checkContainer(engine, containerName);
const foundDB = await checkContainer(engine, id);
const foundDependentContainer = await checkContainer(engine, id);
try {
if (foundDB && !found) {
if (foundDependentContainer && !found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
@ -141,6 +141,11 @@ export async function startTcpProxy(
} -d coollabsio/${defaultProxyImageTcp}`
);
}
if (!foundDependentContainer && found) {
return await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
);
}
} catch (error) {
return error;
}
@ -157,10 +162,10 @@ export async function startHttpProxy(
const containerName = `haproxy-for-${publicPort}`;
const found = await checkContainer(engine, containerName);
const foundDB = await checkContainer(engine, id);
const foundDependentContainer = await checkContainer(engine, id);
try {
if (foundDB && !found) {
if (foundDependentContainer && !found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
@ -169,6 +174,11 @@ export async function startHttpProxy(
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}`
);
}
if (!foundDependentContainer && found) {
return await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
);
}
} catch (error) {
return error;
}

View File

@ -2,8 +2,9 @@ import { asyncExecShell, getEngine, version } from '$lib/common';
import { prisma } from '$lib/database';
export default async function (): Promise<void> {
const destinationDockers = await prisma.destinationDocker.findMany();
for (const destinationDocker of destinationDockers) {
const host = getEngine(destinationDocker.engine);
const engines = [...new Set(destinationDockers.map(({ engine }) => engine))];
for (const engine of engines) {
const host = getEngine(engine);
// Cleanup old coolify images
try {
let { stdout: images } = await asyncExecShell(
@ -28,7 +29,7 @@ export default async function (): Promise<void> {
}
// Cleanup old images older than a day
try {
await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=24h" -a -f`);
await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
} catch (error) {
//console.log(error);
}

View File

@ -7,6 +7,7 @@ import builder from './builder';
import logger from './logger';
import cleanup from './cleanup';
import proxy from './proxy';
import proxyTcpHttp from './proxyTcpHttp';
import ssl from './ssl';
import sslrenewal from './sslrenewal';
@ -29,17 +30,20 @@ const connectionOptions = {
const cron = async (): Promise<void> => {
new QueueScheduler('proxy', connectionOptions);
new QueueScheduler('proxyTcpHttp', connectionOptions);
new QueueScheduler('cleanup', connectionOptions);
new QueueScheduler('ssl', connectionOptions);
new QueueScheduler('sslRenew', connectionOptions);
const queue = {
proxy: new Queue('proxy', { ...connectionOptions }),
proxyTcpHttp: new Queue('proxyTcpHttp', { ...connectionOptions }),
cleanup: new Queue('cleanup', { ...connectionOptions }),
ssl: new Queue('ssl', { ...connectionOptions }),
sslRenew: new Queue('sslRenew', { ...connectionOptions })
};
await queue.proxy.drain();
await queue.proxyTcpHttp.drain();
await queue.cleanup.drain();
await queue.ssl.drain();
await queue.sslRenew.drain();
@ -54,6 +58,16 @@ const cron = async (): Promise<void> => {
}
);
new Worker(
'proxyTcpHttp',
async () => {
await proxyTcpHttp();
},
{
...connectionOptions
}
);
new Worker(
'ssl',
async () => {
@ -85,6 +99,7 @@ const cron = async (): Promise<void> => {
);
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
await queue.proxyTcpHttp.add('proxyTcpHttp', {}, { repeat: { every: 10000 } });
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });

View File

@ -0,0 +1,55 @@
import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database';
import { startCoolifyProxy, startHttpProxy, startTcpProxy } from '$lib/haproxy';
export default async function (): Promise<void | {
status: number;
body: { message: string; error: string };
}> {
try {
// Coolify Proxy
const localDocker = await prisma.destinationDocker.findFirst({
where: { engine: '/var/run/docker.sock' }
});
if (localDocker && localDocker.isCoolifyProxyUsed) {
await startCoolifyProxy('/var/run/docker.sock');
}
// TCP Proxies
const databasesWithPublicPort = await prisma.database.findMany({
where: { publicPort: { not: null } },
include: { settings: true, destinationDocker: true }
});
for (const database of databasesWithPublicPort) {
const { destinationDockerId, destinationDocker, publicPort, id } = database;
if (destinationDockerId) {
const { privatePort } = generateDatabaseConfiguration(database);
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
}
}
const wordpressWithFtp = await prisma.wordpress.findMany({
where: { ftpPublicPort: { not: null } },
include: { service: { include: { destinationDocker: true } } }
});
for (const ftp of wordpressWithFtp) {
const { service, ftpPublicPort } = ftp;
const { destinationDockerId, destinationDocker, id } = service;
if (destinationDockerId) {
await startTcpProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22);
}
}
// HTTP Proxies
const minioInstances = await prisma.minio.findMany({
where: { publicPort: { not: null } },
include: { service: { include: { destinationDocker: true } } }
});
for (const minio of minioInstances) {
const { service, publicPort } = minio;
const { destinationDockerId, destinationDocker, id } = service;
if (destinationDockerId) {
await startHttpProxy(destinationDocker, id, publicPort, 9000);
}
}
} catch (error) {
return ErrorHandler(error.response?.body || error);
}
}

View File

@ -29,10 +29,12 @@
disabled={!isRunning}
readonly={!isRunning}
placeholder="Generated automatically after start"
isPasswordField
id="rootUserPassword"
name="rootUserPassword"
bind:value={database.rootUserPassword}
/>
<Explainer text="Could be changed while the database is running." />
</div>
<div class="grid grid-cols-2 items-center">
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>

View File

@ -1,7 +1,7 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler, stopDatabase } from '$lib/database';
import { deleteProxy } from '$lib/haproxy';
import { stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const del: RequestHandler = async (event) => {
@ -12,7 +12,7 @@ export const del: RequestHandler = async (event) => {
const database = await db.getDatabase({ id, teamId });
if (database.destinationDockerId) {
const everStarted = await stopDatabase(database);
if (everStarted) await deleteProxy({ id });
if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort);
}
await db.removeDatabase({ id });
return { status: 200 };

View File

@ -1,20 +1,16 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { generateDatabaseConfiguration, ErrorHandler } from '$lib/database';
import { generateDatabaseConfiguration, ErrorHandler, getFreePort } from '$lib/database';
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
import getPort, { portNumbers } from 'get-port';
export const post: RequestHandler = async (event) => {
const { status, body, teamId } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const { isPublic, appendOnly = true } = await event.request.json();
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
const publicPort = await getFreePort();
try {
await db.setDatabase({ id, isPublic, appendOnly });

View File

@ -13,20 +13,25 @@ export const get: RequestHandler = async (event) => {
select: { id: true, email: true, teams: true }
});
let accounts = [];
let allTeams = [];
if (teamId === '0') {
accounts = await db.prisma.user.findMany({ select: { id: true, email: true, teams: true } });
allTeams = await db.prisma.team.findMany({
where: { users: { none: { id: userId } } },
include: { permissions: true }
});
}
const teams = await db.prisma.permission.findMany({
where: { userId: teamId === '0' ? undefined : userId },
include: { team: { include: { _count: { select: { users: true } } } } }
const ownTeams = await db.prisma.team.findMany({
where: { users: { some: { id: userId } } },
include: { permissions: true }
});
const invitations = await db.prisma.teamInvitation.findMany({ where: { uid: userId } });
return {
status: 200,
body: {
teams,
ownTeams,
allTeams,
invitations,
account,
accounts

View File

@ -36,18 +36,8 @@
if (accounts.length === 0) {
accounts.push(account);
}
export let teams;
const ownTeams = teams.filter((team) => {
if (team.team.id === $session.teamId) {
return team;
}
});
const otherTeams = teams.filter((team) => {
if (team.team.id !== $session.teamId) {
return team;
}
});
export let ownTeams;
export let allTeams;
async function resetPassword(id) {
const sure = window.confirm('Are you sure you want to reset the password?');
@ -167,49 +157,51 @@
<div class="title font-bold">Teams</div>
<div class="flex items-center justify-center pt-10">
<div class="flex flex-col">
<div class="flex flex-col flex-wrap justify-center px-2 pb-10 md:flex-row">
<div class="flex flex-row flex-wrap justify-center px-2 pb-10 md:flex-row">
{#each ownTeams as team}
<a href="/iam/team/{team.teamId}" class="w-96 p-2 no-underline">
<a href="/iam/team/{team.id}" class="w-96 p-2 no-underline">
<div
class="box-selection relative"
class:hover:bg-cyan-600={team.team?.id !== '0'}
class:hover:bg-red-500={team.team?.id === '0'}
class:hover:bg-cyan-600={team.id !== '0'}
class:hover:bg-red-500={team.id === '0'}
>
<div class="truncate text-center text-xl font-bold">
{team.team.name}
{team.name}
</div>
<div class="truncate text-center font-bold">
{team.team?.id === '0' ? 'root team' : ''}
{team.id === '0' ? 'root team' : ''}
</div>
<div class="mt-1 text-center">{team.team._count.users} member(s)</div>
<div class:mt-6={team.id !== '0'} class="mt-1 text-center">
{team.permissions?.length} member(s)
</div>
</div>
</a>
{/each}
</div>
{#if $session.teamId === '0' && otherTeams.length > 0}
{#if $session.teamId === '0' && allTeams.length > 0}
<div class="pb-5 pt-10 text-xl font-bold">Other Teams</div>
{/if}
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
{#each otherTeams as team}
<a href="/iam/team/{team.teamId}" class="w-96 p-2 no-underline">
<div
class="box-selection relative"
class:hover:bg-cyan-600={team.team?.id !== '0'}
class:hover:bg-red-500={team.team?.id === '0'}
>
<div class="truncate text-center text-xl font-bold">
{team.team.name}
</div>
<div class="truncate text-center font-bold">
{team.team?.id === '0' ? 'root team' : ''}
</div>
<div class="flex flex-row flex-wrap justify-center px-2 md:flex-row">
{#each allTeams as team}
<a href="/iam/team/{team.id}" class="w-96 p-2 no-underline">
<div
class="box-selection relative"
class:hover:bg-cyan-600={team.id !== '0'}
class:hover:bg-red-500={team.id === '0'}
>
<div class="truncate text-center text-xl font-bold">
{team.name}
</div>
<div class="truncate text-center font-bold">
{team.id === '0' ? 'root team' : ''}
</div>
<div class="mt-1 text-center">{team.team._count.users} member(s)</div>
</div>
</a>
{/each}
</div>
<div class="mt-1 text-center">{team.permissions?.length} member(s)</div>
</div>
</a>
{/each}
</div>
{/if}
</div>
</div>
</div>

View File

@ -11,7 +11,9 @@
let loading = false;
async function handleSubmit() {
if (loading) return;
try {
loading = true;
await post('/new/destination/check.json', { network: payload.network });
const { id } = await post('/new/destination/docker.json', {
...payload

View File

@ -4,9 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { startHttpProxy } from '$lib/haproxy';
import getPort, { portNumbers } from 'get-port';
import { getDomain } from '$lib/components/common';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { ErrorHandler, getFreePort, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
import type { ComposeFile } from '$lib/types/composeFile';
@ -28,13 +26,10 @@ export const post: RequestHandler = async (event) => {
serviceSecret
} = service;
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
const publicPort = await getFreePort();
const consolePort = 9001;
const apiPort = 9000;

View File

@ -2,7 +2,7 @@ import { dev } from '$app/env';
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
import { decrypt, encrypt } from '$lib/crypto';
import * as db from '$lib/database';
import { generateDatabaseConfiguration, ErrorHandler, generatePassword } from '$lib/database';
import { ErrorHandler, generatePassword, getFreePort } from '$lib/database';
import { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
import type { ComposeFile } from '$lib/types/composeFile';
import type { RequestHandler } from '@sveltejs/kit';
@ -16,11 +16,10 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body };
const { id } = event.params;
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const { ftpEnabled } = await event.request.json();
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
const publicPort = await getFreePort();
let ftpUser = cuid();
let ftpPassword = generatePassword();