@@ -1,7 +1,7 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	"name": "coolify",
 | 
						"name": "coolify",
 | 
				
			||||||
	"description": "An open-source & self-hostable Heroku / Netlify alternative.",
 | 
						"description": "An open-source & self-hostable Heroku / Netlify alternative.",
 | 
				
			||||||
	"version": "2.1.1",
 | 
						"version": "2.2.0",
 | 
				
			||||||
	"license": "AGPL-3.0",
 | 
						"license": "AGPL-3.0",
 | 
				
			||||||
	"scripts": {
 | 
						"scripts": {
 | 
				
			||||||
		"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev",
 | 
							"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								prisma/migrations/20220327180323_ghost/migration.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								prisma/migrations/20220327180323_ghost/migration.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					-- CreateTable
 | 
				
			||||||
 | 
					CREATE TABLE "Ghost" (
 | 
				
			||||||
 | 
					    "id" TEXT NOT NULL PRIMARY KEY,
 | 
				
			||||||
 | 
					    "defaultEmail" TEXT NOT NULL,
 | 
				
			||||||
 | 
					    "defaultPassword" TEXT NOT NULL,
 | 
				
			||||||
 | 
					    "mariadbUser" TEXT NOT NULL,
 | 
				
			||||||
 | 
					    "mariadbPassword" TEXT NOT NULL,
 | 
				
			||||||
 | 
					    "mariadbRootUser" TEXT NOT NULL,
 | 
				
			||||||
 | 
					    "mariadbRootUserPassword" TEXT NOT NULL,
 | 
				
			||||||
 | 
					    "mariadbDatabase" TEXT,
 | 
				
			||||||
 | 
					    "mariadbPublicPort" INTEGER,
 | 
				
			||||||
 | 
					    "serviceId" TEXT NOT NULL,
 | 
				
			||||||
 | 
					    "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
 | 
					    "updatedAt" DATETIME NOT NULL,
 | 
				
			||||||
 | 
					    CONSTRAINT "Ghost_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- CreateIndex
 | 
				
			||||||
 | 
					CREATE UNIQUE INDEX "Ghost_serviceId_key" ON "Ghost"("serviceId");
 | 
				
			||||||
@@ -278,6 +278,7 @@ model Service {
 | 
				
			|||||||
  minio               Minio?
 | 
					  minio               Minio?
 | 
				
			||||||
  vscodeserver        Vscodeserver?
 | 
					  vscodeserver        Vscodeserver?
 | 
				
			||||||
  wordpress           Wordpress?
 | 
					  wordpress           Wordpress?
 | 
				
			||||||
 | 
					  ghost               Ghost?
 | 
				
			||||||
  serviceSecret       ServiceSecret[]
 | 
					  serviceSecret       ServiceSecret[]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -332,3 +333,19 @@ model Wordpress {
 | 
				
			|||||||
  createdAt             DateTime @default(now())
 | 
					  createdAt             DateTime @default(now())
 | 
				
			||||||
  updatedAt             DateTime @updatedAt
 | 
					  updatedAt             DateTime @updatedAt
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					model Ghost {
 | 
				
			||||||
 | 
					  id                      String   @id @default(cuid())
 | 
				
			||||||
 | 
					  defaultEmail            String
 | 
				
			||||||
 | 
					  defaultPassword         String
 | 
				
			||||||
 | 
					  mariadbUser             String
 | 
				
			||||||
 | 
					  mariadbPassword         String
 | 
				
			||||||
 | 
					  mariadbRootUser         String
 | 
				
			||||||
 | 
					  mariadbRootUserPassword String
 | 
				
			||||||
 | 
					  mariadbDatabase         String?
 | 
				
			||||||
 | 
					  mariadbPublicPort       Int?
 | 
				
			||||||
 | 
					  serviceId               String   @unique
 | 
				
			||||||
 | 
					  service                 Service  @relation(fields: [serviceId], references: [id])
 | 
				
			||||||
 | 
					  createdAt               DateTime @default(now())
 | 
				
			||||||
 | 
					  updatedAt               DateTime @updatedAt
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								src/app.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/app.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -8,9 +8,11 @@ declare namespace App {
 | 
				
			|||||||
	interface Platform {}
 | 
						interface Platform {}
 | 
				
			||||||
	interface Session extends SessionData {}
 | 
						interface Session extends SessionData {}
 | 
				
			||||||
	interface Stuff {
 | 
						interface Stuff {
 | 
				
			||||||
 | 
							service: any;
 | 
				
			||||||
		application: any;
 | 
							application: any;
 | 
				
			||||||
		isRunning: boolean;
 | 
							isRunning: boolean;
 | 
				
			||||||
		appId: string;
 | 
							appId: string;
 | 
				
			||||||
 | 
							readOnly: boolean;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								src/lib/components/svg/services/Ghost.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/lib/components/svg/services/Ghost.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
						export let isAbsolute = false;
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<img
 | 
				
			||||||
 | 
						alt="ghost logo"
 | 
				
			||||||
 | 
						class={isAbsolute ? 'w-12 absolute top-0 left-0 -m-3 -mt-5' : 'w-8 mx-auto'}
 | 
				
			||||||
 | 
						src="/ghost.png"
 | 
				
			||||||
 | 
					/>
 | 
				
			||||||
							
								
								
									
										24
									
								
								src/lib/components/svg/services/N8n.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/lib/components/svg/services/N8n.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
						export let isAbsolute = false;
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svg
 | 
				
			||||||
 | 
						class={isAbsolute ? 'w-12 h-12 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
 | 
				
			||||||
 | 
						viewBox="0 0 220 105"
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
						<g>
 | 
				
			||||||
 | 
							<path
 | 
				
			||||||
 | 
								fill="#FF6D5A"
 | 
				
			||||||
 | 
								d="M183.9,0.2c-9.8,0-18,6.7-20.3,15.8h-29.2c-11.5,0-20.8,9.3-20.8,20.8c0,5.7-4.7,10.4-10.4,10.4H99
 | 
				
			||||||
 | 
							c-2.3-9.1-10.5-15.8-20.3-15.8c-9.8,0-18,6.7-20.3,15.8H41.7c-2.3-9.1-10.5-15.8-20.3-15.8c-11.6,0-21,9.4-21,21
 | 
				
			||||||
 | 
							c0,11.6,9.4,21,21,21c9.8,0,18-6.7,20.3-15.8h16.7c2.3,9.1,10.5,15.8,20.3,15.8c9.7,0,17.9-6.6,20.3-15.6h4.2
 | 
				
			||||||
 | 
							c5.7,0,10.4,4.7,10.4,10.4c0,11.5,9.3,20.8,20.8,20.8h6.8c2.3,9.1,10.5,15.8,20.3,15.8c11.6,0,21-9.4,21-21c0-11.6-9.4-21-21-21
 | 
				
			||||||
 | 
							c-9.8,0-18,6.7-20.3,15.8h-6.8c-5.7,0-10.4-4.7-10.4-10.4c0-6.3-2.8-11.9-7.2-15.7c4.4-3.8,7.2-9.4,7.2-15.7
 | 
				
			||||||
 | 
							c0-5.7,4.7-10.4,10.4-10.4h29.2c2.3,9.1,10.5,15.8,20.3,15.8c11.6,0,21-9.4,21-21C204.9,9.6,195.5,0.2,183.9,0.2z M21.4,63
 | 
				
			||||||
 | 
							c-5.8,0-10.6-4.8-10.6-10.6s4.8-10.6,10.6-10.6S32,46.6,32,52.4S27.3,63,21.4,63z M78.7,63c-5.8,0-10.6-4.8-10.6-10.6
 | 
				
			||||||
 | 
							s4.8-10.6,10.6-10.6s10.6,4.8,10.6,10.6S84.6,63,78.7,63z M161.5,73.2c5.8,0,10.6,4.8,10.6,10.6s-4.8,10.6-10.6,10.6
 | 
				
			||||||
 | 
							s-10.6-4.8-10.6-10.6C150.9,77.9,155.7,73.2,161.5,73.2z M183.9,31.8c-5.8,0-10.6-4.8-10.6-10.6s4.8-10.6,10.6-10.6
 | 
				
			||||||
 | 
							s10.6,4.8,10.6,10.6C194.5,27,189.8,31.8,183.9,31.8z"
 | 
				
			||||||
 | 
							/>
 | 
				
			||||||
 | 
						</g>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
							
								
								
									
										159
									
								
								src/lib/components/svg/services/UptimeKuma.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/lib/components/svg/services/UptimeKuma.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,159 @@
 | 
				
			|||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
						export let isAbsolute = false;
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svg
 | 
				
			||||||
 | 
						class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-10 h-10 mx-auto'}
 | 
				
			||||||
 | 
						xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
						xmlns:xlink="http://www.w3.org/1999/xlink"
 | 
				
			||||||
 | 
						version="1.1"
 | 
				
			||||||
 | 
						preserveAspectRatio="xMidYMid meet"
 | 
				
			||||||
 | 
						viewBox="0 0 640 640"
 | 
				
			||||||
 | 
						width="640"
 | 
				
			||||||
 | 
						height="640"
 | 
				
			||||||
 | 
						><defs
 | 
				
			||||||
 | 
							><path
 | 
				
			||||||
 | 
								d="M407.55 916.24C471.25 916.24 522.89 967.88 522.89 1031.57C522.89 1113.88 522.89 1245.44 522.89 1327.74C522.89 1391.44 471.25 1443.08 407.55 1443.08C325.25 1443.08 193.68 1443.08 111.38 1443.08C47.69 1443.08 -3.95 1391.44 -3.95 1327.74C-3.95 1245.44 -3.95 1113.88 -3.95 1031.57C-3.95 967.88 47.69 916.24 111.38 916.24C193.68 916.24 325.25 916.24 407.55 916.24Z"
 | 
				
			||||||
 | 
								id="a1LdTs1gvU"
 | 
				
			||||||
 | 
							/><linearGradient
 | 
				
			||||||
 | 
								id="gradientcoH7TNh19"
 | 
				
			||||||
 | 
								gradientUnits="userSpaceOnUse"
 | 
				
			||||||
 | 
								x1="256.07"
 | 
				
			||||||
 | 
								y1="1132.14"
 | 
				
			||||||
 | 
								x2="609.11"
 | 
				
			||||||
 | 
								y2="1480.42"
 | 
				
			||||||
 | 
								><stop style="stop-color: #c2efd2;stop-opacity: 1" offset="0%" /><stop
 | 
				
			||||||
 | 
									style="stop-color: #8ff0e5;stop-opacity: 1"
 | 
				
			||||||
 | 
									offset="100%"
 | 
				
			||||||
 | 
								/></linearGradient
 | 
				
			||||||
 | 
							><path
 | 
				
			||||||
 | 
								d="M-467.41 394.63C-467.41 554.76 -597.42 684.76 -757.55 684.76C-917.68 684.76 -1047.69 554.76 -1047.69 394.63C-1047.69 234.5 -917.68 104.49 -757.55 104.49C-597.42 104.49 -467.41 234.5 -467.41 394.63Z"
 | 
				
			||||||
 | 
								id="a1uaEBd4xM"
 | 
				
			||||||
 | 
							/><path
 | 
				
			||||||
 | 
								d="M-96.99 -586.14C-57.24 -619.85 -5.79 -604.75 19.26 -580.46C31.43 -568.66 56.57 -546.36 40.97 -491.67C32.76 -462.87 10.41 -436.4 -26.05 -412.27C-15.07 -377.85 -5.6 -344.76 2.36 -313C14.29 -265.36 13.55 -189.67 -26.05 -155.4C-67.27 -119.73 -166.91 -104.09 -234.24 -103.09C-301.57 -102.1 -406.19 -113.09 -461.6 -155.4C-517.01 -197.7 -512.24 -257.07 -498.04 -313C-488.58 -350.28 -476.43 -383.38 -461.6 -412.27C-505.54 -441.3 -530.54 -467.76 -536.6 -491.67C-545.68 -527.54 -530.93 -565.61 -501.12 -586.14C-471.31 -606.67 -435.18 -606.9 -400.45 -586.14C-377.3 -572.3 -354.79 -542.13 -332.92 -495.62C-287.85 -505.25 -254.96 -509.57 -234.24 -508.6C-214.74 -507.68 -186.57 -503.36 -149.72 -495.62C-135.81 -537.95 -118.23 -568.12 -96.99 -586.14Z"
 | 
				
			||||||
 | 
								id="f8p7QlEjN3"
 | 
				
			||||||
 | 
							/><linearGradient
 | 
				
			||||||
 | 
								id="gradienta4Tg99ZOOp"
 | 
				
			||||||
 | 
								gradientUnits="userSpaceOnUse"
 | 
				
			||||||
 | 
								x1="-440.25"
 | 
				
			||||||
 | 
								y1="-388.59"
 | 
				
			||||||
 | 
								x2="-100.49"
 | 
				
			||||||
 | 
								y2="-147.33"
 | 
				
			||||||
 | 
								><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
 | 
				
			||||||
 | 
									style="stop-color: #7ae6a1;stop-opacity: 1"
 | 
				
			||||||
 | 
									offset="100%"
 | 
				
			||||||
 | 
								/></linearGradient
 | 
				
			||||||
 | 
							><path
 | 
				
			||||||
 | 
								d="M-86.03 -10.69C-61.35 -10.69 -41.34 9.32 -41.34 34.01C-41.34 119.07 -41.34 329.58 -41.34 414.65C-41.34 439.33 -61.35 459.34 -86.03 459.34C-136.01 459.34 -241.25 459.34 -291.23 459.34C-315.92 459.34 -335.93 439.33 -335.93 414.65C-335.93 329.58 -335.93 119.07 -335.93 34.01C-335.93 9.32 -315.92 -10.69 -291.23 -10.69C-241.25 -10.69 -136.01 -10.69 -86.03 -10.69Z"
 | 
				
			||||||
 | 
								id="d32ZZRxd1S"
 | 
				
			||||||
 | 
							/><linearGradient
 | 
				
			||||||
 | 
								id="gradientb1JxIe4xUm"
 | 
				
			||||||
 | 
								gradientUnits="userSpaceOnUse"
 | 
				
			||||||
 | 
								x1="-791.65"
 | 
				
			||||||
 | 
								y1="-33.27"
 | 
				
			||||||
 | 
								x2="892.1"
 | 
				
			||||||
 | 
								y2="418.94"
 | 
				
			||||||
 | 
								><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
 | 
				
			||||||
 | 
									style="stop-color: #5ae98f;stop-opacity: 1"
 | 
				
			||||||
 | 
									offset="100%"
 | 
				
			||||||
 | 
								/></linearGradient
 | 
				
			||||||
 | 
							><path
 | 
				
			||||||
 | 
								d="M-257.95 458.12C-247.92 449.62 -234.93 453.43 -228.61 459.56C-225.54 462.54 -219.19 468.17 -223.13 481.97C-225.2 489.24 -230.84 495.92 -240.05 502.01C-237.27 510.7 -234.88 519.06 -232.88 527.07C-229.86 539.1 -230.05 558.21 -240.05 566.86C-250.45 575.86 -275.6 579.81 -292.6 580.06C-309.6 580.31 -336.01 577.54 -349.99 566.86C-363.98 556.18 -362.77 541.19 -359.19 527.07C-356.8 517.66 -353.73 509.31 -349.99 502.01C-361.08 494.69 -367.39 488.01 -368.92 481.97C-371.22 472.92 -367.49 463.31 -359.97 458.12C-352.44 452.94 -343.32 452.88 -334.56 458.12C-328.71 461.62 -323.03 469.23 -317.51 480.97C-306.13 478.54 -297.83 477.45 -292.6 477.7C-287.68 477.93 -280.56 479.02 -271.26 480.97C-267.75 470.29 -263.32 462.67 -257.95 458.12Z"
 | 
				
			||||||
 | 
								id="b19LRRbPrG"
 | 
				
			||||||
 | 
							/><path
 | 
				
			||||||
 | 
								d="M490.4 235.64C544.09 358.38 544.09 435.34 490.4 466.5C409.85 513.24 199.96 527.49 139.54 455.64C99.26 407.74 99.26 334.4 139.54 235.64C180.5 168.18 238.71 134.45 314.17 134.45C389.64 134.45 448.38 168.18 490.4 235.64Z"
 | 
				
			||||||
 | 
								id="bN5StdyPU"
 | 
				
			||||||
 | 
							/><linearGradient
 | 
				
			||||||
 | 
								id="gradientb1HT15TsY0"
 | 
				
			||||||
 | 
								gradientUnits="userSpaceOnUse"
 | 
				
			||||||
 | 
								x1="259.78"
 | 
				
			||||||
 | 
								y1="261.15"
 | 
				
			||||||
 | 
								x2="463.85"
 | 
				
			||||||
 | 
								y2="456.49"
 | 
				
			||||||
 | 
								><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
 | 
				
			||||||
 | 
									style="stop-color: #86e6a9;stop-opacity: 1"
 | 
				
			||||||
 | 
									offset="100%"
 | 
				
			||||||
 | 
								/></linearGradient
 | 
				
			||||||
 | 
							><path
 | 
				
			||||||
 | 
								d="M393.81 -775.89C428.26 -748.09 439.99 -725.54 429 -708.22C412.51 -682.24 353.16 -646.07 324.5 -657.93C305.39 -665.83 294.22 -687.32 290.97 -722.41C292.69 -748.43 304.61 -767.19 326.73 -778.69C348.85 -790.19 371.21 -789.26 393.81 -775.89Z"
 | 
				
			||||||
 | 
								id="arh6miPP2"
 | 
				
			||||||
 | 
							/><linearGradient
 | 
				
			||||||
 | 
								id="gradientc2g6rBSAiq"
 | 
				
			||||||
 | 
								gradientUnits="userSpaceOnUse"
 | 
				
			||||||
 | 
								x1="330.1"
 | 
				
			||||||
 | 
								y1="-733.26"
 | 
				
			||||||
 | 
								x2="419.69"
 | 
				
			||||||
 | 
								y2="-707.1"
 | 
				
			||||||
 | 
								><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
 | 
				
			||||||
 | 
									style="stop-color: #86e6a9;stop-opacity: 1"
 | 
				
			||||||
 | 
									offset="100%"
 | 
				
			||||||
 | 
								/></linearGradient
 | 
				
			||||||
 | 
							><path
 | 
				
			||||||
 | 
								d="M675.36 -369.24C669.97 -325.31 657.02 -303.43 636.51 -303.61C605.74 -303.87 543.67 -335.15 538.59 -365.74C535.2 -386.14 547.54 -406.99 575.61 -428.29C598.61 -440.58 620.83 -440.37 642.29 -427.67C663.74 -414.97 674.77 -395.49 675.36 -369.24Z"
 | 
				
			||||||
 | 
								id="a2VENFzCvL"
 | 
				
			||||||
 | 
							/><linearGradient
 | 
				
			||||||
 | 
								id="gradientc18GuJy4sZ"
 | 
				
			||||||
 | 
								gradientUnits="userSpaceOnUse"
 | 
				
			||||||
 | 
								x1="605.5"
 | 
				
			||||||
 | 
								y1="-400.8"
 | 
				
			||||||
 | 
								x2="630.64"
 | 
				
			||||||
 | 
								y2="-310.92"
 | 
				
			||||||
 | 
								><stop style="stop-color: #5cdd8b;stop-opacity: 1" offset="0%" /><stop
 | 
				
			||||||
 | 
									style="stop-color: #86e6a9;stop-opacity: 1"
 | 
				
			||||||
 | 
									offset="100%"
 | 
				
			||||||
 | 
								/></linearGradient
 | 
				
			||||||
 | 
							></defs
 | 
				
			||||||
 | 
						><g
 | 
				
			||||||
 | 
							><g
 | 
				
			||||||
 | 
								><g><use xlink:href="#a1LdTs1gvU" opacity="1" fill="url(#gradientcoH7TNh19)" /></g><g
 | 
				
			||||||
 | 
									><use xlink:href="#a1uaEBd4xM" opacity="1" fill="#ebf0ed" fill-opacity="1" /></g
 | 
				
			||||||
 | 
								><g
 | 
				
			||||||
 | 
									><use xlink:href="#f8p7QlEjN3" opacity="1" fill="url(#gradienta4Tg99ZOOp)" /><g
 | 
				
			||||||
 | 
										><use
 | 
				
			||||||
 | 
											xlink:href="#f8p7QlEjN3"
 | 
				
			||||||
 | 
											opacity="1"
 | 
				
			||||||
 | 
											fill-opacity="0"
 | 
				
			||||||
 | 
											stroke="#ffffff"
 | 
				
			||||||
 | 
											stroke-width="98"
 | 
				
			||||||
 | 
											stroke-opacity="0.57"
 | 
				
			||||||
 | 
										/></g
 | 
				
			||||||
 | 
									></g
 | 
				
			||||||
 | 
								><g
 | 
				
			||||||
 | 
									><use xlink:href="#d32ZZRxd1S" opacity="1" fill="url(#gradientb1JxIe4xUm)" /><g
 | 
				
			||||||
 | 
										><use
 | 
				
			||||||
 | 
											xlink:href="#d32ZZRxd1S"
 | 
				
			||||||
 | 
											opacity="1"
 | 
				
			||||||
 | 
											fill-opacity="0"
 | 
				
			||||||
 | 
											stroke="#f2f2f2"
 | 
				
			||||||
 | 
											stroke-width="60"
 | 
				
			||||||
 | 
											stroke-opacity="0.51"
 | 
				
			||||||
 | 
										/></g
 | 
				
			||||||
 | 
									></g
 | 
				
			||||||
 | 
								><g
 | 
				
			||||||
 | 
									><use xlink:href="#b19LRRbPrG" opacity="1" fill="#d8ad9a" fill-opacity="1" /><g
 | 
				
			||||||
 | 
										><use
 | 
				
			||||||
 | 
											xlink:href="#b19LRRbPrG"
 | 
				
			||||||
 | 
											opacity="1"
 | 
				
			||||||
 | 
											fill-opacity="0"
 | 
				
			||||||
 | 
											stroke="#ffffff"
 | 
				
			||||||
 | 
											stroke-width="17"
 | 
				
			||||||
 | 
											stroke-opacity="1"
 | 
				
			||||||
 | 
										/></g
 | 
				
			||||||
 | 
									></g
 | 
				
			||||||
 | 
								><g
 | 
				
			||||||
 | 
									><use xlink:href="#bN5StdyPU" opacity="1" fill="url(#gradientb1HT15TsY0)" /><g
 | 
				
			||||||
 | 
										><use
 | 
				
			||||||
 | 
											xlink:href="#bN5StdyPU"
 | 
				
			||||||
 | 
											opacity="1"
 | 
				
			||||||
 | 
											fill-opacity="0"
 | 
				
			||||||
 | 
											stroke="#f2f2f2"
 | 
				
			||||||
 | 
											stroke-width="200"
 | 
				
			||||||
 | 
											stroke-opacity="0.51"
 | 
				
			||||||
 | 
										/></g
 | 
				
			||||||
 | 
									></g
 | 
				
			||||||
 | 
								><g><use xlink:href="#arh6miPP2" opacity="1" fill="url(#gradientc2g6rBSAiq)" /></g><g
 | 
				
			||||||
 | 
									><use xlink:href="#a2VENFzCvL" opacity="1" fill="url(#gradientc18GuJy4sZ)" /></g
 | 
				
			||||||
 | 
								></g
 | 
				
			||||||
 | 
							></g
 | 
				
			||||||
 | 
						></svg
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
@@ -107,6 +107,7 @@ export const supportedServiceTypesAndVersions = [
 | 
				
			|||||||
		name: 'plausibleanalytics',
 | 
							name: 'plausibleanalytics',
 | 
				
			||||||
		fancyName: 'Plausible Analytics',
 | 
							fancyName: 'Plausible Analytics',
 | 
				
			||||||
		baseImage: 'plausible/analytics',
 | 
							baseImage: 'plausible/analytics',
 | 
				
			||||||
 | 
							images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
 | 
				
			||||||
		versions: ['latest'],
 | 
							versions: ['latest'],
 | 
				
			||||||
		ports: {
 | 
							ports: {
 | 
				
			||||||
			main: 8000
 | 
								main: 8000
 | 
				
			||||||
@@ -143,6 +144,7 @@ export const supportedServiceTypesAndVersions = [
 | 
				
			|||||||
		name: 'wordpress',
 | 
							name: 'wordpress',
 | 
				
			||||||
		fancyName: 'Wordpress',
 | 
							fancyName: 'Wordpress',
 | 
				
			||||||
		baseImage: 'wordpress',
 | 
							baseImage: 'wordpress',
 | 
				
			||||||
 | 
							images: ['bitnami/mysql:5.7'],
 | 
				
			||||||
		versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
 | 
							versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
 | 
				
			||||||
		ports: {
 | 
							ports: {
 | 
				
			||||||
			main: 80
 | 
								main: 80
 | 
				
			||||||
@@ -165,6 +167,34 @@ export const supportedServiceTypesAndVersions = [
 | 
				
			|||||||
		ports: {
 | 
							ports: {
 | 
				
			||||||
			main: 8010
 | 
								main: 8010
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							name: 'n8n',
 | 
				
			||||||
 | 
							fancyName: 'n8n',
 | 
				
			||||||
 | 
							baseImage: 'n8nio/n8n',
 | 
				
			||||||
 | 
							versions: ['latest'],
 | 
				
			||||||
 | 
							ports: {
 | 
				
			||||||
 | 
								main: 5678
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							name: 'uptimekuma',
 | 
				
			||||||
 | 
							fancyName: 'Uptime Kuma',
 | 
				
			||||||
 | 
							baseImage: 'louislam/uptime-kuma',
 | 
				
			||||||
 | 
							versions: ['latest'],
 | 
				
			||||||
 | 
							ports: {
 | 
				
			||||||
 | 
								main: 3001
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							name: 'ghost',
 | 
				
			||||||
 | 
							fancyName: 'Ghost',
 | 
				
			||||||
 | 
							baseImage: 'bitnami/ghost',
 | 
				
			||||||
 | 
							images: ['bitnami/mariadb'],
 | 
				
			||||||
 | 
							versions: ['latest'],
 | 
				
			||||||
 | 
							ports: {
 | 
				
			||||||
 | 
								main: 2368
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -189,6 +219,13 @@ export function getServiceImage(type) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return '';
 | 
						return '';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					export function getServiceImages(type) {
 | 
				
			||||||
 | 
						const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
 | 
				
			||||||
 | 
						if (found) {
 | 
				
			||||||
 | 
							return found.images;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return [];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
export function generateDatabaseConfiguration(database) {
 | 
					export function generateDatabaseConfiguration(database) {
 | 
				
			||||||
	const {
 | 
						const {
 | 
				
			||||||
		id,
 | 
							id,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { asyncExecShell, getEngine } from '$lib/common';
 | 
				
			||||||
import { decrypt, encrypt } from '$lib/crypto';
 | 
					import { decrypt, encrypt } from '$lib/crypto';
 | 
				
			||||||
import cuid from 'cuid';
 | 
					import cuid from 'cuid';
 | 
				
			||||||
import { generatePassword } from '.';
 | 
					import { generatePassword } from '.';
 | 
				
			||||||
@@ -20,6 +21,7 @@ export async function getService({ id, teamId }) {
 | 
				
			|||||||
			minio: true,
 | 
								minio: true,
 | 
				
			||||||
			vscodeserver: true,
 | 
								vscodeserver: true,
 | 
				
			||||||
			wordpress: true,
 | 
								wordpress: true,
 | 
				
			||||||
 | 
								ghost: true,
 | 
				
			||||||
			serviceSecret: true
 | 
								serviceSecret: true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
@@ -43,12 +45,18 @@ export async function getService({ id, teamId }) {
 | 
				
			|||||||
	if (body.wordpress?.mysqlRootUserPassword)
 | 
						if (body.wordpress?.mysqlRootUserPassword)
 | 
				
			||||||
		body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword);
 | 
							body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (body.ghost?.mariadbPassword) body.ghost.mariadbPassword = decrypt(body.ghost.mariadbPassword);
 | 
				
			||||||
 | 
						if (body.ghost?.mariadbRootUserPassword)
 | 
				
			||||||
 | 
							body.ghost.mariadbRootUserPassword = decrypt(body.ghost.mariadbRootUserPassword);
 | 
				
			||||||
 | 
						if (body.ghost?.defaultPassword) body.ghost.defaultPassword = decrypt(body.ghost.defaultPassword);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (body?.serviceSecret.length > 0) {
 | 
						if (body?.serviceSecret.length > 0) {
 | 
				
			||||||
		body.serviceSecret = body.serviceSecret.map((s) => {
 | 
							body.serviceSecret = body.serviceSecret.map((s) => {
 | 
				
			||||||
			s.value = decrypt(s.value);
 | 
								s.value = decrypt(s.value);
 | 
				
			||||||
			return s;
 | 
								return s;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return { ...body };
 | 
						return { ...body };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -119,6 +127,44 @@ export async function configureServiceType({ id, type }) {
 | 
				
			|||||||
				type
 | 
									type
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
						} else if (type === 'n8n') {
 | 
				
			||||||
 | 
							await prisma.service.update({
 | 
				
			||||||
 | 
								where: { id },
 | 
				
			||||||
 | 
								data: {
 | 
				
			||||||
 | 
									type
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						} else if (type === 'uptimekuma') {
 | 
				
			||||||
 | 
							await prisma.service.update({
 | 
				
			||||||
 | 
								where: { id },
 | 
				
			||||||
 | 
								data: {
 | 
				
			||||||
 | 
									type
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						} else if (type === 'ghost') {
 | 
				
			||||||
 | 
							const defaultEmail = `${cuid()}@coolify.io`;
 | 
				
			||||||
 | 
							const defaultPassword = encrypt(generatePassword());
 | 
				
			||||||
 | 
							const mariadbUser = cuid();
 | 
				
			||||||
 | 
							const mariadbPassword = encrypt(generatePassword());
 | 
				
			||||||
 | 
							const mariadbRootUser = cuid();
 | 
				
			||||||
 | 
							const mariadbRootUserPassword = encrypt(generatePassword());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							await prisma.service.update({
 | 
				
			||||||
 | 
								where: { id },
 | 
				
			||||||
 | 
								data: {
 | 
				
			||||||
 | 
									type,
 | 
				
			||||||
 | 
									ghost: {
 | 
				
			||||||
 | 
										create: {
 | 
				
			||||||
 | 
											defaultEmail,
 | 
				
			||||||
 | 
											defaultPassword,
 | 
				
			||||||
 | 
											mariadbUser,
 | 
				
			||||||
 | 
											mariadbPassword,
 | 
				
			||||||
 | 
											mariadbRootUser,
 | 
				
			||||||
 | 
											mariadbRootUserPassword
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export async function setServiceVersion({ id, version }) {
 | 
					export async function setServiceVersion({ id, version }) {
 | 
				
			||||||
@@ -139,7 +185,7 @@ export async function updatePlausibleAnalyticsService({ id, fqdn, email, usernam
 | 
				
			|||||||
	await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
 | 
						await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
 | 
				
			||||||
	await prisma.service.update({ where: { id }, data: { name, fqdn } });
 | 
						await prisma.service.update({ where: { id }, data: { name, fqdn } });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export async function updateNocoDbOrMinioService({ id, fqdn, name }) {
 | 
					export async function updateService({ id, fqdn, name }) {
 | 
				
			||||||
	return await prisma.service.update({ where: { id }, data: { fqdn, name } });
 | 
						return await prisma.service.update({ where: { id }, data: { fqdn, name } });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export async function updateLanguageToolService({ id, fqdn, name }) {
 | 
					export async function updateLanguageToolService({ id, fqdn, name }) {
 | 
				
			||||||
@@ -160,8 +206,15 @@ export async function updateWordpress({ id, fqdn, name, mysqlDatabase, extraConf
 | 
				
			|||||||
export async function updateMinioService({ id, publicPort }) {
 | 
					export async function updateMinioService({ id, publicPort }) {
 | 
				
			||||||
	return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
 | 
						return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					export async function updateGhostService({ id, fqdn, name, mariadbDatabase }) {
 | 
				
			||||||
 | 
						return await prisma.service.update({
 | 
				
			||||||
 | 
							where: { id },
 | 
				
			||||||
 | 
							data: { fqdn, name, ghost: { update: { mariadbDatabase } } }
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function removeService({ id }) {
 | 
					export async function removeService({ id }) {
 | 
				
			||||||
 | 
						await prisma.ghost.deleteMany({ where: { serviceId: id } });
 | 
				
			||||||
	await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
 | 
						await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
 | 
				
			||||||
	await prisma.minio.deleteMany({ where: { serviceId: id } });
 | 
						await prisma.minio.deleteMany({ where: { serviceId: id } });
 | 
				
			||||||
	await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
 | 
						await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,32 +104,34 @@ export async function generateSSLCerts() {
 | 
				
			|||||||
	});
 | 
						});
 | 
				
			||||||
	for (const application of applications) {
 | 
						for (const application of applications) {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			const {
 | 
								if (application.fqdn && application.destinationDockerId) {
 | 
				
			||||||
				fqdn,
 | 
									const {
 | 
				
			||||||
				id,
 | 
										fqdn,
 | 
				
			||||||
				destinationDocker: { engine, network },
 | 
										id,
 | 
				
			||||||
				settings: { previews }
 | 
										destinationDocker: { engine, network },
 | 
				
			||||||
			} = application;
 | 
										settings: { previews }
 | 
				
			||||||
			const isRunning = await checkContainer(engine, id);
 | 
									} = application;
 | 
				
			||||||
			const domain = getDomain(fqdn);
 | 
									const isRunning = await checkContainer(engine, id);
 | 
				
			||||||
			const isHttps = fqdn.startsWith('https://');
 | 
									const domain = getDomain(fqdn);
 | 
				
			||||||
			if (isRunning) {
 | 
									const isHttps = fqdn.startsWith('https://');
 | 
				
			||||||
				if (isHttps) ssls.push({ domain, id, isCoolify: false });
 | 
									if (isRunning) {
 | 
				
			||||||
			}
 | 
										if (isHttps) ssls.push({ domain, id, isCoolify: false });
 | 
				
			||||||
			if (previews) {
 | 
									}
 | 
				
			||||||
				const host = getEngine(engine);
 | 
									if (previews) {
 | 
				
			||||||
				const { stdout } = await asyncExecShell(
 | 
										const host = getEngine(engine);
 | 
				
			||||||
					`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
 | 
										const { stdout } = await asyncExecShell(
 | 
				
			||||||
				);
 | 
											`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
 | 
				
			||||||
				const containers = stdout
 | 
										);
 | 
				
			||||||
					.trim()
 | 
										const containers = stdout
 | 
				
			||||||
					.split('\n')
 | 
											.trim()
 | 
				
			||||||
					.filter((a) => a)
 | 
											.split('\n')
 | 
				
			||||||
					.map((c) => c.replace(/"/g, ''));
 | 
											.filter((a) => a)
 | 
				
			||||||
				if (containers.length > 0) {
 | 
											.map((c) => c.replace(/"/g, ''));
 | 
				
			||||||
					for (const container of containers) {
 | 
										if (containers.length > 0) {
 | 
				
			||||||
						let previewDomain = `${container.split('-')[1]}.${domain}`;
 | 
											for (const container of containers) {
 | 
				
			||||||
						if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
 | 
												let previewDomain = `${container.split('-')[1]}.${domain}`;
 | 
				
			||||||
 | 
												if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -143,26 +145,29 @@ export async function generateSSLCerts() {
 | 
				
			|||||||
			minio: true,
 | 
								minio: true,
 | 
				
			||||||
			plausibleAnalytics: true,
 | 
								plausibleAnalytics: true,
 | 
				
			||||||
			vscodeserver: true,
 | 
								vscodeserver: true,
 | 
				
			||||||
			wordpress: true
 | 
								wordpress: true,
 | 
				
			||||||
 | 
								ghost: true
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		orderBy: { createdAt: 'desc' }
 | 
							orderBy: { createdAt: 'desc' }
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (const service of services) {
 | 
						for (const service of services) {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			const {
 | 
								if (service.fqdn && service.destinationDockerId) {
 | 
				
			||||||
				fqdn,
 | 
									const {
 | 
				
			||||||
				id,
 | 
										fqdn,
 | 
				
			||||||
				type,
 | 
										id,
 | 
				
			||||||
				destinationDocker: { engine }
 | 
										type,
 | 
				
			||||||
			} = service;
 | 
										destinationDocker: { engine }
 | 
				
			||||||
			const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
 | 
									} = service;
 | 
				
			||||||
			if (found) {
 | 
									const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
 | 
				
			||||||
				const domain = getDomain(fqdn);
 | 
									if (found) {
 | 
				
			||||||
				const isHttps = fqdn.startsWith('https://');
 | 
										const domain = getDomain(fqdn);
 | 
				
			||||||
				const isRunning = await checkContainer(engine, id);
 | 
										const isHttps = fqdn.startsWith('https://');
 | 
				
			||||||
				if (isRunning) {
 | 
										const isRunning = await checkContainer(engine, id);
 | 
				
			||||||
					if (isHttps) ssls.push({ domain, id, isCoolify: false });
 | 
										if (isRunning) {
 | 
				
			||||||
 | 
											if (isHttps) ssls.push({ domain, id, isCoolify: false });
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} catch (error) {
 | 
							} catch (error) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
	export let application;
 | 
						export let application;
 | 
				
			||||||
 | 
						import Select from 'svelte-select';
 | 
				
			||||||
	import { goto } from '$app/navigation';
 | 
						import { goto } from '$app/navigation';
 | 
				
			||||||
	import { page } from '$app/stores';
 | 
						import { page } from '$app/stores';
 | 
				
			||||||
	import { get, post } from '$lib/api';
 | 
						import { get, post } from '$lib/api';
 | 
				
			||||||
@@ -35,6 +35,9 @@
 | 
				
			|||||||
			Authorization: `token ${$gitTokens.githubToken}`
 | 
								Authorization: `token ${$gitTokens.githubToken}`
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let reposSelectOptions;
 | 
				
			||||||
 | 
						let branchSelectOptions;
 | 
				
			||||||
	async function loadRepositories() {
 | 
						async function loadRepositories() {
 | 
				
			||||||
		let page = 1;
 | 
							let page = 1;
 | 
				
			||||||
		let reposCount = 0;
 | 
							let reposCount = 0;
 | 
				
			||||||
@@ -49,8 +52,13 @@
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		loading.repositories = false;
 | 
							loading.repositories = false;
 | 
				
			||||||
 | 
							reposSelectOptions = repositories.map((repo) => ({
 | 
				
			||||||
 | 
								value: repo.full_name,
 | 
				
			||||||
 | 
								label: repo.name
 | 
				
			||||||
 | 
							}));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	async function loadBranches() {
 | 
						async function loadBranches(event) {
 | 
				
			||||||
 | 
							selected.repository = event.detail.value;
 | 
				
			||||||
		loading.branches = true;
 | 
							loading.branches = true;
 | 
				
			||||||
		selected.branch = undefined;
 | 
							selected.branch = undefined;
 | 
				
			||||||
		selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
 | 
							selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
 | 
				
			||||||
@@ -58,6 +66,10 @@
 | 
				
			|||||||
			branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
 | 
								branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
 | 
				
			||||||
				Authorization: `token ${$gitTokens.githubToken}`
 | 
									Authorization: `token ${$gitTokens.githubToken}`
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
								branchSelectOptions = branches.map((branch) => ({
 | 
				
			||||||
 | 
									value: branch.name,
 | 
				
			||||||
 | 
									label: branch.name
 | 
				
			||||||
 | 
								}));
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		} catch ({ error }) {
 | 
							} catch ({ error }) {
 | 
				
			||||||
			return errorNotification(error);
 | 
								return errorNotification(error);
 | 
				
			||||||
@@ -65,7 +77,8 @@
 | 
				
			|||||||
			loading.branches = false;
 | 
								loading.branches = false;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	async function isBranchAlreadyUsed() {
 | 
						async function isBranchAlreadyUsed(event) {
 | 
				
			||||||
 | 
							selected.branch = event.detail.value;
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			const data = await get(
 | 
								const data = await get(
 | 
				
			||||||
				`/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}`
 | 
									`/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}`
 | 
				
			||||||
@@ -153,47 +166,33 @@
 | 
				
			|||||||
{:else}
 | 
					{:else}
 | 
				
			||||||
	<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
 | 
						<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
 | 
				
			||||||
		<div class="flex-col space-y-3 md:space-y-0 space-x-1">
 | 
							<div class="flex-col space-y-3 md:space-y-0 space-x-1">
 | 
				
			||||||
			{#if loading.repositories}
 | 
								<div class="flex gap-4">
 | 
				
			||||||
				<select name="repository" disabled class="w-96">
 | 
									<div class="custom-select-wrapper">
 | 
				
			||||||
					<option selected value="">Loading repositories...</option>
 | 
										<Select
 | 
				
			||||||
				</select>
 | 
											placeholder={loading.repositories
 | 
				
			||||||
			{:else}
 | 
												? 'Loading repositories ...'
 | 
				
			||||||
				<select
 | 
												: 'Please select a repository'}
 | 
				
			||||||
					name="repository"
 | 
											id="repository"
 | 
				
			||||||
					class="w-96"
 | 
											on:select={loadBranches}
 | 
				
			||||||
					bind:value={selected.repository}
 | 
											items={reposSelectOptions}
 | 
				
			||||||
					on:change={loadBranches}
 | 
											isDisabled={loading.repositories}
 | 
				
			||||||
				>
 | 
										/>
 | 
				
			||||||
					<option value="" disabled selected>Please select a repository</option>
 | 
									</div>
 | 
				
			||||||
					{#each repositories as repository}
 | 
									<input class="hidden" bind:value={selected.projectId} name="projectId" />
 | 
				
			||||||
						<option value={repository.full_name}>{repository.name}</option>
 | 
									<div class="custom-select-wrapper">
 | 
				
			||||||
					{/each}
 | 
										<Select
 | 
				
			||||||
				</select>
 | 
											placeholder={loading.branches
 | 
				
			||||||
			{/if}
 | 
												? 'Loading branches ...'
 | 
				
			||||||
			<input class="hidden" bind:value={selected.projectId} name="projectId" />
 | 
												: !selected.repository
 | 
				
			||||||
			{#if loading.branches}
 | 
												? 'Please select a repository first'
 | 
				
			||||||
				<select name="branch" disabled class="w-96">
 | 
												: 'Please select a branch'}
 | 
				
			||||||
					<option selected value="">Loading branches...</option>
 | 
											id="repository"
 | 
				
			||||||
				</select>
 | 
											on:select={isBranchAlreadyUsed}
 | 
				
			||||||
			{:else}
 | 
											items={branchSelectOptions}
 | 
				
			||||||
				<select
 | 
											isDisabled={loading.branches || !selected.repository}
 | 
				
			||||||
					name="branch"
 | 
										/>
 | 
				
			||||||
					class="w-96"
 | 
									</div>
 | 
				
			||||||
					disabled={!selected.repository}
 | 
								</div>
 | 
				
			||||||
					bind:value={selected.branch}
 | 
					 | 
				
			||||||
					on:change={isBranchAlreadyUsed}
 | 
					 | 
				
			||||||
				>
 | 
					 | 
				
			||||||
					{#if !selected.repository}
 | 
					 | 
				
			||||||
						<option value="" disabled selected>Select a repository first</option>
 | 
					 | 
				
			||||||
					{:else}
 | 
					 | 
				
			||||||
						<option value="" disabled selected>Please select a branch</option>
 | 
					 | 
				
			||||||
					{/if}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					{#each branches as branch}
 | 
					 | 
				
			||||||
						<option value={branch.name}>{branch.name}</option>
 | 
					 | 
				
			||||||
					{/each}
 | 
					 | 
				
			||||||
				</select>
 | 
					 | 
				
			||||||
			{/if}
 | 
					 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div class="pt-5 flex-col flex justify-center items-center space-y-4">
 | 
							<div class="pt-5 flex-col flex justify-center items-center space-y-4">
 | 
				
			||||||
			<button
 | 
								<button
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -103,7 +103,7 @@
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	async function forceRestartProxy() {
 | 
						async function forceRestartProxy() {
 | 
				
			||||||
		const sure = confirm(
 | 
							const sure = confirm(
 | 
				
			||||||
			'Are you sure you want to restart the proxy? Everyting will be reconfigured in ~10 sec.'
 | 
								'Are you sure you want to restart the proxy? Everything will be reconfigured in ~10 secs.'
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
		if (sure) {
 | 
							if (sure) {
 | 
				
			||||||
			try {
 | 
								try {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,7 +106,7 @@
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	async function forceRestartProxy() {
 | 
						async function forceRestartProxy() {
 | 
				
			||||||
		const sure = confirm(
 | 
							const sure = confirm(
 | 
				
			||||||
			'Are you sure you want to restart the proxy? Everyting will be reconfigured in ~10 sec.'
 | 
								'Are you sure you want to restart the proxy? Everything will be reconfigured in ~10 secs.'
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
		if (sure) {
 | 
							if (sure) {
 | 
				
			||||||
			try {
 | 
								try {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										90
									
								
								src/routes/services/[id]/_Services/_Ghost.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/routes/services/[id]/_Services/_Ghost.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
						import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
 | 
				
			||||||
 | 
						export let readOnly;
 | 
				
			||||||
 | 
						export let service;
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="flex space-x-1 py-5 font-bold">
 | 
				
			||||||
 | 
						<div class="title">Ghost</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
 | 
						<label for="email">Default Email Address</label>
 | 
				
			||||||
 | 
						<input
 | 
				
			||||||
 | 
							name="email"
 | 
				
			||||||
 | 
							id="email"
 | 
				
			||||||
 | 
							disabled
 | 
				
			||||||
 | 
							readonly
 | 
				
			||||||
 | 
							placeholder="Email address"
 | 
				
			||||||
 | 
							value={service.ghost.defaultEmail}
 | 
				
			||||||
 | 
							required
 | 
				
			||||||
 | 
						/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
 | 
						<label for="defaultPassword">Default Password</label>
 | 
				
			||||||
 | 
						<CopyPasswordField
 | 
				
			||||||
 | 
							id="defaultPassword"
 | 
				
			||||||
 | 
							isPasswordField
 | 
				
			||||||
 | 
							readonly
 | 
				
			||||||
 | 
							disabled
 | 
				
			||||||
 | 
							name="defaultPassword"
 | 
				
			||||||
 | 
							value={service.ghost.defaultPassword}
 | 
				
			||||||
 | 
						/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<div class="flex space-x-1 py-5 font-bold">
 | 
				
			||||||
 | 
						<div class="title">MariaDB</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
 | 
						<label for="mariadbUser">Username</label>
 | 
				
			||||||
 | 
						<CopyPasswordField
 | 
				
			||||||
 | 
							name="mariadbUser"
 | 
				
			||||||
 | 
							id="mariadbUser"
 | 
				
			||||||
 | 
							value={service.ghost.mariadbUser}
 | 
				
			||||||
 | 
							readonly
 | 
				
			||||||
 | 
							disabled
 | 
				
			||||||
 | 
						/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
 | 
						<label for="mariadbPassword">Password</label>
 | 
				
			||||||
 | 
						<CopyPasswordField
 | 
				
			||||||
 | 
							id="mariadbPassword"
 | 
				
			||||||
 | 
							isPasswordField
 | 
				
			||||||
 | 
							readonly
 | 
				
			||||||
 | 
							disabled
 | 
				
			||||||
 | 
							name="mariadbPassword"
 | 
				
			||||||
 | 
							value={service.ghost.mariadbPassword}
 | 
				
			||||||
 | 
						/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
 | 
						<label for="mariadbDatabase">Database</label>
 | 
				
			||||||
 | 
						<input
 | 
				
			||||||
 | 
							name="mariadbDatabase"
 | 
				
			||||||
 | 
							id="mariadbDatabase"
 | 
				
			||||||
 | 
							required
 | 
				
			||||||
 | 
							readonly={readOnly}
 | 
				
			||||||
 | 
							disabled={readOnly}
 | 
				
			||||||
 | 
							bind:value={service.ghost.mariadbDatabase}
 | 
				
			||||||
 | 
							placeholder="eg: ghost_db"
 | 
				
			||||||
 | 
						/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
 | 
						<label for="mariadbRootUser">Root DB User</label>
 | 
				
			||||||
 | 
						<CopyPasswordField
 | 
				
			||||||
 | 
							id="mariadbRootUser"
 | 
				
			||||||
 | 
							isPasswordField
 | 
				
			||||||
 | 
							readonly
 | 
				
			||||||
 | 
							disabled
 | 
				
			||||||
 | 
							name="mariadbRootUser"
 | 
				
			||||||
 | 
							value={service.ghost.mariadbRootUser}
 | 
				
			||||||
 | 
						/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<div class="grid grid-cols-2 items-center px-10">
 | 
				
			||||||
 | 
						<label for="mariadbRootUserPassword">Root DB Password</label>
 | 
				
			||||||
 | 
						<CopyPasswordField
 | 
				
			||||||
 | 
							id="mariadbRootUserPassword"
 | 
				
			||||||
 | 
							isPasswordField
 | 
				
			||||||
 | 
							readonly
 | 
				
			||||||
 | 
							disabled
 | 
				
			||||||
 | 
							name="mariadbRootUserPassword"
 | 
				
			||||||
 | 
							value={service.ghost.mariadbRootUserPassword}
 | 
				
			||||||
 | 
						/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@@ -10,6 +10,7 @@
 | 
				
			|||||||
	import Setting from '$lib/components/Setting.svelte';
 | 
						import Setting from '$lib/components/Setting.svelte';
 | 
				
			||||||
	import { errorNotification } from '$lib/form';
 | 
						import { errorNotification } from '$lib/form';
 | 
				
			||||||
	import { toast } from '@zerodevx/svelte-toast';
 | 
						import { toast } from '@zerodevx/svelte-toast';
 | 
				
			||||||
 | 
						import Ghost from './_Ghost.svelte';
 | 
				
			||||||
	import MinIo from './_MinIO.svelte';
 | 
						import MinIo from './_MinIO.svelte';
 | 
				
			||||||
	import PlausibleAnalytics from './_PlausibleAnalytics.svelte';
 | 
						import PlausibleAnalytics from './_PlausibleAnalytics.svelte';
 | 
				
			||||||
	import VsCodeServer from './_VSCodeServer.svelte';
 | 
						import VsCodeServer from './_VSCodeServer.svelte';
 | 
				
			||||||
@@ -142,6 +143,8 @@
 | 
				
			|||||||
				<VsCodeServer {service} />
 | 
									<VsCodeServer {service} />
 | 
				
			||||||
			{:else if service.type === 'wordpress'}
 | 
								{:else if service.type === 'wordpress'}
 | 
				
			||||||
				<Wordpress bind:service {isRunning} {readOnly} />
 | 
									<Wordpress bind:service {isRunning} {readOnly} />
 | 
				
			||||||
 | 
								{:else if service.type === 'ghost'}
 | 
				
			||||||
 | 
									<Ghost bind:service {readOnly} />
 | 
				
			||||||
			{/if}
 | 
								{/if}
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</form>
 | 
						</form>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,7 @@
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			if (service.plausibleAnalytics?.email && service.plausibleAnalytics.username) readOnly = true;
 | 
								if (service.plausibleAnalytics?.email && service.plausibleAnalytics.username) readOnly = true;
 | 
				
			||||||
			if (service.wordpress?.mysqlDatabase) readOnly = true;
 | 
								if (service.wordpress?.mysqlDatabase) readOnly = true;
 | 
				
			||||||
 | 
								if (service.ghost?.mariadbDatabase && service.ghost.mariadbDatabase) readOnly = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				props: {
 | 
									props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,6 +38,9 @@
 | 
				
			|||||||
	import { post } from '$lib/api';
 | 
						import { post } from '$lib/api';
 | 
				
			||||||
	import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
 | 
						import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
 | 
				
			||||||
	import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
 | 
						import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
 | 
				
			||||||
 | 
						import N8n from '$lib/components/svg/services/N8n.svelte';
 | 
				
			||||||
 | 
						import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
 | 
				
			||||||
 | 
						import Ghost from '$lib/components/svg/services/Ghost.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const { id } = $page.params;
 | 
						const { id } = $page.params;
 | 
				
			||||||
	const from = $page.url.searchParams.get('from');
 | 
						const from = $page.url.searchParams.get('from');
 | 
				
			||||||
@@ -77,6 +80,12 @@
 | 
				
			|||||||
						<VaultWarden isAbsolute />
 | 
											<VaultWarden isAbsolute />
 | 
				
			||||||
					{:else if type.name === 'languagetool'}
 | 
										{:else if type.name === 'languagetool'}
 | 
				
			||||||
						<LanguageTool isAbsolute />
 | 
											<LanguageTool isAbsolute />
 | 
				
			||||||
 | 
										{:else if type.name === 'n8n'}
 | 
				
			||||||
 | 
											<N8n isAbsolute />
 | 
				
			||||||
 | 
										{:else if type.name === 'uptimekuma'}
 | 
				
			||||||
 | 
											<UptimeKuma isAbsolute />
 | 
				
			||||||
 | 
										{:else if type.name === 'ghost'}
 | 
				
			||||||
 | 
											<Ghost isAbsolute />
 | 
				
			||||||
					{/if}{type.fancyName}
 | 
										{/if}{type.fancyName}
 | 
				
			||||||
				</button>
 | 
									</button>
 | 
				
			||||||
			</form>
 | 
								</form>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								src/routes/services/[id]/ghost/index.json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/routes/services/[id]/ghost/index.json.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					import { getUserDetails } from '$lib/common';
 | 
				
			||||||
 | 
					import * as db from '$lib/database';
 | 
				
			||||||
 | 
					import { ErrorHandler } from '$lib/database';
 | 
				
			||||||
 | 
					import type { RequestHandler } from '@sveltejs/kit';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const post: RequestHandler = async (event) => {
 | 
				
			||||||
 | 
						const { status, body } = await getUserDetails(event);
 | 
				
			||||||
 | 
						if (status === 401) return { status, body };
 | 
				
			||||||
 | 
						const { id } = event.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let {
 | 
				
			||||||
 | 
							name,
 | 
				
			||||||
 | 
							fqdn,
 | 
				
			||||||
 | 
							ghost: { mariadbDatabase }
 | 
				
			||||||
 | 
						} = await event.request.json();
 | 
				
			||||||
 | 
						if (fqdn) fqdn = fqdn.toLowerCase();
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							await db.updateGhostService({ id, fqdn, name, mariadbDatabase });
 | 
				
			||||||
 | 
							return { status: 201 };
 | 
				
			||||||
 | 
						} catch (error) {
 | 
				
			||||||
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										133
									
								
								src/routes/services/[id]/ghost/start.json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/routes/services/[id]/ghost/start.json.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
						asyncExecShell,
 | 
				
			||||||
 | 
						createDirectories,
 | 
				
			||||||
 | 
						getDomain,
 | 
				
			||||||
 | 
						getEngine,
 | 
				
			||||||
 | 
						getUserDetails
 | 
				
			||||||
 | 
					} from '$lib/common';
 | 
				
			||||||
 | 
					import * as db from '$lib/database';
 | 
				
			||||||
 | 
					import { promises as fs } from 'fs';
 | 
				
			||||||
 | 
					import yaml from 'js-yaml';
 | 
				
			||||||
 | 
					import type { RequestHandler } from '@sveltejs/kit';
 | 
				
			||||||
 | 
					import { ErrorHandler, getServiceImage } from '$lib/database';
 | 
				
			||||||
 | 
					import { makeLabelForServices } from '$lib/buildPacks/common';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const post: RequestHandler = async (event) => {
 | 
				
			||||||
 | 
						const { teamId, status, body } = await getUserDetails(event);
 | 
				
			||||||
 | 
						if (status === 401) return { status, body };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const { id } = event.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							const service = await db.getService({ id, teamId });
 | 
				
			||||||
 | 
							const {
 | 
				
			||||||
 | 
								type,
 | 
				
			||||||
 | 
								version,
 | 
				
			||||||
 | 
								destinationDockerId,
 | 
				
			||||||
 | 
								destinationDocker,
 | 
				
			||||||
 | 
								serviceSecret,
 | 
				
			||||||
 | 
								fqdn,
 | 
				
			||||||
 | 
								ghost: {
 | 
				
			||||||
 | 
									defaultEmail,
 | 
				
			||||||
 | 
									defaultPassword,
 | 
				
			||||||
 | 
									mariadbRootUser,
 | 
				
			||||||
 | 
									mariadbRootUserPassword,
 | 
				
			||||||
 | 
									mariadbDatabase,
 | 
				
			||||||
 | 
									mariadbPassword,
 | 
				
			||||||
 | 
									mariadbUser
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} = service;
 | 
				
			||||||
 | 
							const network = destinationDockerId && destinationDocker.network;
 | 
				
			||||||
 | 
							const host = getEngine(destinationDocker.engine);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const { workdir } = await createDirectories({ repository: type, buildId: id });
 | 
				
			||||||
 | 
							const image = getServiceImage(type);
 | 
				
			||||||
 | 
							const domain = getDomain(fqdn);
 | 
				
			||||||
 | 
							const config = {
 | 
				
			||||||
 | 
								ghost: {
 | 
				
			||||||
 | 
									image: `${image}:${version}`,
 | 
				
			||||||
 | 
									volume: `${id}-ghost:/bitnami/ghost`,
 | 
				
			||||||
 | 
									environmentVariables: {
 | 
				
			||||||
 | 
										GHOST_HOST: domain,
 | 
				
			||||||
 | 
										GHOST_EMAIL: defaultEmail,
 | 
				
			||||||
 | 
										GHOST_PASSWORD: defaultPassword,
 | 
				
			||||||
 | 
										GHOST_DATABASE_HOST: `${id}-mariadb`,
 | 
				
			||||||
 | 
										GHOST_DATABASE_USER: mariadbUser,
 | 
				
			||||||
 | 
										GHOST_DATABASE_PASSWORD: mariadbPassword,
 | 
				
			||||||
 | 
										GHOST_DATABASE_NAME: mariadbDatabase,
 | 
				
			||||||
 | 
										GHOST_DATABASE_PORT_NUMBER: 3306
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								mariadb: {
 | 
				
			||||||
 | 
									image: `bitnami/mariadb:latest`,
 | 
				
			||||||
 | 
									volume: `${id}-mariadb:/bitnami/mariadb`,
 | 
				
			||||||
 | 
									environmentVariables: {
 | 
				
			||||||
 | 
										MARIADB_USER: mariadbUser,
 | 
				
			||||||
 | 
										MARIADB_PASSWORD: mariadbPassword,
 | 
				
			||||||
 | 
										MARIADB_DATABASE: mariadbDatabase,
 | 
				
			||||||
 | 
										MARIADB_ROOT_USER: mariadbRootUser,
 | 
				
			||||||
 | 
										MARIADB_ROOT_PASSWORD: mariadbRootUserPassword
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							if (serviceSecret.length > 0) {
 | 
				
			||||||
 | 
								serviceSecret.forEach((secret) => {
 | 
				
			||||||
 | 
									config.ghost.environmentVariables[secret.name] = secret.value;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							const composeFile = {
 | 
				
			||||||
 | 
								version: '3.8',
 | 
				
			||||||
 | 
								services: {
 | 
				
			||||||
 | 
									[id]: {
 | 
				
			||||||
 | 
										container_name: id,
 | 
				
			||||||
 | 
										image: config.ghost.image,
 | 
				
			||||||
 | 
										networks: [network],
 | 
				
			||||||
 | 
										volumes: [config.ghost.volume],
 | 
				
			||||||
 | 
										environment: config.ghost.environmentVariables,
 | 
				
			||||||
 | 
										restart: 'always',
 | 
				
			||||||
 | 
										labels: makeLabelForServices('ghost'),
 | 
				
			||||||
 | 
										depends_on: [`${id}-mariadb`]
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									[`${id}-mariadb`]: {
 | 
				
			||||||
 | 
										container_name: `${id}-mariadb`,
 | 
				
			||||||
 | 
										image: config.mariadb.image,
 | 
				
			||||||
 | 
										networks: [network],
 | 
				
			||||||
 | 
										volumes: [config.mariadb.volume],
 | 
				
			||||||
 | 
										environment: config.mariadb.environmentVariables,
 | 
				
			||||||
 | 
										restart: 'always'
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								networks: {
 | 
				
			||||||
 | 
									[network]: {
 | 
				
			||||||
 | 
										external: true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								volumes: {
 | 
				
			||||||
 | 
									[config.ghost.volume.split(':')[0]]: {
 | 
				
			||||||
 | 
										name: config.ghost.volume.split(':')[0]
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									[config.mariadb.volume.split(':')[0]]: {
 | 
				
			||||||
 | 
										name: config.mariadb.volume.split(':')[0]
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							const composeFileDestination = `${workdir}/docker-compose.yaml`;
 | 
				
			||||||
 | 
							await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								if (version === 'latest') {
 | 
				
			||||||
 | 
									await asyncExecShell(
 | 
				
			||||||
 | 
										`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									status: 200
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							} catch (error) {
 | 
				
			||||||
 | 
								return ErrorHandler(error);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} catch (error) {
 | 
				
			||||||
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										39
									
								
								src/routes/services/[id]/ghost/stop.json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/routes/services/[id]/ghost/stop.json.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					import { getUserDetails, removeDestinationDocker } from '$lib/common';
 | 
				
			||||||
 | 
					import * as db from '$lib/database';
 | 
				
			||||||
 | 
					import { ErrorHandler } from '$lib/database';
 | 
				
			||||||
 | 
					import { checkContainer } from '$lib/haproxy';
 | 
				
			||||||
 | 
					import type { RequestHandler } from '@sveltejs/kit';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const post: RequestHandler = async (event) => {
 | 
				
			||||||
 | 
						const { teamId, status, body } = await getUserDetails(event);
 | 
				
			||||||
 | 
						if (status === 401) return { status, body };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const { id } = event.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							const service = await db.getService({ id, teamId });
 | 
				
			||||||
 | 
							const { destinationDockerId, destinationDocker, fqdn } = service;
 | 
				
			||||||
 | 
							if (destinationDockerId) {
 | 
				
			||||||
 | 
								const engine = destinationDocker.engine;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									let found = await checkContainer(engine, id);
 | 
				
			||||||
 | 
									if (found) {
 | 
				
			||||||
 | 
										await removeDestinationDocker({ id, engine });
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									found = await checkContainer(engine, `${id}-mariadb`);
 | 
				
			||||||
 | 
									if (found) {
 | 
				
			||||||
 | 
										await removeDestinationDocker({ id: `${id}-mariadb`, engine });
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} catch (error) {
 | 
				
			||||||
 | 
									console.error(error);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								status: 200
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} catch (error) {
 | 
				
			||||||
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -4,7 +4,8 @@ import {
 | 
				
			|||||||
	generateDatabaseConfiguration,
 | 
						generateDatabaseConfiguration,
 | 
				
			||||||
	getServiceImage,
 | 
						getServiceImage,
 | 
				
			||||||
	getVersions,
 | 
						getVersions,
 | 
				
			||||||
	ErrorHandler
 | 
						ErrorHandler,
 | 
				
			||||||
 | 
						getServiceImages
 | 
				
			||||||
} from '$lib/database';
 | 
					} from '$lib/database';
 | 
				
			||||||
import { dockerInstance } from '$lib/docker';
 | 
					import { dockerInstance } from '$lib/docker';
 | 
				
			||||||
import type { RequestHandler } from '@sveltejs/kit';
 | 
					import type { RequestHandler } from '@sveltejs/kit';
 | 
				
			||||||
@@ -23,7 +24,13 @@ export const get: RequestHandler = async (event) => {
 | 
				
			|||||||
			const host = getEngine(destinationDocker.engine);
 | 
								const host = getEngine(destinationDocker.engine);
 | 
				
			||||||
			const docker = dockerInstance({ destinationDocker });
 | 
								const docker = dockerInstance({ destinationDocker });
 | 
				
			||||||
			const baseImage = getServiceImage(type);
 | 
								const baseImage = getServiceImage(type);
 | 
				
			||||||
 | 
								const images = getServiceImages(type);
 | 
				
			||||||
			docker.engine.pull(`${baseImage}:${version}`);
 | 
								docker.engine.pull(`${baseImage}:${version}`);
 | 
				
			||||||
 | 
								if (images?.length > 0) {
 | 
				
			||||||
 | 
									for (const image of images) {
 | 
				
			||||||
 | 
										docker.engine.pull(`${image}:latest`);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			try {
 | 
								try {
 | 
				
			||||||
				const { stdout } = await asyncExecShell(
 | 
									const { stdout } = await asyncExecShell(
 | 
				
			||||||
					`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
 | 
										`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,6 +39,9 @@
 | 
				
			|||||||
	import cuid from 'cuid';
 | 
						import cuid from 'cuid';
 | 
				
			||||||
	import { browser } from '$app/env';
 | 
						import { browser } from '$app/env';
 | 
				
			||||||
	import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
 | 
						import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
 | 
				
			||||||
 | 
						import N8n from '$lib/components/svg/services/N8n.svelte';
 | 
				
			||||||
 | 
						import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
 | 
				
			||||||
 | 
						import Ghost from '$lib/components/svg/services/Ghost.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export let service;
 | 
						export let service;
 | 
				
			||||||
	export let isRunning;
 | 
						export let isRunning;
 | 
				
			||||||
@@ -109,6 +112,18 @@
 | 
				
			|||||||
			<a href="https://languagetool.org/dev" target="_blank">
 | 
								<a href="https://languagetool.org/dev" target="_blank">
 | 
				
			||||||
				<LanguageTool />
 | 
									<LanguageTool />
 | 
				
			||||||
			</a>
 | 
								</a>
 | 
				
			||||||
 | 
							{:else if service.type === 'n8n'}
 | 
				
			||||||
 | 
								<a href="https://n8n.io" target="_blank">
 | 
				
			||||||
 | 
									<N8n />
 | 
				
			||||||
 | 
								</a>
 | 
				
			||||||
 | 
							{:else if service.type === 'uptimekuma'}
 | 
				
			||||||
 | 
								<a href="https://github.com/louislam/uptime-kuma" target="_blank">
 | 
				
			||||||
 | 
									<UptimeKuma />
 | 
				
			||||||
 | 
								</a>
 | 
				
			||||||
 | 
							{:else if service.type === 'ghost'}
 | 
				
			||||||
 | 
								<a href="https://ghost.org" target="_blank">
 | 
				
			||||||
 | 
									<Ghost />
 | 
				
			||||||
 | 
								</a>
 | 
				
			||||||
		{/if}
 | 
							{/if}
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,7 +41,7 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
					networks: [network],
 | 
										networks: [network],
 | 
				
			||||||
					environment: config.environmentVariables,
 | 
										environment: config.environmentVariables,
 | 
				
			||||||
					restart: 'always',
 | 
										restart: 'always',
 | 
				
			||||||
					volumes: [`${id}-ngrams:/ngrams`],
 | 
										volumes: [config.volume],
 | 
				
			||||||
					labels: makeLabelForServices('languagetool')
 | 
										labels: makeLabelForServices('languagetool')
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -51,20 +51,20 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			volumes: {
 | 
								volumes: {
 | 
				
			||||||
				[`${id}-ngrams`]: {
 | 
									[config.volume.split(':')[0]]: {
 | 
				
			||||||
					external: true
 | 
										name: config.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 {
 | 
					 | 
				
			||||||
			await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}-ngrams`);
 | 
					 | 
				
			||||||
		} catch (error) {
 | 
					 | 
				
			||||||
			console.log(error);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
 | 
								if (version === 'latest') {
 | 
				
			||||||
 | 
									await asyncExecShell(
 | 
				
			||||||
 | 
										`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
								await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				status: 200
 | 
									status: 200
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
	if (fqdn) fqdn = fqdn.toLowerCase();
 | 
						if (fqdn) fqdn = fqdn.toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	try {
 | 
						try {
 | 
				
			||||||
		await db.updateNocoDbOrMinioService({ id, fqdn, name });
 | 
							await db.updateService({ id, fqdn, name });
 | 
				
			||||||
		return { status: 201 };
 | 
							return { status: 201 };
 | 
				
			||||||
	} catch (error) {
 | 
						} catch (error) {
 | 
				
			||||||
		return ErrorHandler(error);
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -76,19 +76,13 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			volumes: {
 | 
								volumes: {
 | 
				
			||||||
				[config.volume.split(':')[0]]: {
 | 
									[config.volume.split(':')[0]]: {
 | 
				
			||||||
					external: true
 | 
										name: config.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 {
 | 
					
 | 
				
			||||||
			await asyncExecShell(
 | 
					 | 
				
			||||||
				`DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}`
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
		} catch (error) {
 | 
					 | 
				
			||||||
			console.log(error);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
								await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
				
			||||||
			await db.updateMinioService({ id, publicPort });
 | 
								await db.updateMinioService({ id, publicPort });
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								src/routes/services/[id]/n8n/index.json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/routes/services/[id]/n8n/index.json.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					import { getUserDetails } from '$lib/common';
 | 
				
			||||||
 | 
					import * as db from '$lib/database';
 | 
				
			||||||
 | 
					import { ErrorHandler } from '$lib/database';
 | 
				
			||||||
 | 
					import type { RequestHandler } from '@sveltejs/kit';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const post: RequestHandler = async (event) => {
 | 
				
			||||||
 | 
						const { status, body } = await getUserDetails(event);
 | 
				
			||||||
 | 
						if (status === 401) return { status, body };
 | 
				
			||||||
 | 
						const { id } = event.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let { name, fqdn } = await event.request.json();
 | 
				
			||||||
 | 
						if (fqdn) fqdn = fqdn.toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							await db.updateService({ id, fqdn, name });
 | 
				
			||||||
 | 
							return { status: 201 };
 | 
				
			||||||
 | 
						} catch (error) {
 | 
				
			||||||
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/routes/services/[id]/n8n/start.json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/routes/services/[id]/n8n/start.json.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
 | 
				
			||||||
 | 
					import * as db from '$lib/database';
 | 
				
			||||||
 | 
					import { promises as fs } from 'fs';
 | 
				
			||||||
 | 
					import yaml from 'js-yaml';
 | 
				
			||||||
 | 
					import type { RequestHandler } from '@sveltejs/kit';
 | 
				
			||||||
 | 
					import { ErrorHandler, getServiceImage } from '$lib/database';
 | 
				
			||||||
 | 
					import { makeLabelForServices } from '$lib/buildPacks/common';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const post: RequestHandler = async (event) => {
 | 
				
			||||||
 | 
						const { teamId, status, body } = await getUserDetails(event);
 | 
				
			||||||
 | 
						if (status === 401) return { status, body };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const { id } = event.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							const service = await db.getService({ id, teamId });
 | 
				
			||||||
 | 
							const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
 | 
				
			||||||
 | 
							const network = destinationDockerId && destinationDocker.network;
 | 
				
			||||||
 | 
							const host = getEngine(destinationDocker.engine);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const { workdir } = await createDirectories({ repository: type, buildId: id });
 | 
				
			||||||
 | 
							const image = getServiceImage(type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const config = {
 | 
				
			||||||
 | 
								image: `${image}:${version}`,
 | 
				
			||||||
 | 
								volume: `${id}-n8n:/root/.n8n`,
 | 
				
			||||||
 | 
								environmentVariables: {}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							if (serviceSecret.length > 0) {
 | 
				
			||||||
 | 
								serviceSecret.forEach((secret) => {
 | 
				
			||||||
 | 
									config.environmentVariables[secret.name] = secret.value;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							const composeFile = {
 | 
				
			||||||
 | 
								version: '3.8',
 | 
				
			||||||
 | 
								services: {
 | 
				
			||||||
 | 
									[id]: {
 | 
				
			||||||
 | 
										container_name: id,
 | 
				
			||||||
 | 
										image: config.image,
 | 
				
			||||||
 | 
										networks: [network],
 | 
				
			||||||
 | 
										volumes: [config.volume],
 | 
				
			||||||
 | 
										environment: config.environmentVariables,
 | 
				
			||||||
 | 
										restart: 'always',
 | 
				
			||||||
 | 
										labels: makeLabelForServices('n8n')
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								networks: {
 | 
				
			||||||
 | 
									[network]: {
 | 
				
			||||||
 | 
										external: true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								volumes: {
 | 
				
			||||||
 | 
									[config.volume.split(':')[0]]: {
 | 
				
			||||||
 | 
										name: config.volume.split(':')[0]
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							const composeFileDestination = `${workdir}/docker-compose.yaml`;
 | 
				
			||||||
 | 
							await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								if (version === 'latest') {
 | 
				
			||||||
 | 
									await asyncExecShell(
 | 
				
			||||||
 | 
										`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									status: 200
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							} catch (error) {
 | 
				
			||||||
 | 
								return ErrorHandler(error);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} catch (error) {
 | 
				
			||||||
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										35
									
								
								src/routes/services/[id]/n8n/stop.json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/routes/services/[id]/n8n/stop.json.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import { getUserDetails, removeDestinationDocker } from '$lib/common';
 | 
				
			||||||
 | 
					import * as db from '$lib/database';
 | 
				
			||||||
 | 
					import { ErrorHandler } from '$lib/database';
 | 
				
			||||||
 | 
					import { checkContainer } from '$lib/haproxy';
 | 
				
			||||||
 | 
					import type { RequestHandler } from '@sveltejs/kit';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const post: RequestHandler = async (event) => {
 | 
				
			||||||
 | 
						const { teamId, status, body } = await getUserDetails(event);
 | 
				
			||||||
 | 
						if (status === 401) return { status, body };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const { id } = event.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							const service = await db.getService({ id, teamId });
 | 
				
			||||||
 | 
							const { destinationDockerId, destinationDocker, fqdn } = service;
 | 
				
			||||||
 | 
							if (destinationDockerId) {
 | 
				
			||||||
 | 
								const engine = destinationDocker.engine;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									const found = await checkContainer(engine, id);
 | 
				
			||||||
 | 
									if (found) {
 | 
				
			||||||
 | 
										await removeDestinationDocker({ id, engine });
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} catch (error) {
 | 
				
			||||||
 | 
									console.error(error);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								status: 200
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} catch (error) {
 | 
				
			||||||
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -12,7 +12,7 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
	if (fqdn) fqdn = fqdn.toLowerCase();
 | 
						if (fqdn) fqdn = fqdn.toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	try {
 | 
						try {
 | 
				
			||||||
		await db.updateNocoDbOrMinioService({ id, fqdn, name });
 | 
							await db.updateService({ id, fqdn, name });
 | 
				
			||||||
		return { status: 201 };
 | 
							return { status: 201 };
 | 
				
			||||||
	} catch (error) {
 | 
						} catch (error) {
 | 
				
			||||||
		return ErrorHandler(error);
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,6 +52,11 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
		await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
 | 
							await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
 | 
								if (version === 'latest') {
 | 
				
			||||||
 | 
									await asyncExecShell(
 | 
				
			||||||
 | 
										`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
								await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				status: 200
 | 
									status: 200
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -158,29 +158,21 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			volumes: {
 | 
								volumes: {
 | 
				
			||||||
				[config.postgresql.volume.split(':')[0]]: {
 | 
									[config.postgresql.volume.split(':')[0]]: {
 | 
				
			||||||
					external: true
 | 
										name: config.postgresql.volume.split(':')[0]
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
				[config.clickhouse.volume.split(':')[0]]: {
 | 
									[config.clickhouse.volume.split(':')[0]]: {
 | 
				
			||||||
					external: true
 | 
										name: config.clickhouse.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 {
 | 
							if (version === 'latest') {
 | 
				
			||||||
			await asyncExecShell(
 | 
								await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
 | 
				
			||||||
				`DOCKER_HOST=${host} docker volume create ${config.postgresql.volume.split(':')[0]}`
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			await asyncExecShell(
 | 
					 | 
				
			||||||
				`DOCKER_HOST=${host} docker volume create ${config.clickhouse.volume.split(':')[0]}`
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
		} catch (error) {
 | 
					 | 
				
			||||||
			console.log(error);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		await asyncExecShell(
 | 
							await asyncExecShell(
 | 
				
			||||||
			`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
 | 
								`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
 | 
					 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			status: 200
 | 
								status: 200
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								src/routes/services/[id]/uptimekuma/index.json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/routes/services/[id]/uptimekuma/index.json.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					import { getUserDetails } from '$lib/common';
 | 
				
			||||||
 | 
					import * as db from '$lib/database';
 | 
				
			||||||
 | 
					import { ErrorHandler } from '$lib/database';
 | 
				
			||||||
 | 
					import type { RequestHandler } from '@sveltejs/kit';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const post: RequestHandler = async (event) => {
 | 
				
			||||||
 | 
						const { status, body } = await getUserDetails(event);
 | 
				
			||||||
 | 
						if (status === 401) return { status, body };
 | 
				
			||||||
 | 
						const { id } = event.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let { name, fqdn } = await event.request.json();
 | 
				
			||||||
 | 
						if (fqdn) fqdn = fqdn.toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							await db.updateService({ id, fqdn, name });
 | 
				
			||||||
 | 
							return { status: 201 };
 | 
				
			||||||
 | 
						} catch (error) {
 | 
				
			||||||
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/routes/services/[id]/uptimekuma/start.json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/routes/services/[id]/uptimekuma/start.json.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
 | 
				
			||||||
 | 
					import * as db from '$lib/database';
 | 
				
			||||||
 | 
					import { promises as fs } from 'fs';
 | 
				
			||||||
 | 
					import yaml from 'js-yaml';
 | 
				
			||||||
 | 
					import type { RequestHandler } from '@sveltejs/kit';
 | 
				
			||||||
 | 
					import { ErrorHandler, getServiceImage } from '$lib/database';
 | 
				
			||||||
 | 
					import { makeLabelForServices } from '$lib/buildPacks/common';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const post: RequestHandler = async (event) => {
 | 
				
			||||||
 | 
						const { teamId, status, body } = await getUserDetails(event);
 | 
				
			||||||
 | 
						if (status === 401) return { status, body };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const { id } = event.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							const service = await db.getService({ id, teamId });
 | 
				
			||||||
 | 
							const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
 | 
				
			||||||
 | 
							const network = destinationDockerId && destinationDocker.network;
 | 
				
			||||||
 | 
							const host = getEngine(destinationDocker.engine);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const { workdir } = await createDirectories({ repository: type, buildId: id });
 | 
				
			||||||
 | 
							const image = getServiceImage(type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const config = {
 | 
				
			||||||
 | 
								image: `${image}:${version}`,
 | 
				
			||||||
 | 
								volume: `${id}-uptimekuma:/app/data`,
 | 
				
			||||||
 | 
								environmentVariables: {}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							if (serviceSecret.length > 0) {
 | 
				
			||||||
 | 
								serviceSecret.forEach((secret) => {
 | 
				
			||||||
 | 
									config.environmentVariables[secret.name] = secret.value;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							const composeFile = {
 | 
				
			||||||
 | 
								version: '3.8',
 | 
				
			||||||
 | 
								services: {
 | 
				
			||||||
 | 
									[id]: {
 | 
				
			||||||
 | 
										container_name: id,
 | 
				
			||||||
 | 
										image: config.image,
 | 
				
			||||||
 | 
										networks: [network],
 | 
				
			||||||
 | 
										volumes: [config.volume],
 | 
				
			||||||
 | 
										environment: config.environmentVariables,
 | 
				
			||||||
 | 
										restart: 'always',
 | 
				
			||||||
 | 
										labels: makeLabelForServices('uptimekuma')
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								networks: {
 | 
				
			||||||
 | 
									[network]: {
 | 
				
			||||||
 | 
										external: true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								volumes: {
 | 
				
			||||||
 | 
									[config.volume.split(':')[0]]: {
 | 
				
			||||||
 | 
										name: config.volume.split(':')[0]
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							const composeFileDestination = `${workdir}/docker-compose.yaml`;
 | 
				
			||||||
 | 
							await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								if (version === 'latest') {
 | 
				
			||||||
 | 
									await asyncExecShell(
 | 
				
			||||||
 | 
										`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									status: 200
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							} catch (error) {
 | 
				
			||||||
 | 
								return ErrorHandler(error);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} catch (error) {
 | 
				
			||||||
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										35
									
								
								src/routes/services/[id]/uptimekuma/stop.json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/routes/services/[id]/uptimekuma/stop.json.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import { getUserDetails, removeDestinationDocker } from '$lib/common';
 | 
				
			||||||
 | 
					import * as db from '$lib/database';
 | 
				
			||||||
 | 
					import { ErrorHandler } from '$lib/database';
 | 
				
			||||||
 | 
					import { checkContainer } from '$lib/haproxy';
 | 
				
			||||||
 | 
					import type { RequestHandler } from '@sveltejs/kit';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const post: RequestHandler = async (event) => {
 | 
				
			||||||
 | 
						const { teamId, status, body } = await getUserDetails(event);
 | 
				
			||||||
 | 
						if (status === 401) return { status, body };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const { id } = event.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							const service = await db.getService({ id, teamId });
 | 
				
			||||||
 | 
							const { destinationDockerId, destinationDocker, fqdn } = service;
 | 
				
			||||||
 | 
							if (destinationDockerId) {
 | 
				
			||||||
 | 
								const engine = destinationDocker.engine;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									const found = await checkContainer(engine, id);
 | 
				
			||||||
 | 
									if (found) {
 | 
				
			||||||
 | 
										await removeDestinationDocker({ id, engine });
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} catch (error) {
 | 
				
			||||||
 | 
									console.error(error);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								status: 200
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} catch (error) {
 | 
				
			||||||
 | 
							return ErrorHandler(error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -52,20 +52,18 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			volumes: {
 | 
								volumes: {
 | 
				
			||||||
				[config.volume.split(':')[0]]: {
 | 
									[config.volume.split(':')[0]]: {
 | 
				
			||||||
					external: true
 | 
										name: config.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 {
 | 
				
			||||||
			await asyncExecShell(
 | 
								if (version === 'latest') {
 | 
				
			||||||
				`DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}`
 | 
									await asyncExecShell(
 | 
				
			||||||
			);
 | 
										`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
 | 
				
			||||||
		} catch (error) {
 | 
									);
 | 
				
			||||||
			console.log(error);
 | 
								}
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		try {
 | 
					 | 
				
			||||||
			await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
								await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				status: 200
 | 
									status: 200
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,29 +61,20 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			volumes: {
 | 
								volumes: {
 | 
				
			||||||
				[config.volume.split(':')[0]]: {
 | 
									[config.volume.split(':')[0]]: {
 | 
				
			||||||
					external: true
 | 
										name: config.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 {
 | 
							if (version === 'latest') {
 | 
				
			||||||
			await asyncExecShell(
 | 
								await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
 | 
				
			||||||
				`DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}`
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
		} catch (error) {
 | 
					 | 
				
			||||||
			console.log(error);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		try {
 | 
					 | 
				
			||||||
			await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
					 | 
				
			||||||
			return {
 | 
					 | 
				
			||||||
				status: 200
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		} catch (error) {
 | 
					 | 
				
			||||||
			return ErrorHandler(error);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								status: 200
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
	} catch (error) {
 | 
						} catch (error) {
 | 
				
			||||||
		return ErrorHandler(error);
 | 
							return ErrorHandler(error);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,6 +72,7 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
					container_name: id,
 | 
										container_name: id,
 | 
				
			||||||
					image: config.wordpress.image,
 | 
										image: config.wordpress.image,
 | 
				
			||||||
					environment: config.wordpress.environmentVariables,
 | 
										environment: config.wordpress.environmentVariables,
 | 
				
			||||||
 | 
										volumes: [config.wordpress.volume],
 | 
				
			||||||
					networks: [network],
 | 
										networks: [network],
 | 
				
			||||||
					restart: 'always',
 | 
										restart: 'always',
 | 
				
			||||||
					depends_on: [`${id}-mysql`],
 | 
										depends_on: [`${id}-mysql`],
 | 
				
			||||||
@@ -80,6 +81,7 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
				[`${id}-mysql`]: {
 | 
									[`${id}-mysql`]: {
 | 
				
			||||||
					container_name: `${id}-mysql`,
 | 
										container_name: `${id}-mysql`,
 | 
				
			||||||
					image: config.mysql.image,
 | 
										image: config.mysql.image,
 | 
				
			||||||
 | 
										volumes: [config.mysql.volume],
 | 
				
			||||||
					environment: config.mysql.environmentVariables,
 | 
										environment: config.mysql.environmentVariables,
 | 
				
			||||||
					networks: [network],
 | 
										networks: [network],
 | 
				
			||||||
					restart: 'always'
 | 
										restart: 'always'
 | 
				
			||||||
@@ -91,29 +93,22 @@ export const post: RequestHandler = async (event) => {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			volumes: {
 | 
								volumes: {
 | 
				
			||||||
				[config.mysql.volume.split(':')[0]]: {
 | 
					 | 
				
			||||||
					external: true
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				[config.wordpress.volume.split(':')[0]]: {
 | 
									[config.wordpress.volume.split(':')[0]]: {
 | 
				
			||||||
					external: true
 | 
										name: config.wordpress.volume.split(':')[0]
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									[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 {
 | 
					 | 
				
			||||||
			await asyncExecShell(
 | 
					 | 
				
			||||||
				`DOCKER_HOST=${host} docker volume create ${config.mysql.volume.split(':')[0]}`
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			await asyncExecShell(
 | 
					 | 
				
			||||||
				`DOCKER_HOST=${host} docker volume create ${config.wordpress.volume.split(':')[0]}`
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
		} catch (error) {
 | 
					 | 
				
			||||||
			console.log(error);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
 | 
								if (version === 'latest') {
 | 
				
			||||||
 | 
									await asyncExecShell(
 | 
				
			||||||
 | 
										`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
								await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				status: 200
 | 
									status: 200
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,9 @@
 | 
				
			|||||||
	import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
 | 
						import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
 | 
				
			||||||
	import { post } from '$lib/api';
 | 
						import { post } from '$lib/api';
 | 
				
			||||||
	import { goto } from '$app/navigation';
 | 
						import { goto } from '$app/navigation';
 | 
				
			||||||
 | 
						import N8n from '$lib/components/svg/services/N8n.svelte';
 | 
				
			||||||
 | 
						import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
 | 
				
			||||||
 | 
						import Ghost from '$lib/components/svg/services/Ghost.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	export let services;
 | 
						export let services;
 | 
				
			||||||
	async function newService() {
 | 
						async function newService() {
 | 
				
			||||||
@@ -58,6 +61,12 @@
 | 
				
			|||||||
						<VaultWarden isAbsolute />
 | 
											<VaultWarden isAbsolute />
 | 
				
			||||||
					{:else if service.type === 'languagetool'}
 | 
										{:else if service.type === 'languagetool'}
 | 
				
			||||||
						<LanguageTool isAbsolute />
 | 
											<LanguageTool isAbsolute />
 | 
				
			||||||
 | 
										{:else if service.type === 'n8n'}
 | 
				
			||||||
 | 
											<N8n isAbsolute />
 | 
				
			||||||
 | 
										{:else if service.type === 'uptimekuma'}
 | 
				
			||||||
 | 
											<UptimeKuma isAbsolute />
 | 
				
			||||||
 | 
										{:else if service.type === 'ghost'}
 | 
				
			||||||
 | 
											<Ghost isAbsolute />
 | 
				
			||||||
					{/if}
 | 
										{/if}
 | 
				
			||||||
					<div class="font-bold text-xl text-center truncate">
 | 
										<div class="font-bold text-xl text-center truncate">
 | 
				
			||||||
						{service.name}
 | 
											{service.name}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,6 +37,29 @@ textarea {
 | 
				
			|||||||
	@apply min-w-[24rem] rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-300 disabled:bg-transparent md:text-sm;
 | 
						@apply min-w-[24rem] rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-300 disabled:bg-transparent md:text-sm;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#svelte .custom-select-wrapper .selectContainer.disabled input {
 | 
				
			||||||
 | 
						@apply placeholder:text-stone-600;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#svelte .custom-select-wrapper .selectContainer input {
 | 
				
			||||||
 | 
						@apply text-white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#svelte .custom-select-wrapper .selectContainer {
 | 
				
			||||||
 | 
						@apply h-12 w-96 rounded border-none bg-coolgray-200 p-2 text-xs font-bold tracking-tight  outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#svelte .listContainer {
 | 
				
			||||||
 | 
						@apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-coollabs scrollbar-track-coolgray-200;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#svelte .item.hover {
 | 
				
			||||||
 | 
						@apply bg-coolgray-100 text-white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#svelte .item.active {
 | 
				
			||||||
 | 
						@apply bg-coollabs text-white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
select {
 | 
					select {
 | 
				
			||||||
	@apply h-12 w-96 rounded bg-coolgray-200  p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm;
 | 
						@apply h-12 w-96 rounded bg-coolgray-200  p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								static/ghost.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/ghost.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 40 KiB  | 
		Reference in New Issue
	
	Block a user