diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 986a773bf..d5e322337 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -332,6 +332,12 @@ model Wordpress {
mysqlRootUserPassword String
mysqlDatabase String?
mysqlPublicPort Int?
+ ftpEnabled Boolean @default(false)
+ ftpUser String?
+ ftpPassword String?
+ ftpPublicPort Int?
+ ftpHostKey String?
+ ftpHostKeyPrivate String?
serviceId String @unique
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
diff --git a/src/lib/components/Setting.svelte b/src/lib/components/Setting.svelte
index c8764bed7..7d87dd6fb 100644
--- a/src/lib/components/Setting.svelte
+++ b/src/lib/components/Setting.svelte
@@ -7,6 +7,7 @@
export let isCenter = true;
export let disabled = false;
export let dataTooltip = null;
+ export let loading = false;
@@ -26,7 +27,7 @@
on:click
aria-pressed="false"
class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
- class:opacity-50={disabled}
+ class:opacity-50={disabled || loading}
class:bg-green-600={setting}
class:bg-stone-700={!setting}
>
@@ -40,6 +41,7 @@
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
class:opacity-0={setting}
class:opacity-100={!setting}
+ class:animate-spin={loading}
aria-hidden="true"
>
@@ -57,6 +59,7 @@
aria-hidden="true"
class:opacity-100={setting}
class:opacity-0={!setting}
+ class:animate-spin={loading}
>
{:else if service.type === 'wordpress'}
-
+
{:else if service.type === 'ghost'}
{:else if service.type === 'meilisearch'}
@@ -151,17 +152,4 @@
{/if}
-
diff --git a/src/routes/services/[id]/_Services/_Wordpress.svelte b/src/routes/services/[id]/_Services/_Wordpress.svelte
index 883178a22..dc7eb4ebb 100644
--- a/src/routes/services/[id]/_Services/_Wordpress.svelte
+++ b/src/routes/services/[id]/_Services/_Wordpress.svelte
@@ -1,9 +1,55 @@
@@ -28,6 +74,29 @@ define('SUBDOMAIN_INSTALL', false);`
: 'N/A'}>{service.wordpress.extraConfig}
+
+ changeSettings('ftpEnabled')}
+ title="Enable sFTP connection to WordPress data"
+ description="Enables an on-demand sFTP connection to the WordPress data directory. This is useful if you want to use sFTP to upload files."
+ />
+
+{#if service.wordpress.ftpEnabled}
+
+ sFTP Connection URI
+
+
+
+ User
+
+
+
+ Password
+
+
+{/if}
diff --git a/src/routes/services/[id]/__layout.svelte b/src/routes/services/[id]/__layout.svelte
index 362f89786..37158d711 100644
--- a/src/routes/services/[id]/__layout.svelte
+++ b/src/routes/services/[id]/__layout.svelte
@@ -16,7 +16,7 @@
const endpoint = `/services/${params.id}.json`;
const res = await fetch(endpoint);
if (res.ok) {
- const { service, isRunning } = await res.json();
+ const { service, isRunning, settings } = await res.json();
if (!service || Object.entries(service).length === 0) {
return {
status: 302,
@@ -45,7 +45,8 @@
stuff: {
service,
isRunning,
- readOnly
+ readOnly,
+ settings
}
};
}
diff --git a/src/routes/services/[id]/index.json.ts b/src/routes/services/[id]/index.json.ts
index 676ff6405..f75ff87f6 100644
--- a/src/routes/services/[id]/index.json.ts
+++ b/src/routes/services/[id]/index.json.ts
@@ -17,7 +17,7 @@ export const get: RequestHandler = async (event) => {
const { id } = event.params;
try {
const service = await db.getService({ id, teamId });
- const { destinationDockerId, destinationDocker, type, version } = service;
+ const { destinationDockerId, destinationDocker, type, version, settings } = service;
let isRunning = false;
if (destinationDockerId) {
@@ -46,7 +46,8 @@ export const get: RequestHandler = async (event) => {
return {
body: {
isRunning,
- service
+ service,
+ settings
}
};
} catch (error) {
diff --git a/src/routes/services/[id]/index.svelte b/src/routes/services/[id]/index.svelte
index 9f64e039b..a16591266 100644
--- a/src/routes/services/[id]/index.svelte
+++ b/src/routes/services/[id]/index.svelte
@@ -6,7 +6,8 @@
props: {
service: stuff.service,
isRunning: stuff.isRunning,
- readOnly: stuff.readOnly
+ readOnly: stuff.readOnly,
+ settings: stuff.settings
}
};
}
@@ -37,6 +38,7 @@
export let service;
export let isRunning;
export let readOnly;
+ export let settings;
if (browser && window.location.hostname === 'demo.coolify.io' && !service.fqdn) {
service.fqdn = `http://${cuid()}.demo.coolify.io`;
@@ -76,4 +78,4 @@
-
+
diff --git a/src/routes/services/[id]/wordpress/settings.json.ts b/src/routes/services/[id]/wordpress/settings.json.ts
new file mode 100644
index 000000000..728d4919f
--- /dev/null
+++ b/src/routes/services/[id]/wordpress/settings.json.ts
@@ -0,0 +1,138 @@
+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 { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
+import type { RequestHandler } from '@sveltejs/kit';
+import cuid from 'cuid';
+import fs from 'fs/promises';
+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 { ftpEnabled } = await event.request.json();
+ const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
+ let ftpUser = cuid();
+ const ftpPassword = generatePassword();
+
+ const hostkeyDir = dev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
+ try {
+ const { stdout: password } = await asyncExecShell(
+ `echo ${ftpPassword} | openssl passwd -1 -stdin`
+ );
+ 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,
+ ftpHostKey,
+ ftpHostKeyPrivate
+ } = data;
+ if (user) ftpUser = user;
+ 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 /tmp/${id} < /dev/null`
+ );
+ const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`);
+ await db.prisma.wordpress.update({
+ where: { serviceId: id },
+ data: { ftpHostKey: encrypt(ftpHostKey.replace('\n', '')) }
+ });
+ } else {
+ await asyncExecShell(`echo ${decrypt(ftpHostKey)} > ${hostkeyDir}/${id}.ed25519`);
+ }
+ if (!ftpHostKeyPrivate) {
+ await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f /tmp/${id}.rsa < /dev/null`);
+ const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat /tmp/${id}.rsa`);
+ await db.prisma.wordpress.update({
+ where: { serviceId: id },
+ data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate.replace('\n', '')) }
+ });
+ } else {
+ await asyncExecShell(`echo ${decrypt(ftpHostKeyPrivate)} > ${hostkeyDir}/${id}.rsa`);
+ }
+ if (destinationDockerId) {
+ const { network, engine } = destinationDocker;
+ const host = getEngine(engine);
+ if (ftpEnabled) {
+ await db.prisma.wordpress.update({
+ where: { serviceId: id },
+ data: { ftpPublicPort: publicPort, ftpUser, ftpPassword: 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);
+ //
+ }
+
+ await asyncExecShell(
+ `DOCKER_HOST=${host} docker run --restart always --add-host 'host.docker.internal:host-gateway' --network ${network} --name ${id}-ftp -v ${id}-wordpress-data:/home/${ftpUser} -v ${hostkeyDir}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key -v ${hostkeyDir}/${id}.rsa:/etc/ssh/ssh_host_rsa_key -d atmoz/sftp '${ftpUser}:${password.replace(
+ '\n',
+ ''
+ )}:e:1001'`
+ );
+
+ await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22);
+ } else {
+ await db.prisma.wordpress.update({
+ where: { serviceId: id },
+ data: { ftpPublicPort: null }
+ });
+ 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);
+ //
+ }
+
+ 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);
+ }
+};