@@ -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,6 +104,7 @@ export async function generateSSLCerts() {
|
|||||||
});
|
});
|
||||||
for (const application of applications) {
|
for (const application of applications) {
|
||||||
try {
|
try {
|
||||||
|
if (application.fqdn && application.destinationDockerId) {
|
||||||
const {
|
const {
|
||||||
fqdn,
|
fqdn,
|
||||||
id,
|
id,
|
||||||
@@ -133,6 +134,7 @@ export async function generateSSLCerts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Error during generateSSLCerts with ${application.fqdn}: ${error}`);
|
console.log(`Error during generateSSLCerts with ${application.fqdn}: ${error}`);
|
||||||
}
|
}
|
||||||
@@ -143,13 +145,15 @@ 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 {
|
||||||
|
if (service.fqdn && service.destinationDockerId) {
|
||||||
const {
|
const {
|
||||||
fqdn,
|
fqdn,
|
||||||
id,
|
id,
|
||||||
@@ -165,6 +169,7 @@ export async function generateSSLCerts() {
|
|||||||
if (isHttps) ssls.push({ domain, id, isCoolify: false });
|
if (isHttps) ssls.push({ domain, id, isCoolify: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Error during generateSSLCerts with ${service.fqdn}: ${error}`);
|
console.log(`Error during generateSSLCerts with ${service.fqdn}: ${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}
|
|
||||||
<option value={repository.full_name}>{repository.name}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
{/if}
|
|
||||||
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
||||||
{#if loading.branches}
|
<div class="custom-select-wrapper">
|
||||||
<select name="branch" disabled class="w-96">
|
<Select
|
||||||
<option selected value="">Loading branches...</option>
|
placeholder={loading.branches
|
||||||
</select>
|
? 'Loading branches ...'
|
||||||
{:else}
|
: !selected.repository
|
||||||
<select
|
? 'Please select a repository first'
|
||||||
name="branch"
|
: 'Please select a branch'}
|
||||||
class="w-96"
|
id="repository"
|
||||||
disabled={!selected.repository}
|
on:select={isBranchAlreadyUsed}
|
||||||
bind:value={selected.branch}
|
items={branchSelectOptions}
|
||||||
on:change={isBranchAlreadyUsed}
|
isDisabled={loading.branches || !selected.repository}
|
||||||
>
|
/>
|
||||||
{#if !selected.repository}
|
</div>
|
||||||
<option value="" disabled selected>Select a repository first</option>
|
</div>
|
||||||
{: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 {
|
||||||
|
if (version === 'latest') {
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}`
|
`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,22 +61,16 @@ 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`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
@@ -84,7 +78,4 @@ export const post: RequestHandler = async (event) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return ErrorHandler(error);
|
return ErrorHandler(error);
|
||||||
}
|
}
|
||||||
} catch (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 {
|
try {
|
||||||
|
if (version === 'latest') {
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker volume create ${config.mysql.volume.split(':')[0]}`
|
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||||
);
|
);
|
||||||
await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker volume create ${config.wordpress.volume.split(':')[0]}`
|
|
||||||
);
|
|
||||||
} 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
|
||||||
|
|||||||
@@ -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