Merge pull request #1081 from coollabsio/next

v3.12.32
This commit is contained in:
Andras Bacsai
2023-05-25 09:14:17 +02:00
committed by GitHub
7 changed files with 236 additions and 161 deletions

View File

@@ -604,53 +604,54 @@ async function cleanupStorage() {
if (!destination.remoteVerified) continue; if (!destination.remoteVerified) continue;
enginesDone.add(destination.remoteIpAddress); enginesDone.add(destination.remoteIpAddress);
} }
let lowDiskSpace = false; await cleanupDockerStorage(destination.id);
try { // let lowDiskSpace = false;
let stdout = null; // try {
if (!isDev) { // let stdout = null;
const output = await executeCommand({ // if (!isDev) {
dockerId: destination.id, // const output = await executeCommand({
command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`, // dockerId: destination.id,
shell: true // command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
}); // shell: true
stdout = output.stdout; // });
} else { // stdout = output.stdout;
const output = await executeCommand({ // } else {
command: `df -kPT /` // const output = await executeCommand({
}); // command: `df -kPT /`
stdout = output.stdout; // });
} // stdout = output.stdout;
let lines = stdout.trim().split('\n'); // }
let header = lines[0]; // let lines = stdout.trim().split('\n');
let regex = // let header = lines[0];
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g; // let regex =
const boundaries = []; // /^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
let match; // const boundaries = [];
// let match;
while ((match = regex.exec(header))) { // while ((match = regex.exec(header))) {
boundaries.push(match[0].length); // boundaries.push(match[0].length);
} // }
boundaries[boundaries.length - 1] = -1; // boundaries[boundaries.length - 1] = -1;
const data = lines.slice(1).map((line) => { // const data = lines.slice(1).map((line) => {
const cl = boundaries.map((boundary) => { // const cl = boundaries.map((boundary) => {
const column = boundary > 0 ? line.slice(0, boundary) : line; // const column = boundary > 0 ? line.slice(0, boundary) : line;
line = line.slice(boundary); // line = line.slice(boundary);
return column.trim(); // return column.trim();
}); // });
return { // return {
capacity: Number.parseInt(cl[5], 10) / 100 // capacity: Number.parseInt(cl[5], 10) / 100
}; // };
}); // });
if (data.length > 0) { // if (data.length > 0) {
const { capacity } = data[0]; // const { capacity } = data[0];
if (capacity > 0.8) { // if (capacity > 0.8) {
lowDiskSpace = true; // lowDiskSpace = true;
} // }
} // }
} catch (error) {} // } catch (error) {}
if (lowDiskSpace) { // if (lowDiskSpace) {
await cleanupDockerStorage(destination.id); // await cleanupDockerStorage(destination.id);
} // }
} }
} }

View File

@@ -19,7 +19,7 @@ import { saveBuildLog } from './buildPacks/common';
import { scheduler } from './scheduler'; import { scheduler } from './scheduler';
import type { ExecaChildProcess } from 'execa'; import type { ExecaChildProcess } from 'execa';
export const version = '3.12.31'; export const version = '3.12.32';
export const isDev = process.env.NODE_ENV === 'development'; export const isDev = process.env.NODE_ENV === 'development';
export const proxyPort = process.env.COOLIFY_PROXY_PORT; export const proxyPort = process.env.COOLIFY_PROXY_PORT;
export const proxySecurePort = process.env.COOLIFY_PROXY_SECURE_PORT; export const proxySecurePort = process.env.COOLIFY_PROXY_SECURE_PORT;
@@ -579,7 +579,8 @@ export async function executeCommand({
stream = false, stream = false,
buildId, buildId,
applicationId, applicationId,
debug debug,
timeout = 0
}: { }: {
command: string; command: string;
sshCommand?: boolean; sshCommand?: boolean;
@@ -589,6 +590,7 @@ export async function executeCommand({
buildId?: string; buildId?: string;
applicationId?: string; applicationId?: string;
debug?: boolean; debug?: boolean;
timeout?: number;
}): Promise<ExecaChildProcess<string>> { }): Promise<ExecaChildProcess<string>> {
const { execa, execaCommand } = await import('execa'); const { execa, execaCommand } = await import('execa');
const { parse } = await import('shell-quote'); const { parse } = await import('shell-quote');
@@ -613,20 +615,26 @@ export async function executeCommand({
} }
if (sshCommand) { if (sshCommand) {
if (shell) { if (shell) {
return execaCommand(`ssh ${remoteIpAddress}-remote ${command}`); return execaCommand(`ssh ${remoteIpAddress}-remote ${command}`, {
timeout
});
} }
return await execa('ssh', [`${remoteIpAddress}-remote`, dockerCommand, ...dockerArgs]); return await execa('ssh', [`${remoteIpAddress}-remote`, dockerCommand, ...dockerArgs], {
timeout
});
} }
if (stream) { if (stream) {
return await new Promise(async (resolve, reject) => { return await new Promise(async (resolve, reject) => {
let subprocess = null; let subprocess = null;
if (shell) { if (shell) {
subprocess = execaCommand(command, { subprocess = execaCommand(command, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine } env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
timeout
}); });
} else { } else {
subprocess = execa(dockerCommand, dockerArgs, { subprocess = execa(dockerCommand, dockerArgs, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine } env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
timeout
}); });
} }
const logs = []; const logs = [];
@@ -680,19 +688,26 @@ export async function executeCommand({
} else { } else {
if (shell) { if (shell) {
return await execaCommand(command, { return await execaCommand(command, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine } env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
timeout
}); });
} else { } else {
return await execa(dockerCommand, dockerArgs, { return await execa(dockerCommand, dockerArgs, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine } env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
timeout
}); });
} }
} }
} else { } else {
if (shell) { if (shell) {
return execaCommand(command, { shell: true }); return execaCommand(command, {
shell: true,
timeout
});
} }
return await execa(dockerCommand, dockerArgs); return await execa(dockerCommand, dockerArgs, {
timeout
});
} }
} }
@@ -849,97 +864,97 @@ export function generatePassword({
type DatabaseConfiguration = type DatabaseConfiguration =
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
MYSQL_DATABASE: string; MYSQL_DATABASE: string;
MYSQL_PASSWORD: string; MYSQL_PASSWORD: string;
MYSQL_ROOT_USER: string; MYSQL_ROOT_USER: string;
MYSQL_USER: string; MYSQL_USER: string;
MYSQL_ROOT_PASSWORD: string; MYSQL_ROOT_PASSWORD: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
MONGO_INITDB_ROOT_USERNAME?: string; MONGO_INITDB_ROOT_USERNAME?: string;
MONGO_INITDB_ROOT_PASSWORD?: string; MONGO_INITDB_ROOT_PASSWORD?: string;
MONGODB_ROOT_USER?: string; MONGODB_ROOT_USER?: string;
MONGODB_ROOT_PASSWORD?: string; MONGODB_ROOT_PASSWORD?: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
MARIADB_ROOT_USER: string; MARIADB_ROOT_USER: string;
MARIADB_ROOT_PASSWORD: string; MARIADB_ROOT_PASSWORD: string;
MARIADB_USER: string; MARIADB_USER: string;
MARIADB_PASSWORD: string; MARIADB_PASSWORD: string;
MARIADB_DATABASE: string; MARIADB_DATABASE: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
POSTGRES_PASSWORD?: string; POSTGRES_PASSWORD?: string;
POSTGRES_USER?: string; POSTGRES_USER?: string;
POSTGRES_DB?: string; POSTGRES_DB?: string;
POSTGRESQL_POSTGRES_PASSWORD?: string; POSTGRESQL_POSTGRES_PASSWORD?: string;
POSTGRESQL_USERNAME?: string; POSTGRESQL_USERNAME?: string;
POSTGRESQL_PASSWORD?: string; POSTGRESQL_PASSWORD?: string;
POSTGRESQL_DATABASE?: string; POSTGRESQL_DATABASE?: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
REDIS_AOF_ENABLED: string; REDIS_AOF_ENABLED: string;
REDIS_PASSWORD: string; REDIS_PASSWORD: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
COUCHDB_PASSWORD: string; COUCHDB_PASSWORD: string;
COUCHDB_USER: string; COUCHDB_USER: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
EDGEDB_SERVER_PASSWORD: string; EDGEDB_SERVER_PASSWORD: string;
EDGEDB_SERVER_USER: string; EDGEDB_SERVER_USER: string;
EDGEDB_SERVER_DATABASE: string; EDGEDB_SERVER_DATABASE: string;
EDGEDB_SERVER_TLS_CERT_MODE: string; EDGEDB_SERVER_TLS_CERT_MODE: string;
}; };
}; };
export function generateDatabaseConfiguration(database: any): DatabaseConfiguration { export function generateDatabaseConfiguration(database: any): DatabaseConfiguration {
const { id, dbUser, dbUserPassword, rootUser, rootUserPassword, defaultDatabase, version, type } = const { id, dbUser, dbUserPassword, rootUser, rootUserPassword, defaultDatabase, version, type } =
database; database;
@@ -1038,9 +1053,8 @@ export function generateDatabaseConfiguration(database: any): DatabaseConfigurat
}; };
if (isARM()) { if (isARM()) {
configuration.volume = `${id}-${type}-data:/data`; configuration.volume = `${id}-${type}-data:/data`;
configuration.command = `/usr/local/bin/redis-server --appendonly ${ configuration.command = `/usr/local/bin/redis-server --appendonly ${appendOnly ? 'yes' : 'no'
appendOnly ? 'yes' : 'no' } --requirepass ${dbUserPassword}`;
} --requirepass ${dbUserPassword}`;
} }
return configuration; return configuration;
} else if (type === 'couchdb') { } else if (type === 'couchdb') {
@@ -1125,12 +1139,12 @@ export type ComposeFileService = {
command?: string; command?: string;
ports?: string[]; ports?: string[];
build?: build?:
| { | {
context: string; context: string;
dockerfile: string; dockerfile: string;
args?: Record<string, unknown>; args?: Record<string, unknown>;
} }
| string; | string;
deploy?: { deploy?: {
restart_policy?: { restart_policy?: {
condition?: string; condition?: string;
@@ -1201,7 +1215,7 @@ export const createDirectories = async ({
let workdirFound = false; let workdirFound = false;
try { try {
workdirFound = !!(await fs.stat(workdir)); workdirFound = !!(await fs.stat(workdir));
} catch (error) {} } catch (error) { }
if (workdirFound) { if (workdirFound) {
await executeCommand({ command: `rm -fr ${workdir}` }); await executeCommand({ command: `rm -fr ${workdir}` });
} }
@@ -1728,7 +1742,7 @@ export async function stopBuild(buildId, applicationId) {
} }
} }
count++; count++;
} catch (error) {} } catch (error) { }
}, 100); }, 100);
}); });
} }
@@ -1751,7 +1765,7 @@ export async function cleanupDockerStorage(dockerId) {
// Cleanup images that are not used by any container // Cleanup images that are not used by any container
try { try {
await executeCommand({ dockerId, command: `docker image prune -af` }); await executeCommand({ dockerId, command: `docker image prune -af` });
} catch (error) {} } catch (error) { }
// Prune coolify managed containers // Prune coolify managed containers
try { try {
@@ -1759,12 +1773,12 @@ export async function cleanupDockerStorage(dockerId) {
dockerId, dockerId,
command: `docker container prune -f --filter "label=coolify.managed=true"` command: `docker container prune -f --filter "label=coolify.managed=true"`
}); });
} catch (error) {} } catch (error) { }
// Cleanup build caches // Cleanup build caches
try { try {
await executeCommand({ dockerId, command: `docker builder prune -af` }); await executeCommand({ dockerId, command: `docker builder prune -af` });
} catch (error) {} } catch (error) { }
} }
export function persistentVolumes(id, persistentStorage, config) { export function persistentVolumes(id, persistentStorage, config) {

View File

@@ -640,8 +640,7 @@ export async function restartApplication(
const volumes = const volumes =
persistentStorage?.map((storage) => { persistentStorage?.map((storage) => {
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : '' return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
}${storage.path}`;
}) || []; }) || [];
const composeVolumes = volumes.map((volume) => { const composeVolumes = volumes.map((volume) => {
return { return {

View File

@@ -18,6 +18,7 @@ import type {
Proxy, Proxy,
SaveDestinationSettings SaveDestinationSettings
} from './types'; } from './types';
import { removeService } from '../../../../lib/services/common';
export async function listDestinations(request: FastifyRequest<ListDestinations>) { export async function listDestinations(request: FastifyRequest<ListDestinations>) {
try { try {
@@ -143,6 +144,35 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
return errorHandler({ status, message }); return errorHandler({ status, message });
} }
} }
export async function forceDeleteDestination(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params;
const services = await prisma.service.findMany({ where: { destinationDockerId: id } });
for (const service of services) {
await removeService({ id: service.id });
}
const applications = await prisma.application.findMany({ where: { destinationDockerId: id } });
for (const application of applications) {
await prisma.applicationSettings.deleteMany({ where: { application: { id: application.id } } });
await prisma.buildLog.deleteMany({ where: { applicationId: application.id } });
await prisma.build.deleteMany({ where: { applicationId: application.id } });
await prisma.secret.deleteMany({ where: { applicationId: application.id } });
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: application.id } });
await prisma.applicationConnectedDatabase.deleteMany({ where: { applicationId: application.id } });
await prisma.previewApplication.deleteMany({ where: { applicationId: application.id } });
}
const databases = await prisma.database.findMany({ where: { destinationDockerId: id } });
for (const database of databases) {
await prisma.databaseSettings.deleteMany({ where: { databaseId: database.id } });
await prisma.databaseSecret.deleteMany({ where: { databaseId: database.id } });
await prisma.database.delete({ where: { id: database.id } });
}
await prisma.destinationDocker.delete({ where: { id } });
return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function deleteDestination(request: FastifyRequest<OnlyId>) { export async function deleteDestination(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params; const { id } = request.params;
@@ -318,6 +348,7 @@ export async function verifyRemoteDockerEngineFn(id: string) {
} }
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } }); await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } });
} catch (error) { } catch (error) {
console.log(error)
throw new Error('Error while verifying remote docker engine'); throw new Error('Error while verifying remote docker engine');
} }
} }

