+
Free Disk
{usage?.disk.freePercentage}%
@@ -217,7 +217,7 @@
{$t('index.applications')}
@@ -227,7 +227,7 @@
{$t('index.destinations')}
@@ -238,7 +238,7 @@
{$t('index.git_sources')}
@@ -250,7 +250,7 @@
{$t('index.databases')}
@@ -261,7 +261,7 @@
{$t('index.services')}
@@ -272,7 +272,7 @@
{$t('index.teams')}
diff --git a/src/routes/services/[id]/_Services/_PlausibleAnalytics.svelte b/src/routes/services/[id]/_Services/_PlausibleAnalytics.svelte
index 90a923dff..c348f123e 100644
--- a/src/routes/services/[id]/_Services/_PlausibleAnalytics.svelte
+++ b/src/routes/services/[id]/_Services/_PlausibleAnalytics.svelte
@@ -96,16 +96,3 @@
disabled
/>
-
diff --git a/src/routes/services/[id]/_Services/_Wordpress.svelte b/src/routes/services/[id]/_Services/_Wordpress.svelte
index 601843534..7dd62c946 100644
--- a/src/routes/services/[id]/_Services/_Wordpress.svelte
+++ b/src/routes/services/[id]/_Services/_Wordpress.svelte
@@ -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);
+ }
}
}
@@ -106,51 +119,95 @@ define('SUBDOMAIN_INSTALL', false);`
+
+ !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."
+ />
+
+{#if service.wordpress.ownMysql}
+
+ Host
+
+
+
+ Port
+
+
+{/if}
{$t('index.database')}
-
- {$t('forms.root_user')}
-
-
-
- {$t('forms.roots_password')}
-
-
+{#if !service.wordpress.ownMysql}
+
+ {$t('forms.root_user')}
+
+
+
+ {$t('forms.roots_password')}
+
+
+{/if}
{$t('forms.user')}
-
+
{$t('forms.password')}
diff --git a/src/routes/services/[id]/wordpress/ftp.json.ts b/src/routes/services/[id]/wordpress/ftp.json.ts
new file mode 100644
index 000000000..cee85bc41
--- /dev/null
+++ b/src/routes/services/[id]/wordpress/ftp.json.ts
@@ -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`
+ );
+ }
+};
diff --git a/src/routes/services/[id]/wordpress/index.json.ts b/src/routes/services/[id]/wordpress/index.json.ts
index c76cd1a92..a35270009 100644
--- a/src/routes/services/[id]/wordpress/index.json.ts
+++ b/src/routes/services/[id]/wordpress/index.json.ts
@@ -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);
diff --git a/src/routes/services/[id]/wordpress/settings.json.ts b/src/routes/services/[id]/wordpress/settings.json.ts
index cee85bc41..4848dd220 100644
--- a/src/routes/services/[id]/wordpress/settings.json.ts
+++ b/src/routes/services/[id]/wordpress/settings.json.ts
@@ -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`
- );
}
};
diff --git a/src/routes/services/[id]/wordpress/start.json.ts b/src/routes/services/[id]/wordpress/start.json.ts
index df16d16bc..8375c06d0 100644
--- a/src/routes/services/[id]/wordpress/start.json.ts
+++ b/src/routes/services/[id]/wordpress/start.json.ts
@@ -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 {
diff --git a/src/routes/services/index.svelte b/src/routes/services/index.svelte
index bab3a4322..06a7ce2cc 100644
--- a/src/routes/services/index.svelte
+++ b/src/routes/services/index.svelte
@@ -55,7 +55,7 @@