feat: WP could have custom db
This commit is contained in:
		@@ -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;
 | 
				
			||||||
@@ -353,6 +353,9 @@ model Wordpress {
 | 
				
			|||||||
  id                    String   @id @default(cuid())
 | 
					  id                    String   @id @default(cuid())
 | 
				
			||||||
  extraConfig           String?
 | 
					  extraConfig           String?
 | 
				
			||||||
  tablePrefix           String?
 | 
					  tablePrefix           String?
 | 
				
			||||||
 | 
					  ownMysql              Boolean  @default(false)
 | 
				
			||||||
 | 
					  mysqlHost             String?
 | 
				
			||||||
 | 
					  mysqlPort             Int?
 | 
				
			||||||
  mysqlUser             String
 | 
					  mysqlUser             String
 | 
				
			||||||
  mysqlPassword         String
 | 
					  mysqlPassword         String
 | 
				
			||||||
  mysqlRootUser         String
 | 
					  mysqlRootUser         String
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<img
 | 
					<img
 | 
				
			||||||
	alt="plausible logo"
 | 
						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"
 | 
						src="/plausible.png"
 | 
				
			||||||
/>
 | 
					/>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,7 @@ if (!dev) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const prisma = new PrismaClient({
 | 
					export const prisma = new PrismaClient({
 | 
				
			||||||
	errorFormat: 'pretty',
 | 
						errorFormat: 'minimal',
 | 
				
			||||||
	rejectOnNotFound: false
 | 
						rejectOnNotFound: false
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -419,7 +419,9 @@ export async function updateWordpress({
 | 
				
			|||||||
	name,
 | 
						name,
 | 
				
			||||||
	exposePort,
 | 
						exposePort,
 | 
				
			||||||
	mysqlDatabase,
 | 
						mysqlDatabase,
 | 
				
			||||||
	extraConfig
 | 
						extraConfig,
 | 
				
			||||||
 | 
						mysqlHost,
 | 
				
			||||||
 | 
						mysqlPort
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
	id: string;
 | 
						id: string;
 | 
				
			||||||
	fqdn: string;
 | 
						fqdn: string;
 | 
				
			||||||
@@ -427,10 +429,24 @@ export async function updateWordpress({
 | 
				
			|||||||
	exposePort?: number;
 | 
						exposePort?: number;
 | 
				
			||||||
	mysqlDatabase: string;
 | 
						mysqlDatabase: string;
 | 
				
			||||||
	extraConfig: string;
 | 
						extraConfig: string;
 | 
				
			||||||
 | 
						mysqlHost?: string;
 | 
				
			||||||
 | 
						mysqlPort?: number;
 | 
				
			||||||
}): Promise<Service> {
 | 
					}): Promise<Service> {
 | 
				
			||||||
	return await prisma.service.update({
 | 
						return await prisma.service.update({
 | 
				
			||||||
		where: { id },
 | 
							where: { id },
 | 
				
			||||||
		data: { fqdn, name, exposePort, wordpress: { update: { mysqlDatabase, extraConfig } } }
 | 
							data: {
 | 
				
			||||||
 | 
								fqdn,
 | 
				
			||||||
 | 
								name,
 | 
				
			||||||
 | 
								exposePort,
 | 
				
			||||||
 | 
								wordpress: {
 | 
				
			||||||
 | 
									update: {
 | 
				
			||||||
 | 
										mysqlDatabase,
 | 
				
			||||||
 | 
										extraConfig,
 | 
				
			||||||
 | 
										mysqlHost,
 | 
				
			||||||
 | 
										mysqlPort
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,7 +60,7 @@
 | 
				
			|||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	{/if}
 | 
						{/if}
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
<div class="flex flex-col flex-wrap justify-center">
 | 
					<div class="flex justify-center">
 | 
				
			||||||
	{#if !applications || ownApplications.length === 0}
 | 
						{#if !applications || ownApplications.length === 0}
 | 
				
			||||||
		<div class="flex-col">
 | 
							<div class="flex-col">
 | 
				
			||||||
			<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
 | 
								<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,7 +47,7 @@
 | 
				
			|||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="flex flex-col flex-wrap justify-center">
 | 
					<div class="flex justify-center">
 | 
				
			||||||
	{#if !databases || ownDatabases.length === 0}
 | 
						{#if !databases || ownDatabases.length === 0}
 | 
				
			||||||
		<div class="flex-col">
 | 
							<div class="flex-col">
 | 
				
			||||||
			<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>
 | 
								<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,10 +39,13 @@
 | 
				
			|||||||
	import { t } from '$lib/translations';
 | 
						import { t } from '$lib/translations';
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="flex space-x-1 p-6 text-2xl font-bold">
 | 
					<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
 | 
				
			||||||
	<div class="tracking-tight">{$t('application.destination')}</div>
 | 
						<div class="-mb-5 flex-col">
 | 
				
			||||||
	<span class="arrow-right-applications px-1">></span>
 | 
							<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
 | 
				
			||||||
	<span class="pr-2">{destination.name}</span>
 | 
								Configuration
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<span class="text-xs">{destination.name}</span>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="mx-auto max-w-4xl px-6">
 | 
					<div class="mx-auto max-w-4xl px-6">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -141,21 +141,21 @@
 | 
				
			|||||||
			<div class="title font-bold">Server Usage</div>
 | 
								<div class="title font-bold">Server Usage</div>
 | 
				
			||||||
			<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
 | 
								<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
 | 
				
			||||||
				<Loading />
 | 
									<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>
 | 
										<dt class="truncate text-sm font-medium text-white">Total Memory</dt>
 | 
				
			||||||
					<dd class="mt-1 text-3xl font-semibold text-white">
 | 
										<dd class="mt-1 text-3xl font-semibold text-white">
 | 
				
			||||||
						{(usage?.memory.totalMemMb).toFixed(0)}
 | 
											{(usage?.memory.totalMemMb).toFixed(0)}
 | 
				
			||||||
					</dd>
 | 
										</dd>
 | 
				
			||||||
				</div>
 | 
									</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>
 | 
										<dt class="truncate text-sm font-medium text-white">Used Memory</dt>
 | 
				
			||||||
					<dd class="mt-1 text-3xl font-semibold text-white ">
 | 
										<dd class="mt-1 text-3xl font-semibold text-white ">
 | 
				
			||||||
						{(usage?.memory.usedMemMb).toFixed(0)}
 | 
											{(usage?.memory.usedMemMb).toFixed(0)}
 | 
				
			||||||
					</dd>
 | 
										</dd>
 | 
				
			||||||
				</div>
 | 
									</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>
 | 
										<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">
 | 
										<dd class="mt-1 flex items-center text-3xl font-semibold text-white">
 | 
				
			||||||
						{usage?.memory.freeMemPercentage}%
 | 
											{usage?.memory.freeMemPercentage}%
 | 
				
			||||||
@@ -166,19 +166,19 @@
 | 
				
			|||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</dl>
 | 
								</dl>
 | 
				
			||||||
			<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
 | 
								<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>
 | 
										<dt class="truncate text-sm font-medium text-white">Total CPUs</dt>
 | 
				
			||||||
					<dd class="mt-1 text-3xl font-semibold text-white">
 | 
										<dd class="mt-1 text-3xl font-semibold text-white">
 | 
				
			||||||
						{usage?.cpu.count}
 | 
											{usage?.cpu.count}
 | 
				
			||||||
					</dd>
 | 
										</dd>
 | 
				
			||||||
				</div>
 | 
									</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>
 | 
										<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">
 | 
										<dd class="mt-1 text-3xl font-semibold text-white">
 | 
				
			||||||
						{usage?.cpu.load.join('/')}
 | 
											{usage?.cpu.load.join('/')}
 | 
				
			||||||
					</dd>
 | 
										</dd>
 | 
				
			||||||
				</div>
 | 
									</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>
 | 
										<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">
 | 
										<dd class="mt-1 flex items-center text-3xl  font-semibold text-white">
 | 
				
			||||||
						{usage?.cpu.usage}%
 | 
											{usage?.cpu.usage}%
 | 
				
			||||||
@@ -189,19 +189,19 @@
 | 
				
			|||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</dl>
 | 
								</dl>
 | 
				
			||||||
			<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
 | 
								<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>
 | 
										<dt class="truncate text-sm font-medium text-white">Total Disk</dt>
 | 
				
			||||||
					<dd class="mt-1 text-3xl font-semibold text-white">
 | 
										<dd class="mt-1 text-3xl font-semibold text-white">
 | 
				
			||||||
						{usage?.disk.totalGb}GB
 | 
											{usage?.disk.totalGb}GB
 | 
				
			||||||
					</dd>
 | 
										</dd>
 | 
				
			||||||
				</div>
 | 
									</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>
 | 
										<dt class="truncate text-sm font-medium text-white">Used Disk</dt>
 | 
				
			||||||
					<dd class="mt-1 text-3xl font-semibold text-white">
 | 
										<dd class="mt-1 text-3xl font-semibold text-white">
 | 
				
			||||||
						{usage?.disk.usedGb}GB
 | 
											{usage?.disk.usedGb}GB
 | 
				
			||||||
					</dd>
 | 
										</dd>
 | 
				
			||||||
				</div>
 | 
									</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>
 | 
										<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">
 | 
										<dd class="mt-1 flex items-center text-3xl font-semibold text-white">
 | 
				
			||||||
						{usage?.disk.freePercentage}%
 | 
											{usage?.disk.freePercentage}%
 | 
				
			||||||
@@ -217,7 +217,7 @@
 | 
				
			|||||||
			<a
 | 
								<a
 | 
				
			||||||
				href="/applications"
 | 
									href="/applications"
 | 
				
			||||||
				sveltekit:prefetch
 | 
									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>
 | 
									<dt class="truncate text-sm font-medium text-white">{$t('index.applications')}</dt>
 | 
				
			||||||
				<dd class="mt-1 text-3xl font-semibold ">
 | 
									<dd class="mt-1 text-3xl font-semibold ">
 | 
				
			||||||
@@ -227,7 +227,7 @@
 | 
				
			|||||||
			<a
 | 
								<a
 | 
				
			||||||
				href="/destinations"
 | 
									href="/destinations"
 | 
				
			||||||
				sveltekit:prefetch
 | 
									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>
 | 
									<dt class="truncate text-sm font-medium text-white">{$t('index.destinations')}</dt>
 | 
				
			||||||
				<dd class="mt-1 text-3xl font-semibold ">
 | 
									<dd class="mt-1 text-3xl font-semibold ">
 | 
				
			||||||
@@ -238,7 +238,7 @@
 | 
				
			|||||||
			<a
 | 
								<a
 | 
				
			||||||
				href="/sources"
 | 
									href="/sources"
 | 
				
			||||||
				sveltekit:prefetch
 | 
									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>
 | 
									<dt class="truncate text-sm font-medium text-white">{$t('index.git_sources')}</dt>
 | 
				
			||||||
				<dd class="mt-1 text-3xl font-semibold">
 | 
									<dd class="mt-1 text-3xl font-semibold">
 | 
				
			||||||
@@ -250,7 +250,7 @@
 | 
				
			|||||||
			<a
 | 
								<a
 | 
				
			||||||
				href="/databases"
 | 
									href="/databases"
 | 
				
			||||||
				sveltekit:prefetch
 | 
									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>
 | 
									<dt class="truncate text-sm font-medium text-white">{$t('index.databases')}</dt>
 | 
				
			||||||
				<dd class="mt-1 text-3xl font-semibold ">
 | 
									<dd class="mt-1 text-3xl font-semibold ">
 | 
				
			||||||
@@ -261,7 +261,7 @@
 | 
				
			|||||||
			<a
 | 
								<a
 | 
				
			||||||
				href="/services"
 | 
									href="/services"
 | 
				
			||||||
				sveltekit:prefetch
 | 
									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>
 | 
									<dt class="truncate text-sm font-medium text-white">{$t('index.services')}</dt>
 | 
				
			||||||
				<dd class="mt-1 text-3xl font-semibold ">
 | 
									<dd class="mt-1 text-3xl font-semibold ">
 | 
				
			||||||
@@ -272,7 +272,7 @@
 | 
				
			|||||||
			<a
 | 
								<a
 | 
				
			||||||
				href="/iam"
 | 
									href="/iam"
 | 
				
			||||||
				sveltekit:prefetch
 | 
									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>
 | 
									<dt class="truncate text-sm font-medium text-white">{$t('index.teams')}</dt>
 | 
				
			||||||
				<dd class="mt-1 text-3xl font-semibold ">
 | 
									<dd class="mt-1 text-3xl font-semibold ">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -96,16 +96,3 @@
 | 
				
			|||||||
		disabled
 | 
							disabled
 | 
				
			||||||
	/>
 | 
						/>
 | 
				
			||||||
</div>
 | 
					</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> -->
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,7 @@
 | 
				
			|||||||
	let ftpUser = service.wordpress.ftpUser;
 | 
						let ftpUser = service.wordpress.ftpUser;
 | 
				
			||||||
	let ftpPassword = service.wordpress.ftpPassword;
 | 
						let ftpPassword = service.wordpress.ftpPassword;
 | 
				
			||||||
	let ftpLoading = false;
 | 
						let ftpLoading = false;
 | 
				
			||||||
 | 
						let ownMysql = service.wordpress.ownMysql;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	function generateUrl(publicPort) {
 | 
						function generateUrl(publicPort) {
 | 
				
			||||||
		return browser
 | 
							return browser
 | 
				
			||||||
@@ -40,7 +41,7 @@
 | 
				
			|||||||
					publicPort,
 | 
										publicPort,
 | 
				
			||||||
					ftpUser: user,
 | 
										ftpUser: user,
 | 
				
			||||||
					ftpPassword: password
 | 
										ftpPassword: password
 | 
				
			||||||
				} = await post(`/services/${id}/wordpress/settings.json`, {
 | 
									} = await post(`/services/${id}/wordpress/ftp.json`, {
 | 
				
			||||||
					ftpEnabled
 | 
										ftpEnabled
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
				ftpUrl = generateUrl(publicPort);
 | 
									ftpUrl = generateUrl(publicPort);
 | 
				
			||||||
@@ -52,6 +53,18 @@
 | 
				
			|||||||
			} finally {
 | 
								} finally {
 | 
				
			||||||
				ftpLoading = false;
 | 
									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>
 | 
					</script>
 | 
				
			||||||
@@ -106,18 +119,55 @@ define('SUBDOMAIN_INSTALL', false);`
 | 
				
			|||||||
<div class="flex space-x-1 py-5 font-bold">
 | 
					<div class="flex space-x-1 py-5 font-bold">
 | 
				
			||||||
	<div class="title">MySQL</div>
 | 
						<div class="title">MySQL</div>
 | 
				
			||||||
</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">
 | 
					<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
	<label for="mysqlDatabase">{$t('index.database')}</label>
 | 
						<label for="mysqlDatabase">{$t('index.database')}</label>
 | 
				
			||||||
	<input
 | 
						<input
 | 
				
			||||||
		name="mysqlDatabase"
 | 
							name="mysqlDatabase"
 | 
				
			||||||
		id="mysqlDatabase"
 | 
							id="mysqlDatabase"
 | 
				
			||||||
		required
 | 
							required
 | 
				
			||||||
		readonly={readOnly}
 | 
							readonly={readOnly && !service.wordpress.ownMysql}
 | 
				
			||||||
		disabled={readOnly}
 | 
							disabled={readOnly && !service.wordpress.ownMysql}
 | 
				
			||||||
		bind:value={service.wordpress.mysqlDatabase}
 | 
							bind:value={service.wordpress.mysqlDatabase}
 | 
				
			||||||
		placeholder="{$t('forms.eg')}: wordpress_db"
 | 
							placeholder="{$t('forms.eg')}: wordpress_db"
 | 
				
			||||||
	/>
 | 
						/>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					{#if !service.wordpress.ownMysql}
 | 
				
			||||||
	<div class="grid grid-cols-2 items-center px-10">
 | 
						<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
		<label for="mysqlRootUser">{$t('forms.root_user')}</label>
 | 
							<label for="mysqlRootUser">{$t('forms.root_user')}</label>
 | 
				
			||||||
		<input
 | 
							<input
 | 
				
			||||||
@@ -125,8 +175,8 @@ define('SUBDOMAIN_INSTALL', false);`
 | 
				
			|||||||
			id="mysqlRootUser"
 | 
								id="mysqlRootUser"
 | 
				
			||||||
			placeholder="MySQL {$t('forms.root_user')}"
 | 
								placeholder="MySQL {$t('forms.root_user')}"
 | 
				
			||||||
			value={service.wordpress.mysqlRootUser}
 | 
								value={service.wordpress.mysqlRootUser}
 | 
				
			||||||
		disabled
 | 
								readonly={isRunning || !service.wordpress.ownMysq}
 | 
				
			||||||
		readonly
 | 
								disabled={isRunning || !service.wordpress.ownMysq}
 | 
				
			||||||
		/>
 | 
							/>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div class="grid grid-cols-2 items-center px-10">
 | 
						<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
@@ -134,23 +184,30 @@ define('SUBDOMAIN_INSTALL', false);`
 | 
				
			|||||||
		<CopyPasswordField
 | 
							<CopyPasswordField
 | 
				
			||||||
			id="mysqlRootUserPassword"
 | 
								id="mysqlRootUserPassword"
 | 
				
			||||||
			isPasswordField
 | 
								isPasswordField
 | 
				
			||||||
		readonly
 | 
								readonly={isRunning || !service.wordpress.ownMysq}
 | 
				
			||||||
		disabled
 | 
								disabled={isRunning || !service.wordpress.ownMysq}
 | 
				
			||||||
			name="mysqlRootUserPassword"
 | 
								name="mysqlRootUserPassword"
 | 
				
			||||||
			value={service.wordpress.mysqlRootUserPassword}
 | 
								value={service.wordpress.mysqlRootUserPassword}
 | 
				
			||||||
		/>
 | 
							/>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
<div class="grid grid-cols-2 items-center px-10">
 | 
					<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
	<label for="mysqlUser">{$t('forms.user')}</label>
 | 
						<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>
 | 
				
			||||||
<div class="grid grid-cols-2 items-center px-10">
 | 
					<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
	<label for="mysqlPassword">{$t('forms.password')}</label>
 | 
						<label for="mysqlPassword">{$t('forms.password')}</label>
 | 
				
			||||||
	<CopyPasswordField
 | 
						<CopyPasswordField
 | 
				
			||||||
		id="mysqlPassword"
 | 
							id="mysqlPassword"
 | 
				
			||||||
		isPasswordField
 | 
							isPasswordField
 | 
				
			||||||
		readonly
 | 
							readonly={isRunning || !service.wordpress.ownMysql}
 | 
				
			||||||
		disabled
 | 
							disabled={isRunning || !service.wordpress.ownMysql}
 | 
				
			||||||
		name="mysqlPassword"
 | 
							name="mysqlPassword"
 | 
				
			||||||
		value={service.wordpress.mysqlPassword}
 | 
							value={service.wordpress.mysqlPassword}
 | 
				
			||||||
	/>
 | 
						/>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										185
									
								
								src/routes/services/[id]/wordpress/ftp.json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/routes/services/[id]/wordpress/ftp.json.ts
									
									
									
									
									
										Normal 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`
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -12,13 +12,24 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
		name,
 | 
							name,
 | 
				
			||||||
		fqdn,
 | 
							fqdn,
 | 
				
			||||||
		exposePort,
 | 
							exposePort,
 | 
				
			||||||
		wordpress: { extraConfig, mysqlDatabase }
 | 
							wordpress: { extraConfig, mysqlDatabase, mysqlHost, mysqlPort }
 | 
				
			||||||
	} = await event.request.json();
 | 
						} = await event.request.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (fqdn) fqdn = fqdn.toLowerCase();
 | 
						if (fqdn) fqdn = fqdn.toLowerCase();
 | 
				
			||||||
	if (exposePort) exposePort = Number(exposePort);
 | 
						if (exposePort) exposePort = Number(exposePort);
 | 
				
			||||||
 | 
						if (mysqlPort) mysqlPort = Number(mysqlPort);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	try {
 | 
						try {
 | 
				
			||||||
		await db.updateWordpress({ id, fqdn, name, extraConfig, mysqlDatabase, exposePort });
 | 
							await db.updateWordpress({
 | 
				
			||||||
 | 
								id,
 | 
				
			||||||
 | 
								fqdn,
 | 
				
			||||||
 | 
								name,
 | 
				
			||||||
 | 
								extraConfig,
 | 
				
			||||||
 | 
								mysqlDatabase,
 | 
				
			||||||
 | 
								exposePort,
 | 
				
			||||||
 | 
								mysqlHost,
 | 
				
			||||||
 | 
								mysqlPort
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
		return { status: 201 };
 | 
							return { status: 201 };
 | 
				
			||||||
	} catch (error) {
 | 
						} catch (error) {
 | 
				
			||||||
		return ErrorHandler(error);
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,170 +16,17 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	const { id } = event.params;
 | 
						const { id } = event.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const { ftpEnabled } = await event.request.json();
 | 
						const { ownMysql } = await event.request.json();
 | 
				
			||||||
	const publicPort = await getFreePort();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	let ftpUser = cuid();
 | 
					 | 
				
			||||||
	let ftpPassword = generatePassword();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const hostkeyDir = dev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
 | 
					 | 
				
			||||||
	try {
 | 
						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({
 | 
							await db.prisma.wordpress.update({
 | 
				
			||||||
			where: { serviceId: id },
 | 
								where: { serviceId: id },
 | 
				
			||||||
					data: { ftpHostKey: encrypt(ftpHostKey) }
 | 
								data: { ownMysql }
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
			} 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 {
 | 
							return {
 | 
				
			||||||
				status: 201,
 | 
								status: 201
 | 
				
			||||||
				body: {
 | 
					 | 
				
			||||||
					publicPort,
 | 
					 | 
				
			||||||
					ftpUser,
 | 
					 | 
				
			||||||
					ftpPassword
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return {
 | 
					 | 
				
			||||||
				status: 200,
 | 
					 | 
				
			||||||
				body: {}
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} catch (error) {
 | 
						} catch (error) {
 | 
				
			||||||
		console.log(error);
 | 
							console.log(error);
 | 
				
			||||||
		return ErrorHandler(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`
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,11 +26,14 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
			exposePort,
 | 
								exposePort,
 | 
				
			||||||
			wordpress: {
 | 
								wordpress: {
 | 
				
			||||||
				mysqlDatabase,
 | 
									mysqlDatabase,
 | 
				
			||||||
 | 
									mysqlHost,
 | 
				
			||||||
 | 
									mysqlPort,
 | 
				
			||||||
				mysqlUser,
 | 
									mysqlUser,
 | 
				
			||||||
				mysqlPassword,
 | 
									mysqlPassword,
 | 
				
			||||||
				extraConfig,
 | 
									extraConfig,
 | 
				
			||||||
				mysqlRootUser,
 | 
									mysqlRootUser,
 | 
				
			||||||
				mysqlRootUserPassword
 | 
									mysqlRootUserPassword,
 | 
				
			||||||
 | 
									ownMysql
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} = service;
 | 
							} = service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,7 +48,7 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
				image: `${image}:${version}`,
 | 
									image: `${image}:${version}`,
 | 
				
			||||||
				volume: `${id}-wordpress-data:/var/www/html`,
 | 
									volume: `${id}-wordpress-data:/var/www/html`,
 | 
				
			||||||
				environmentVariables: {
 | 
									environmentVariables: {
 | 
				
			||||||
					WORDPRESS_DB_HOST: `${id}-mysql`,
 | 
										WORDPRESS_DB_HOST: ownMysql ? `${mysqlHost}:${mysqlPort}` : `${id}-mysql`,
 | 
				
			||||||
					WORDPRESS_DB_USER: mysqlUser,
 | 
										WORDPRESS_DB_USER: mysqlUser,
 | 
				
			||||||
					WORDPRESS_DB_PASSWORD: mysqlPassword,
 | 
										WORDPRESS_DB_PASSWORD: mysqlPassword,
 | 
				
			||||||
					WORDPRESS_DB_NAME: mysqlDatabase,
 | 
										WORDPRESS_DB_NAME: mysqlDatabase,
 | 
				
			||||||
@@ -69,7 +72,7 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
				config.wordpress.environmentVariables[secret.name] = secret.value;
 | 
									config.wordpress.environmentVariables[secret.name] = secret.value;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		const composeFile: ComposeFile = {
 | 
							let composeFile: ComposeFile = {
 | 
				
			||||||
			version: '3.8',
 | 
								version: '3.8',
 | 
				
			||||||
			services: {
 | 
								services: {
 | 
				
			||||||
				[id]: {
 | 
									[id]: {
 | 
				
			||||||
@@ -80,7 +83,6 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
					networks: [network],
 | 
										networks: [network],
 | 
				
			||||||
					restart: 'always',
 | 
										restart: 'always',
 | 
				
			||||||
					...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
 | 
										...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
 | 
				
			||||||
					depends_on: [`${id}-mysql`],
 | 
					 | 
				
			||||||
					labels: makeLabelForServices('wordpress'),
 | 
										labels: makeLabelForServices('wordpress'),
 | 
				
			||||||
					deploy: {
 | 
										deploy: {
 | 
				
			||||||
						restart_policy: {
 | 
											restart_policy: {
 | 
				
			||||||
@@ -90,22 +92,6 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
							window: '120s'
 | 
												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: {
 | 
								networks: {
 | 
				
			||||||
@@ -116,12 +102,32 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
			volumes: {
 | 
								volumes: {
 | 
				
			||||||
				[config.wordpress.volume.split(':')[0]]: {
 | 
									[config.wordpress.volume.split(':')[0]]: {
 | 
				
			||||||
					name: 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`;
 | 
							const composeFileDestination = `${workdir}/docker-compose.yaml`;
 | 
				
			||||||
		await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
 | 
							await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,7 +55,7 @@
 | 
				
			|||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="flex flex-col flex-wrap justify-center">
 | 
					<div class="flex justify-center">
 | 
				
			||||||
	{#if !services || ownServices.length === 0}
 | 
						{#if !services || ownServices.length === 0}
 | 
				
			||||||
		<div class="flex-col">
 | 
							<div class="flex-col">
 | 
				
			||||||
			<div class="text-center text-xl font-bold">{$t('service.no_service')}</div>
 | 
								<div class="text-center text-xl font-bold">{$t('service.no_service')}</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -68,10 +68,48 @@
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="flex space-x-1 p-6 px-6 text-2xl font-bold">
 | 
					<div class="flex h-20 items-center space-x-2 p-5 px-6 font-bold">
 | 
				
			||||||
	<div class="tracking-tight">{$t('application.git_source')}</div>
 | 
						<div class="-mb-5 flex-col">
 | 
				
			||||||
	<span class="arrow-right-applications px-1 text-orange-500">></span>
 | 
							<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
 | 
				
			||||||
	<span class="pr-2">{source.name}</span>
 | 
								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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="flex flex-col justify-center">
 | 
					<div class="flex flex-col justify-center">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,7 +62,7 @@
 | 
				
			|||||||
		</button>
 | 
							</button>
 | 
				
			||||||
	{/if}
 | 
						{/if}
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
<div class="flex flex-col flex-wrap justify-center">
 | 
					<div class="flex justify-center">
 | 
				
			||||||
	{#if !sources || ownSources.length === 0}
 | 
						{#if !sources || ownSources.length === 0}
 | 
				
			||||||
		<div class="flex-col">
 | 
							<div class="flex-col">
 | 
				
			||||||
			<div class="text-center text-xl font-bold">{$t('source.no_git_sources_found')}</div>
 | 
								<div class="text-center text-xl font-bold">{$t('source.no_git_sources_found')}</div>
 | 
				
			||||||
@@ -74,11 +74,48 @@
 | 
				
			|||||||
				{#each ownSources as source}
 | 
									{#each ownSources as source}
 | 
				
			||||||
					<a href="/sources/{source.id}" class="w-96 p-2 no-underline">
 | 
										<a href="/sources/{source.id}" class="w-96 p-2 no-underline">
 | 
				
			||||||
						<div
 | 
											<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-red-500={source.gitlabApp && !source.gitlabAppId}
 | 
				
			||||||
							class:border-0={source.gitlabApp && !source.gitlabAppId}
 | 
												class:border-0={source.gitlabApp && !source.gitlabAppId}
 | 
				
			||||||
							class:border-l-4={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>
 | 
												<div class="truncate text-center text-xl font-bold">{source.name}</div>
 | 
				
			||||||
							{#if $session.teamId === '0' && otherSources.length > 0}
 | 
												{#if $session.teamId === '0' && otherSources.length > 0}
 | 
				
			||||||
								<div class="truncate text-center">{source.teams[0].name}</div>
 | 
													<div class="truncate text-center">{source.teams[0].name}</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -356,7 +356,7 @@ a {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.box-selection {
 | 
					.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 {
 | 
					._toastBar {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user