feat: WP could have custom db

This commit is contained in:
Andras Bacsai 2022-05-10 10:12:13 +02:00
parent ede37d296b
commit ce52608f19
19 changed files with 480 additions and 258 deletions

View File

@ -0,0 +1,32 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Wordpress" (
"id" TEXT NOT NULL PRIMARY KEY,
"extraConfig" TEXT,
"tablePrefix" TEXT,
"ownMysql" BOOLEAN NOT NULL DEFAULT false,
"mysqlHost" TEXT,
"mysqlPort" INTEGER,
"mysqlUser" TEXT NOT NULL,
"mysqlPassword" TEXT NOT NULL,
"mysqlRootUser" TEXT NOT NULL,
"mysqlRootUserPassword" TEXT NOT NULL,
"mysqlDatabase" TEXT,
"mysqlPublicPort" INTEGER,
"ftpEnabled" BOOLEAN NOT NULL DEFAULT false,
"ftpUser" TEXT,
"ftpPassword" TEXT,
"ftpPublicPort" INTEGER,
"ftpHostKey" TEXT,
"ftpHostKeyPrivate" TEXT,
"serviceId" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Wordpress_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Wordpress" ("createdAt", "extraConfig", "ftpEnabled", "ftpHostKey", "ftpHostKeyPrivate", "ftpPassword", "ftpPublicPort", "ftpUser", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt") SELECT "createdAt", "extraConfig", "ftpEnabled", "ftpHostKey", "ftpHostKeyPrivate", "ftpPassword", "ftpPublicPort", "ftpUser", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt" FROM "Wordpress";
DROP TABLE "Wordpress";
ALTER TABLE "new_Wordpress" RENAME TO "Wordpress";
CREATE UNIQUE INDEX "Wordpress_serviceId_key" ON "Wordpress"("serviceId");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@ -353,6 +353,9 @@ model Wordpress {
id String @id @default(cuid())
extraConfig String?
tablePrefix String?
ownMysql Boolean @default(false)
mysqlHost String?
mysqlPort Int?
mysqlUser String
mysqlPassword String
mysqlRootUser String

View File

@ -4,6 +4,6 @@
<img
alt="plausible logo"
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-6 mx-auto'}
class={isAbsolute ? 'w-9 absolute top-0 left-0 -m-4' : 'w-6 mx-auto'}
src="/plausible.png"
/>

View File

@ -28,7 +28,7 @@ if (!dev) {
}
export const prisma = new PrismaClient({
errorFormat: 'pretty',
errorFormat: 'minimal',
rejectOnNotFound: false
});

View File

@ -419,7 +419,9 @@ export async function updateWordpress({
name,
exposePort,
mysqlDatabase,
extraConfig
extraConfig,
mysqlHost,
mysqlPort
}: {
id: string;
fqdn: string;
@ -427,10 +429,24 @@ export async function updateWordpress({
exposePort?: number;
mysqlDatabase: string;
extraConfig: string;
mysqlHost?: string;
mysqlPort?: number;
}): Promise<Service> {
return await prisma.service.update({
where: { id },
data: { fqdn, name, exposePort, wordpress: { update: { mysqlDatabase, extraConfig } } }
data: {
fqdn,
name,
exposePort,
wordpress: {
update: {
mysqlDatabase,
extraConfig,
mysqlHost,
mysqlPort
}
}
}
});
}

View File

@ -60,7 +60,7 @@
</div>
{/if}
</div>
<div class="flex flex-col flex-wrap justify-center">
<div class="flex justify-center">
{#if !applications || ownApplications.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>

View File

@ -47,7 +47,7 @@
</div>
</div>
<div class="flex flex-col flex-wrap justify-center">
<div class="flex justify-center">
{#if !databases || ownDatabases.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>

View File

@ -39,10 +39,13 @@
import { t } from '$lib/translations';
</script>
<div class="flex space-x-1 p-6 text-2xl font-bold">
<div class="tracking-tight">{$t('application.destination')}</div>
<span class="arrow-right-applications px-1">></span>
<span class="pr-2">{destination.name}</span>
<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Configuration
</div>
<span class="text-xs">{destination.name}</span>
</div>
</div>
<div class="mx-auto max-w-4xl px-6">

View File

@ -141,21 +141,21 @@
<div class="title font-bold">Server Usage</div>
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
<Loading />
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-white">Total Memory</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{(usage?.memory.totalMemMb).toFixed(0)}
</dd>
</div>
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-white">Used Memory</dt>
<dd class="mt-1 text-3xl font-semibold text-white ">
{(usage?.memory.usedMemMb).toFixed(0)}
</dd>
</div>
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6" class:bg-red-500={memoryWarning}>
<div class="overflow-hidden rounded px-4 py-5 sm:p-6" class:bg-red-500={memoryWarning}>
<dt class="truncate text-sm font-medium text-white">Free Memory</dt>
<dd class="mt-1 flex items-center text-3xl font-semibold text-white">
{usage?.memory.freeMemPercentage}%
@ -166,19 +166,19 @@
</div>
</dl>
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-white">Total CPUs</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{usage?.cpu.count}
</dd>
</div>
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-white">Load Average (5/10/30mins)</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{usage?.cpu.load.join('/')}
</dd>
</div>
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6" class:bg-red-500={cpuWarning}>
<div class="overflow-hidden rounded px-4 py-5 sm:p-6" class:bg-red-500={cpuWarning}>
<dt class="truncate text-sm font-medium text-white">CPU Usage</dt>
<dd class="mt-1 flex items-center text-3xl font-semibold text-white">
{usage?.cpu.usage}%
@ -189,19 +189,19 @@
</div>
</dl>
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-white">Total Disk</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{usage?.disk.totalGb}GB
</dd>
</div>
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6">
<div class="overflow-hidden rounded px-4 py-5 sm:p-6">
<dt class="truncate text-sm font-medium text-white">Used Disk</dt>
<dd class="mt-1 text-3xl font-semibold text-white">
{usage?.disk.usedGb}GB
</dd>
</div>
<div class="overflow-hidden rounded-lg px-4 py-5 sm:p-6" class:bg-red-500={diskWarning}>
<div class="overflow-hidden rounded px-4 py-5 sm:p-6" class:bg-red-500={diskWarning}>
<dt class="truncate text-sm font-medium text-white">Free Disk</dt>
<dd class="mt-1 flex items-center text-3xl font-semibold text-white">
{usage?.disk.freePercentage}%
@ -217,7 +217,7 @@
<a
href="/applications"
sveltekit:prefetch
class="overflow-hidden rounded-lg px-4 py-5 text-green-500 no-underline transition-all duration-100 hover:bg-green-500 hover:text-white sm:p-6"
class="overflow-hidden rounded px-4 py-5 text-green-500 no-underline transition-all duration-100 hover:bg-green-500 hover:text-white sm:p-6"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.applications')}</dt>
<dd class="mt-1 text-3xl font-semibold ">
@ -227,7 +227,7 @@
<a
href="/destinations"
sveltekit:prefetch
class="overflow-hidden rounded-lg px-4 py-5 text-sky-500 no-underline transition-all duration-100 hover:bg-sky-500 hover:text-white sm:p-6"
class="overflow-hidden rounded px-4 py-5 text-sky-500 no-underline transition-all duration-100 hover:bg-sky-500 hover:text-white sm:p-6"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.destinations')}</dt>
<dd class="mt-1 text-3xl font-semibold ">
@ -238,7 +238,7 @@
<a
href="/sources"
sveltekit:prefetch
class="overflow-hidden rounded-lg px-4 py-5 text-orange-500 no-underline transition-all duration-100 hover:bg-orange-500 hover:text-white sm:p-6"
class="overflow-hidden rounded px-4 py-5 text-orange-500 no-underline transition-all duration-100 hover:bg-orange-500 hover:text-white sm:p-6"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.git_sources')}</dt>
<dd class="mt-1 text-3xl font-semibold">
@ -250,7 +250,7 @@
<a
href="/databases"
sveltekit:prefetch
class="overflow-hidden rounded-lg px-4 py-5 text-purple-500 no-underline transition-all duration-100 hover:bg-purple-500 hover:text-white sm:p-6"
class="overflow-hidden rounded px-4 py-5 text-purple-500 no-underline transition-all duration-100 hover:bg-purple-500 hover:text-white sm:p-6"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.databases')}</dt>
<dd class="mt-1 text-3xl font-semibold ">
@ -261,7 +261,7 @@
<a
href="/services"
sveltekit:prefetch
class="overflow-hidden rounded-lg px-4 py-5 text-pink-500 no-underline transition-all duration-100 hover:bg-pink-500 hover:text-white sm:p-6"
class="overflow-hidden rounded px-4 py-5 text-pink-500 no-underline transition-all duration-100 hover:bg-pink-500 hover:text-white sm:p-6"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.services')}</dt>
<dd class="mt-1 text-3xl font-semibold ">
@ -272,7 +272,7 @@
<a
href="/iam"
sveltekit:prefetch
class="overflow-hidden rounded-lg px-4 py-5 text-cyan-500 no-underline transition-all duration-100 hover:bg-cyan-500 hover:text-white sm:p-6"
class="overflow-hidden rounded px-4 py-5 text-cyan-500 no-underline transition-all duration-100 hover:bg-cyan-500 hover:text-white sm:p-6"
>
<dt class="truncate text-sm font-medium text-white">{$t('index.teams')}</dt>
<dd class="mt-1 text-3xl font-semibold ">

View File

@ -96,16 +96,3 @@
disabled
/>
</div>
<!-- <div class="grid grid-cols-3 items-center">
<label for="postgresqlPublicPort">Public Port</label>
<div class="col-span-2 ">
<CopyPasswordField
placeholder="{ $t('forms.generated_automatically_after_start') }"
readonly
disabled
id="postgresqlPublicPort"
name="postgresqlPublicPort"
value={service.plausibleAnalytics.postgresqlPublicPort}
/>
</div>
</div> -->

View File

@ -18,6 +18,7 @@
let ftpUser = service.wordpress.ftpUser;
let ftpPassword = service.wordpress.ftpPassword;
let ftpLoading = false;
let ownMysql = service.wordpress.ownMysql;
function generateUrl(publicPort) {
return browser
@ -40,7 +41,7 @@
publicPort,
ftpUser: user,
ftpPassword: password
} = await post(`/services/${id}/wordpress/settings.json`, {
} = await post(`/services/${id}/wordpress/ftp.json`, {
ftpEnabled
});
ftpUrl = generateUrl(publicPort);
@ -52,6 +53,18 @@
} finally {
ftpLoading = false;
}
} else {
try {
if (name === 'ownMysql') {
ownMysql = !ownMysql;
}
await post(`/services/${id}/wordpress/settings.json`, {
ownMysql
});
service.wordpress.ownMysql = ownMysql;
} catch ({ error }) {
return errorNotification(error);
}
}
}
</script>
@ -106,51 +119,95 @@ define('SUBDOMAIN_INSTALL', false);`
<div class="flex space-x-1 py-5 font-bold">
<div class="title">MySQL</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<Setting
dataTooltip={$t('forms.must_be_stopped_to_modify')}
bind:setting={service.wordpress.ownMysql}
disabled={isRunning}
on:click={() => !isRunning && changeSettings('ownMysql')}
title="Use your own MySQL server"
description="Enables the use of your own MySQL server. If you don't have one, you can use the one provided by Coolify."
/>
</div>
{#if service.wordpress.ownMysql}
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlHost">Host</label>
<input
name="mysqlHost"
id="mysqlHost"
required
readonly={isRunning}
disabled={isRunning}
bind:value={service.wordpress.mysqlHost}
placeholder="{$t('forms.eg')}: db.coolify.io"
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlPort">Port</label>
<input
name="mysqlPort"
id="mysqlPort"
required
readonly={isRunning}
disabled={isRunning}
bind:value={service.wordpress.mysqlPort}
placeholder="{$t('forms.eg')}: 3306"
/>
</div>
{/if}
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlDatabase">{$t('index.database')}</label>
<input
name="mysqlDatabase"
id="mysqlDatabase"
required
readonly={readOnly}
disabled={readOnly}
readonly={readOnly && !service.wordpress.ownMysql}
disabled={readOnly && !service.wordpress.ownMysql}
bind:value={service.wordpress.mysqlDatabase}
placeholder="{$t('forms.eg')}: wordpress_db"
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlRootUser">{$t('forms.root_user')}</label>
<input
name="mysqlRootUser"
id="mysqlRootUser"
placeholder="MySQL {$t('forms.root_user')}"
value={service.wordpress.mysqlRootUser}
disabled
readonly
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlRootUserPassword">{$t('forms.roots_password')}</label>
<CopyPasswordField
id="mysqlRootUserPassword"
isPasswordField
readonly
disabled
name="mysqlRootUserPassword"
value={service.wordpress.mysqlRootUserPassword}
/>
</div>
{#if !service.wordpress.ownMysql}
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlRootUser">{$t('forms.root_user')}</label>
<input
name="mysqlRootUser"
id="mysqlRootUser"
placeholder="MySQL {$t('forms.root_user')}"
value={service.wordpress.mysqlRootUser}
readonly={isRunning || !service.wordpress.ownMysq}
disabled={isRunning || !service.wordpress.ownMysq}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlRootUserPassword">{$t('forms.roots_password')}</label>
<CopyPasswordField
id="mysqlRootUserPassword"
isPasswordField
readonly={isRunning || !service.wordpress.ownMysq}
disabled={isRunning || !service.wordpress.ownMysq}
name="mysqlRootUserPassword"
value={service.wordpress.mysqlRootUserPassword}
/>
</div>
{/if}
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlUser">{$t('forms.user')}</label>
<input name="mysqlUser" id="mysqlUser" value={service.wordpress.mysqlUser} disabled readonly />
<input
name="mysqlUser"
id="mysqlUser"
value={service.wordpress.mysqlUser}
readonly={isRunning || !service.wordpress.ownMysql}
disabled={isRunning || !service.wordpress.ownMysql}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mysqlPassword">{$t('forms.password')}</label>
<CopyPasswordField
id="mysqlPassword"
isPasswordField
readonly
disabled
readonly={isRunning || !service.wordpress.ownMysql}
disabled={isRunning || !service.wordpress.ownMysql}
name="mysqlPassword"
value={service.wordpress.mysqlPassword}
/>

View File

@ -0,0 +1,185 @@
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 { 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';
import cuid from 'cuid';
import fs from 'fs/promises';
import yaml from 'js-yaml';
export const post: RequestHandler = async (event) => {
const { status, body, teamId } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { ftpEnabled } = await event.request.json();
const publicPort = await getFreePort();
let ftpUser = cuid();
let ftpPassword = generatePassword();
const hostkeyDir = dev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
try {
const data = await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpEnabled },
include: { service: { include: { destinationDocker: true } } }
});
const {
service: { destinationDockerId, destinationDocker },
ftpPublicPort: oldPublicPort,
ftpUser: user,
ftpPassword: savedPassword,
ftpHostKey,
ftpHostKeyPrivate
} = data;
if (user) ftpUser = user;
if (savedPassword) ftpPassword = decrypt(savedPassword);
const { stdout: password } = await asyncExecShell(
`echo ${ftpPassword} | openssl passwd -1 -stdin`
);
if (destinationDockerId) {
try {
await fs.stat(hostkeyDir);
} catch (error) {
await asyncExecShell(`mkdir -p ${hostkeyDir}`);
}
if (!ftpHostKey) {
await asyncExecShell(
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
);
const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`);
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpHostKey: encrypt(ftpHostKey) }
});
} else {
await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`);
}
if (!ftpHostKeyPrivate) {
await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`);
const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`);
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
});
} else {
await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`);
}
const { network, engine } = destinationDocker;
const host = getEngine(engine);
if (ftpEnabled) {
await db.prisma.wordpress.update({
where: { serviceId: id },
data: {
ftpPublicPort: publicPort,
ftpUser: user ? undefined : ftpUser,
ftpPassword: savedPassword ? undefined : encrypt(ftpPassword)
}
});
try {
const isRunning = await checkContainer(engine, `${id}-ftp`);
if (isRunning) {
await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
);
}
} catch (error) {
console.log(error);
//
}
const volumes = [
`${id}-wordpress-data:/home/${ftpUser}`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.sh:/etc/sftp.d/chmod.sh`
];
const compose: ComposeFile = {
version: '3.8',
services: {
[`${id}-ftp`]: {
image: `atmoz/sftp:alpine`,
command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`,
extra_hosts: ['host.docker.internal:host-gateway'],
container_name: `${id}-ftp`,
volumes,
networks: [network],
depends_on: [],
restart: 'always'
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[`${id}-wordpress-data`]: {
external: true,
name: `${id}-wordpress-data`
}
}
};
await fs.writeFile(
`${hostkeyDir}/${id}.sh`,
`#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key`
);
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
);
await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22);
} else {
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpPublicPort: null }
});
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
);
} catch (error) {
//
}
await stopTcpHttpProxy(destinationDocker, oldPublicPort);
}
}
if (ftpEnabled) {
return {
status: 201,
body: {
publicPort,
ftpUser,
ftpPassword
}
};
} else {
return {
status: 200,
body: {}
};
}
} catch (error) {
console.log(error);
return ErrorHandler(error);
} finally {
await asyncExecShell(
`rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
);
}
};

View File

@ -12,13 +12,24 @@ export const post: RequestHandler = async (event) => {
name,
fqdn,
exposePort,
wordpress: { extraConfig, mysqlDatabase }
wordpress: { extraConfig, mysqlDatabase, mysqlHost, mysqlPort }
} = await event.request.json();
if (fqdn) fqdn = fqdn.toLowerCase();
if (exposePort) exposePort = Number(exposePort);
if (mysqlPort) mysqlPort = Number(mysqlPort);
try {
await db.updateWordpress({ id, fqdn, name, extraConfig, mysqlDatabase, exposePort });
await db.updateWordpress({
id,
fqdn,
name,
extraConfig,
mysqlDatabase,
exposePort,
mysqlHost,
mysqlPort
});
return { status: 201 };
} catch (error) {
return ErrorHandler(error);

View File

@ -16,170 +16,17 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
const { ftpEnabled } = await event.request.json();
const publicPort = await getFreePort();
let ftpUser = cuid();
let ftpPassword = generatePassword();
const hostkeyDir = dev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
const { ownMysql } = await event.request.json();
try {
const data = await db.prisma.wordpress.update({
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpEnabled },
include: { service: { include: { destinationDocker: true } } }
data: { ownMysql }
});
const {
service: { destinationDockerId, destinationDocker },
ftpPublicPort: oldPublicPort,
ftpUser: user,
ftpPassword: savedPassword,
ftpHostKey,
ftpHostKeyPrivate
} = data;
if (user) ftpUser = user;
if (savedPassword) ftpPassword = decrypt(savedPassword);
const { stdout: password } = await asyncExecShell(
`echo ${ftpPassword} | openssl passwd -1 -stdin`
);
if (destinationDockerId) {
try {
await fs.stat(hostkeyDir);
} catch (error) {
await asyncExecShell(`mkdir -p ${hostkeyDir}`);
}
if (!ftpHostKey) {
await asyncExecShell(
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
);
const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`);
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpHostKey: encrypt(ftpHostKey) }
});
} else {
await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`);
}
if (!ftpHostKeyPrivate) {
await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`);
const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`);
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
});
} else {
await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`);
}
const { network, engine } = destinationDocker;
const host = getEngine(engine);
if (ftpEnabled) {
await db.prisma.wordpress.update({
where: { serviceId: id },
data: {
ftpPublicPort: publicPort,
ftpUser: user ? undefined : ftpUser,
ftpPassword: savedPassword ? undefined : encrypt(ftpPassword)
}
});
try {
const isRunning = await checkContainer(engine, `${id}-ftp`);
if (isRunning) {
await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
);
}
} catch (error) {
console.log(error);
//
}
const volumes = [
`${id}-wordpress-data:/home/${ftpUser}`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
`${
dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.sh:/etc/sftp.d/chmod.sh`
];
const compose: ComposeFile = {
version: '3.8',
services: {
[`${id}-ftp`]: {
image: `atmoz/sftp:alpine`,
command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`,
extra_hosts: ['host.docker.internal:host-gateway'],
container_name: `${id}-ftp`,
volumes,
networks: [network],
depends_on: [],
restart: 'always'
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[`${id}-wordpress-data`]: {
external: true,
name: `${id}-wordpress-data`
}
}
};
await fs.writeFile(
`${hostkeyDir}/${id}.sh`,
`#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key`
);
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
);
await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22);
} else {
await db.prisma.wordpress.update({
where: { serviceId: id },
data: { ftpPublicPort: null }
});
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
);
} catch (error) {
//
}
await stopTcpHttpProxy(destinationDocker, oldPublicPort);
}
}
if (ftpEnabled) {
return {
status: 201,
body: {
publicPort,
ftpUser,
ftpPassword
}
};
} else {
return {
status: 200,
body: {}
};
}
return {
status: 201
};
} catch (error) {
console.log(error);
return ErrorHandler(error);
} finally {
await asyncExecShell(
`rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
);
}
};

View File

@ -26,11 +26,14 @@ export const post: RequestHandler = async (event) => {
exposePort,
wordpress: {
mysqlDatabase,
mysqlHost,
mysqlPort,
mysqlUser,
mysqlPassword,
extraConfig,
mysqlRootUser,
mysqlRootUserPassword
mysqlRootUserPassword,
ownMysql
}
} = service;
@ -45,7 +48,7 @@ export const post: RequestHandler = async (event) => {
image: `${image}:${version}`,
volume: `${id}-wordpress-data:/var/www/html`,
environmentVariables: {
WORDPRESS_DB_HOST: `${id}-mysql`,
WORDPRESS_DB_HOST: ownMysql ? `${mysqlHost}:${mysqlPort}` : `${id}-mysql`,
WORDPRESS_DB_USER: mysqlUser,
WORDPRESS_DB_PASSWORD: mysqlPassword,
WORDPRESS_DB_NAME: mysqlDatabase,
@ -69,7 +72,7 @@ export const post: RequestHandler = async (event) => {
config.wordpress.environmentVariables[secret.name] = secret.value;
});
}
const composeFile: ComposeFile = {
let composeFile: ComposeFile = {
version: '3.8',
services: {
[id]: {
@ -80,7 +83,6 @@ export const post: RequestHandler = async (event) => {
networks: [network],
restart: 'always',
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
depends_on: [`${id}-mysql`],
labels: makeLabelForServices('wordpress'),
deploy: {
restart_policy: {
@ -90,22 +92,6 @@ export const post: RequestHandler = async (event) => {
window: '120s'
}
}
},
[`${id}-mysql`]: {
container_name: `${id}-mysql`,
image: config.mysql.image,
volumes: [config.mysql.volume],
environment: config.mysql.environmentVariables,
networks: [network],
restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
}
},
networks: {
@ -116,12 +102,32 @@ export const post: RequestHandler = async (event) => {
volumes: {
[config.wordpress.volume.split(':')[0]]: {
name: config.wordpress.volume.split(':')[0]
},
[config.mysql.volume.split(':')[0]]: {
name: config.mysql.volume.split(':')[0]
}
}
};
if (!ownMysql) {
composeFile.services[id].depends_on = [`${id}-mysql`];
composeFile.services[`${id}-mysql`] = {
container_name: `${id}-mysql`,
image: config.mysql.image,
volumes: [config.mysql.volume],
environment: config.mysql.environmentVariables,
networks: [network],
restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
};
composeFile.volumes[config.mysql.volume.split(':')[0]] = {
name: config.mysql.volume.split(':')[0]
};
}
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {

View File

@ -55,7 +55,7 @@
</div>
</div>
<div class="flex flex-col flex-wrap justify-center">
<div class="flex justify-center">
{#if !services || ownServices.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">{$t('service.no_service')}</div>

View File

@ -68,10 +68,48 @@
}
</script>
<div class="flex space-x-1 p-6 px-6 text-2xl font-bold">
<div class="tracking-tight">{$t('application.git_source')}</div>
<span class="arrow-right-applications px-1 text-orange-500">></span>
<span class="pr-2">{source.name}</span>
<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
<div class="-mb-5 flex-col">
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Configuration
</div>
<span class="text-xs">{source.name}</span>
</div>
{#if source?.type === 'gitlab'}
<svg viewBox="0 0 128 128" class="w-8">
<path
fill="#FC6D26"
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
fill="#FC6D26"
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
/><path
fill="#FCA326"
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
/><path
fill="#E24329"
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
fill="#FCA326"
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
/><path
fill="#E24329"
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
/>
</svg>
{:else if source?.type === 'github'}
<svg viewBox="0 0 128 128" class="w-8">
<g fill="#ffffff"
><path
fill-rule="evenodd"
clip-rule="evenodd"
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
/><path
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
/></g
>
</svg>
{/if}
</div>
<div class="flex flex-col justify-center">

View File

@ -62,7 +62,7 @@
</button>
{/if}
</div>
<div class="flex flex-col flex-wrap justify-center">
<div class="flex justify-center">
{#if !sources || ownSources.length === 0}
<div class="flex-col">
<div class="text-center text-xl font-bold">{$t('source.no_git_sources_found')}</div>
@ -74,11 +74,48 @@
{#each ownSources as source}
<a href="/sources/{source.id}" class="w-96 p-2 no-underline">
<div
class="box-selection group hover:bg-orange-600"
class="box-selection group relative hover:bg-orange-600"
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
class:border-0={source.gitlabApp && !source.gitlabAppId}
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
>
<div class="absolute top-0 left-0 -m-5 h-10 w-10">
{#if source?.type === 'gitlab'}
<svg viewBox="0 0 128 128">
<path
fill="#FC6D26"
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
fill="#FC6D26"
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
/><path
fill="#FCA326"
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
/><path
fill="#E24329"
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
fill="#FCA326"
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
/><path
fill="#E24329"
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
/>
</svg>
{:else if source?.type === 'github'}
<svg viewBox="0 0 128 128">
<g fill="#ffffff"
><path
fill-rule="evenodd"
clip-rule="evenodd"
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
/><path
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
/></g
>
</svg>
{/if}
</div>
<div class="truncate text-center text-xl font-bold">{source.name}</div>
{#if $session.teamId === '0' && otherSources.length > 0}
<div class="truncate text-center">{source.teams[0].name}</div>

View File

@ -356,7 +356,7 @@ a {
}
.box-selection {
@apply min-w-[16rem] max-w-[24rem] justify-center rounded border-transparent bg-coolgray-200 p-6 shadow-lg transition duration-150 hover:scale-105 hover:border-transparent hover:bg-coolgray-400;
@apply min-w-[16rem] max-w-[24rem] justify-center rounded border-transparent bg-coolgray-200 p-6 hover:border-transparent hover:bg-coolgray-400;
}
._toastBar {