View File

@@ -1,5 +1,5 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { assignSSHKey, checkDestination, deleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers'; import { assignSSHKey, checkDestination, deleteDestination, forceDeleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types'; import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
@@ -14,6 +14,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request)); fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply)); fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request)); fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
fastify.delete<OnlyId>('/:id/force', async (request) => await forceDeleteDestination(request));
fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request)); fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request));
fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request)); fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));

View File

@@ -75,6 +75,25 @@
} }
} }
} }
async function forceDeleteDestination(destination: any) {
let sure = confirm($t('application.confirm_to_delete', { name: destination.name }));
if (sure) {
sure = confirm(
'Are you REALLY sure? This will delete all resources associated with this destination, but not on the destination (server) itself. You will have manually delete everything on the server afterwards.'
);
if (sure) {
sure = confirm('REALLY?');
if (sure) {
try {
await del(`/destinations/${destination.id}/force`, { id: destination.id });
return await goto('/', { replaceState: true });
} catch (error) {
return errorNotification(error);
}
}
}
}
}
function deletable() { function deletable() {
if (!isDestinationDeletable) { if (!isDestinationDeletable) {
return 'Please delete all resources before deleting this.'; return 'Please delete all resources before deleting this.';
@@ -88,7 +107,7 @@
</script> </script>
{#if $page.params.id !== 'new'} {#if $page.params.id !== 'new'}
<nav class="header lg:flex-row flex-col-reverse"> <nav class="header lg:flex-row flex-col-reverse gap-2">
<div class="flex flex-row space-x-2 font-bold pt-10 lg:pt-0"> <div class="flex flex-row space-x-2 font-bold pt-10 lg:pt-0">
<div class="flex flex-col items-center justify-center title"> <div class="flex flex-col items-center justify-center title">
{#if $page.url.pathname === `/destinations/${$page.params.id}`} {#if $page.url.pathname === `/destinations/${$page.params.id}`}
@@ -111,6 +130,16 @@
> >
<Tooltip triggeredBy="#delete">{deletable()}</Tooltip> <Tooltip triggeredBy="#delete">{deletable()}</Tooltip>
</div> </div>
<div class="flex flex-row flex-wrap justify-center lg:justify-start lg:py-0 items-center">
<button
id="forceDelete"
on:click={() => forceDeleteDestination(destination)}
type="submit"
disabled={!$appSession.isAdmin && isDestinationDeletable}
class="icons bg-transparent text-sm text-red-500"><DeleteIcon /></button
>
<Tooltip triggeredBy="#forceDelete">Force Delete</Tooltip>
</div>
</nav> </nav>
{/if} {/if}
<slot /> <slot />

View File

@@ -1,7 +1,7 @@
{ {
"name": "coolify", "name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.", "description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "3.12.31", "version": "3.12.32",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": "github:coollabsio/coolify", "repository": "github:coollabsio/coolify",
"scripts": { "scripts": {
@@ -50,4 +50,4 @@
"open-source", "open-source",
"coolify" "coolify"
] ]
} }