Merge branch 'next' into edge-db
10
apps/api/.env.example
Normal file
@@ -0,0 +1,10 @@
|
||||
COOLIFY_APP_ID=local-dev
|
||||
# 32 bits long secret key
|
||||
COOLIFY_SECRET_KEY=12341234123412341234123412341234
|
||||
COOLIFY_DATABASE_URL=file:../db/dev.db
|
||||
COOLIFY_SENTRY_DSN=
|
||||
|
||||
COOLIFY_IS_ON=docker
|
||||
COOLIFY_WHITE_LABELED=false
|
||||
COOLIFY_WHITE_LABELED_ICON=
|
||||
COOLIFY_AUTO_UPDATE=
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "coolify-api",
|
||||
"name": "api",
|
||||
"description": "Coolify's Fastify API",
|
||||
"license": "AGPL-3.0",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"db:push": "prisma db push && prisma generate",
|
||||
"db:seed": "prisma db seed",
|
||||
@@ -15,55 +15,57 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@breejs/ts-worker": "2.0.0",
|
||||
"@fastify/autoload": "5.1.0",
|
||||
"@fastify/cookie": "7.3.1",
|
||||
"@fastify/cors": "8.0.0",
|
||||
"@fastify/env": "4.0.0",
|
||||
"@fastify/jwt": "6.3.1",
|
||||
"@fastify/static": "6.4.1",
|
||||
"@fastify/autoload": "5.3.1",
|
||||
"@fastify/cookie": "8.1.0",
|
||||
"@fastify/cors": "8.1.0",
|
||||
"@fastify/env": "4.1.0",
|
||||
"@fastify/jwt": "6.3.2",
|
||||
"@fastify/static": "6.5.0",
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@prisma/client": "3.15.2",
|
||||
"@ladjs/graceful": "3.0.2",
|
||||
"@prisma/client": "4.3.1",
|
||||
"axios": "0.27.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bree": "9.1.1",
|
||||
"bree": "9.1.2",
|
||||
"cabin": "9.1.2",
|
||||
"compare-versions": "4.1.3",
|
||||
"compare-versions": "5.0.1",
|
||||
"cuid": "2.1.8",
|
||||
"dayjs": "1.11.4",
|
||||
"dockerode": "3.3.2",
|
||||
"dayjs": "1.11.5",
|
||||
"dockerode": "3.3.4",
|
||||
"dotenv-extended": "2.9.0",
|
||||
"fastify": "4.3.0",
|
||||
"fastify-plugin": "4.0.0",
|
||||
"execa": "6.1.0",
|
||||
"fastify": "4.5.3",
|
||||
"fastify-plugin": "4.2.1",
|
||||
"generate-password": "1.7.0",
|
||||
"get-port": "6.1.2",
|
||||
"got": "12.2.0",
|
||||
"got": "12.4.1",
|
||||
"is-ip": "5.0.0",
|
||||
"is-port-reachable": "4.0.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"node-forge": "1.3.1",
|
||||
"node-os-utils": "1.3.7",
|
||||
"p-queue": "7.2.0",
|
||||
"p-all": "4.0.0",
|
||||
"p-throttle": "5.0.0",
|
||||
"public-ip": "6.0.1",
|
||||
"ssh-config": "4.1.6",
|
||||
"strip-ansi": "7.0.1",
|
||||
"unique-names-generator": "4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.6.1",
|
||||
"@types/node": "18.7.15",
|
||||
"@types/node-os-utils": "1.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.31.0",
|
||||
"@typescript-eslint/parser": "5.31.0",
|
||||
"esbuild": "0.14.50",
|
||||
"eslint": "8.20.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.36.2",
|
||||
"@typescript-eslint/parser": "5.36.2",
|
||||
"esbuild": "0.15.7",
|
||||
"eslint": "8.23.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"nodemon": "2.0.19",
|
||||
"prettier": "2.7.1",
|
||||
"prisma": "3.15.2",
|
||||
"prisma": "4.3.1",
|
||||
"rimraf": "3.0.2",
|
||||
"tsconfig-paths": "4.0.0",
|
||||
"typescript": "4.7.4"
|
||||
"tsconfig-paths": "4.1.0",
|
||||
"typescript": "4.8.2"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "node prisma/seed.js"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- DropIndex
|
||||
DROP INDEX "Application_fqdn_key";
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "DestinationDocker" ADD COLUMN "sshLocalPort" INTEGER;
|
||||
@@ -0,0 +1,30 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "GlitchTip" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"postgresqlUser" TEXT NOT NULL,
|
||||
"postgresqlPassword" TEXT NOT NULL,
|
||||
"postgresqlDatabase" TEXT NOT NULL,
|
||||
"postgresqlPublicPort" INTEGER,
|
||||
"secretKeyBase" TEXT,
|
||||
"defaultEmail" TEXT NOT NULL,
|
||||
"defaultUsername" TEXT NOT NULL,
|
||||
"defaultPassword" TEXT NOT NULL,
|
||||
"defaultEmailFrom" TEXT NOT NULL DEFAULT 'glitchtip@domain.tdl',
|
||||
"emailSmtpHost" TEXT DEFAULT 'domain.tdl',
|
||||
"emailSmtpPort" INTEGER DEFAULT 25,
|
||||
"emailSmtpUser" TEXT,
|
||||
"emailSmtpPassword" TEXT,
|
||||
"emailSmtpUseTls" BOOLEAN DEFAULT false,
|
||||
"emailSmtpUseSsl" BOOLEAN DEFAULT false,
|
||||
"emailBackend" TEXT,
|
||||
"mailgunApiKey" TEXT,
|
||||
"sendgridApiKey" TEXT,
|
||||
"enableOpenUserRegistration" BOOLEAN NOT NULL DEFAULT true,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "GlitchTip_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "GlitchTip_serviceId_key" ON "GlitchTip"("serviceId");
|
||||
@@ -0,0 +1,22 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Appwrite" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"opensslKeyV1" TEXT NOT NULL,
|
||||
"executorSecret" TEXT NOT NULL,
|
||||
"redisPassword" TEXT NOT NULL,
|
||||
"mariadbHost" TEXT,
|
||||
"mariadbPort" INTEGER NOT NULL DEFAULT 3306,
|
||||
"mariadbUser" TEXT NOT NULL,
|
||||
"mariadbPassword" TEXT NOT NULL,
|
||||
"mariadbRootUser" TEXT NOT NULL,
|
||||
"mariadbRootUserPassword" TEXT NOT NULL,
|
||||
"mariadbDatabase" TEXT NOT NULL,
|
||||
"mariadbPublicPort" INTEGER,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Appwrite_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Appwrite_serviceId_key" ON "Appwrite"("serviceId");
|
||||
@@ -0,0 +1,20 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_ApplicationSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||
DROP TABLE "ApplicationSettings";
|
||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Setting" ADD COLUMN "DNSServers" TEXT;
|
||||
@@ -0,0 +1,42 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_GitSource" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"forPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||
"type" TEXT,
|
||||
"apiUrl" TEXT,
|
||||
"htmlUrl" TEXT,
|
||||
"customPort" INTEGER NOT NULL DEFAULT 22,
|
||||
"organization" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"githubAppId" TEXT,
|
||||
"gitlabAppId" TEXT,
|
||||
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt" FROM "GitSource";
|
||||
DROP TABLE "GitSource";
|
||||
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
|
||||
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
|
||||
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
|
||||
CREATE TABLE "new_ApplicationSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||
DROP TABLE "ApplicationSettings";
|
||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Searxng" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"secretKey" TEXT NOT NULL,
|
||||
"redisPassword" TEXT NOT NULL,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Searxng_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Searxng_serviceId_key" ON "Searxng"("serviceId");
|
||||
@@ -0,0 +1,29 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Setting" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"fqdn" TEXT,
|
||||
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||
"proxyPassword" TEXT NOT NULL,
|
||||
"proxyUser" TEXT NOT NULL,
|
||||
"proxyHash" TEXT,
|
||||
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"DNSServers" TEXT,
|
||||
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"ipv4" TEXT,
|
||||
"ipv6" TEXT,
|
||||
"arch" TEXT,
|
||||
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
INSERT INTO "new_Setting" ("DNSServers", "arch", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "DNSServers", "arch", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting";
|
||||
DROP TABLE "Setting";
|
||||
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,24 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Build" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"type" TEXT NOT NULL,
|
||||
"applicationId" TEXT,
|
||||
"destinationDockerId" TEXT,
|
||||
"gitSourceId" TEXT,
|
||||
"githubAppId" TEXT,
|
||||
"gitlabAppId" TEXT,
|
||||
"commit" TEXT,
|
||||
"pullmergeRequestId" TEXT,
|
||||
"forceRebuild" BOOLEAN NOT NULL DEFAULT false,
|
||||
"sourceBranch" TEXT,
|
||||
"branch" TEXT,
|
||||
"status" TEXT DEFAULT 'queued',
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Build" ("applicationId", "branch", "commit", "createdAt", "destinationDockerId", "gitSourceId", "githubAppId", "gitlabAppId", "id", "status", "type", "updatedAt") SELECT "applicationId", "branch", "commit", "createdAt", "destinationDockerId", "gitSourceId", "githubAppId", "gitlabAppId", "id", "status", "type", "updatedAt" FROM "Build";
|
||||
DROP TABLE "Build";
|
||||
ALTER TABLE "new_Build" RENAME TO "Build";
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,18 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Weblate" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"adminPassword" TEXT NOT NULL,
|
||||
"postgresqlHost" TEXT NOT NULL,
|
||||
"postgresqlPort" INTEGER NOT NULL,
|
||||
"postgresqlUser" TEXT NOT NULL,
|
||||
"postgresqlPassword" TEXT NOT NULL,
|
||||
"postgresqlDatabase" TEXT NOT NULL,
|
||||
"postgresqlPublicPort" INTEGER,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Weblate_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Weblate_serviceId_key" ON "Weblate"("serviceId");
|
||||
@@ -0,0 +1,23 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Taiga" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"secretKey" TEXT NOT NULL,
|
||||
"erlangSecret" TEXT NOT NULL,
|
||||
"djangoAdminPassword" TEXT NOT NULL,
|
||||
"djangoAdminUser" TEXT NOT NULL,
|
||||
"rabbitMQUser" TEXT NOT NULL,
|
||||
"rabbitMQPassword" TEXT NOT NULL,
|
||||
"postgresqlHost" TEXT NOT NULL,
|
||||
"postgresqlPort" INTEGER NOT NULL,
|
||||
"postgresqlUser" TEXT NOT NULL,
|
||||
"postgresqlPassword" TEXT NOT NULL,
|
||||
"postgresqlDatabase" TEXT NOT NULL,
|
||||
"postgresqlPublicPort" INTEGER,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Taiga_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Taiga_serviceId_key" ON "Taiga"("serviceId");
|
||||
@@ -0,0 +1,22 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_ApplicationSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||
DROP TABLE "ApplicationSettings";
|
||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to alter the column `time` on the `BuildLog` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`.
|
||||
|
||||
*/
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_BuildLog" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT,
|
||||
"buildId" TEXT NOT NULL,
|
||||
"line" TEXT NOT NULL,
|
||||
"time" BIGINT NOT NULL
|
||||
);
|
||||
INSERT INTO "new_BuildLog" ("applicationId", "buildId", "id", "line", "time") SELECT "applicationId", "buildId", "id", "line", "time" FROM "BuildLog";
|
||||
DROP TABLE "BuildLog";
|
||||
ALTER TABLE "new_BuildLog" RENAME TO "BuildLog";
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,20 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ApplicationConnectedDatabase" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"databaseId" TEXT,
|
||||
"hostedDatabaseType" TEXT,
|
||||
"hostedDatabaseHost" TEXT,
|
||||
"hostedDatabasePort" INTEGER,
|
||||
"hostedDatabaseName" TEXT,
|
||||
"hostedDatabaseUser" TEXT,
|
||||
"hostedDatabasePassword" TEXT,
|
||||
"hostedDatabaseDBName" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationConnectedDatabase_databaseId_fkey" FOREIGN KEY ("databaseId") REFERENCES "Database" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "ApplicationConnectedDatabase_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ApplicationConnectedDatabase_applicationId_key" ON "ApplicationConnectedDatabase"("applicationId");
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Setting" ADD COLUMN "isAPIDebuggingEnabled" BOOLEAN DEFAULT false;
|
||||
@@ -1,6 +1,6 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "linux-musl"]
|
||||
binaryTargets = ["native"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
@@ -11,6 +11,7 @@ datasource db {
|
||||
model Setting {
|
||||
id String @id @default(cuid())
|
||||
fqdn String? @unique
|
||||
isAPIDebuggingEnabled Boolean? @default(false)
|
||||
isRegistrationEnabled Boolean @default(false)
|
||||
dualCerts Boolean @default(false)
|
||||
minPort Int @default(9000)
|
||||
@@ -20,12 +21,14 @@ model Setting {
|
||||
proxyHash String?
|
||||
isAutoUpdateEnabled Boolean @default(false)
|
||||
isDNSCheckEnabled Boolean @default(true)
|
||||
DNSServers String?
|
||||
isTraefikUsed Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
ipv4 String?
|
||||
ipv6 String?
|
||||
arch String?
|
||||
concurrentBuilds Int @default(1)
|
||||
}
|
||||
|
||||
model User {
|
||||
@@ -82,7 +85,7 @@ model TeamInvitation {
|
||||
model Application {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
fqdn String? @unique
|
||||
fqdn String?
|
||||
repository String?
|
||||
configHash String?
|
||||
branch String?
|
||||
@@ -115,18 +118,39 @@ model Application {
|
||||
settings ApplicationSettings?
|
||||
secrets Secret[]
|
||||
teams Team[]
|
||||
connectedDatabase ApplicationConnectedDatabase?
|
||||
}
|
||||
|
||||
model ApplicationConnectedDatabase {
|
||||
id String @id @default(cuid())
|
||||
applicationId String @unique
|
||||
databaseId String?
|
||||
hostedDatabaseType String?
|
||||
hostedDatabaseHost String?
|
||||
hostedDatabasePort Int?
|
||||
hostedDatabaseName String?
|
||||
hostedDatabaseUser String?
|
||||
hostedDatabasePassword String?
|
||||
hostedDatabaseDBName String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
database Database? @relation(fields: [databaseId], references: [id])
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
}
|
||||
|
||||
model ApplicationSettings {
|
||||
id String @id @default(cuid())
|
||||
applicationId String @unique
|
||||
dualCerts Boolean @default(false)
|
||||
debug Boolean @default(false)
|
||||
previews Boolean @default(false)
|
||||
autodeploy Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
id String @id @default(cuid())
|
||||
applicationId String @unique
|
||||
dualCerts Boolean @default(false)
|
||||
debug Boolean @default(false)
|
||||
previews Boolean @default(false)
|
||||
autodeploy Boolean @default(true)
|
||||
isBot Boolean @default(false)
|
||||
isPublicRepository Boolean @default(false)
|
||||
isDBBranching Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
}
|
||||
|
||||
model ApplicationPersistentStorage {
|
||||
@@ -182,7 +206,7 @@ model BuildLog {
|
||||
applicationId String?
|
||||
buildId String
|
||||
line String
|
||||
time Int
|
||||
time BigInt
|
||||
}
|
||||
|
||||
model Build {
|
||||
@@ -194,6 +218,9 @@ model Build {
|
||||
githubAppId String?
|
||||
gitlabAppId String?
|
||||
commit String?
|
||||
pullmergeRequestId String?
|
||||
forceRebuild Boolean @default(false)
|
||||
sourceBranch String?
|
||||
branch String?
|
||||
status String? @default("queued")
|
||||
createdAt DateTime @default(now())
|
||||
@@ -215,6 +242,7 @@ model DestinationDocker {
|
||||
updatedAt DateTime @updatedAt
|
||||
sshKeyId String?
|
||||
sshKey SshKey? @relation(fields: [sshKeyId], references: [id])
|
||||
sshLocalPort Int?
|
||||
application Application[]
|
||||
database Database[]
|
||||
service Service[]
|
||||
@@ -235,6 +263,7 @@ model SshKey {
|
||||
model GitSource {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
forPublic Boolean @default(false)
|
||||
type String?
|
||||
apiUrl String?
|
||||
htmlUrl String?
|
||||
@@ -282,22 +311,23 @@ model GitlabApp {
|
||||
}
|
||||
|
||||
model Database {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
publicPort Int?
|
||||
defaultDatabase String?
|
||||
type String?
|
||||
version String?
|
||||
dbUser String?
|
||||
dbUserPassword String?
|
||||
rootUser String?
|
||||
rootUserPassword String?
|
||||
destinationDockerId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
settings DatabaseSettings?
|
||||
teams Team[]
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
publicPort Int?
|
||||
defaultDatabase String?
|
||||
type String?
|
||||
version String?
|
||||
dbUser String?
|
||||
dbUserPassword String?
|
||||
rootUser String?
|
||||
rootUserPassword String?
|
||||
destinationDockerId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
settings DatabaseSettings?
|
||||
teams Team[]
|
||||
applicationConnectedDatabase ApplicationConnectedDatabase[]
|
||||
}
|
||||
|
||||
model DatabaseSettings {
|
||||
@@ -322,19 +352,25 @@ model Service {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
fider Fider?
|
||||
ghost Ghost?
|
||||
hasura Hasura?
|
||||
meiliSearch MeiliSearch?
|
||||
minio Minio?
|
||||
moodle Moodle?
|
||||
plausibleAnalytics PlausibleAnalytics?
|
||||
persistentStorage ServicePersistentStorage[]
|
||||
serviceSecret ServiceSecret[]
|
||||
umami Umami?
|
||||
vscodeserver Vscodeserver?
|
||||
wordpress Wordpress?
|
||||
teams Team[]
|
||||
|
||||
fider Fider?
|
||||
ghost Ghost?
|
||||
glitchTip GlitchTip?
|
||||
hasura Hasura?
|
||||
meiliSearch MeiliSearch?
|
||||
minio Minio?
|
||||
moodle Moodle?
|
||||
plausibleAnalytics PlausibleAnalytics?
|
||||
umami Umami?
|
||||
vscodeserver Vscodeserver?
|
||||
wordpress Wordpress?
|
||||
appwrite Appwrite?
|
||||
searxng Searxng?
|
||||
weblate Weblate?
|
||||
taiga Taiga?
|
||||
}
|
||||
|
||||
model PlausibleAnalytics {
|
||||
@@ -490,3 +526,94 @@ model Moodle {
|
||||
updatedAt DateTime @updatedAt
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
}
|
||||
|
||||
model Appwrite {
|
||||
id String @id @default(cuid())
|
||||
serviceId String @unique
|
||||
opensslKeyV1 String
|
||||
executorSecret String
|
||||
redisPassword String
|
||||
mariadbHost String?
|
||||
mariadbPort Int @default(3306)
|
||||
mariadbUser String
|
||||
mariadbPassword String
|
||||
mariadbRootUser String
|
||||
mariadbRootUserPassword String
|
||||
mariadbDatabase String
|
||||
mariadbPublicPort Int?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
}
|
||||
|
||||
model GlitchTip {
|
||||
id String @id @default(cuid())
|
||||
postgresqlUser String
|
||||
postgresqlPassword String
|
||||
postgresqlDatabase String
|
||||
postgresqlPublicPort Int?
|
||||
secretKeyBase String?
|
||||
defaultEmail String
|
||||
defaultUsername String
|
||||
defaultPassword String
|
||||
defaultEmailFrom String @default("glitchtip@domain.tdl")
|
||||
emailSmtpHost String? @default("domain.tdl")
|
||||
emailSmtpPort Int? @default(25)
|
||||
emailSmtpUser String?
|
||||
emailSmtpPassword String?
|
||||
emailSmtpUseTls Boolean? @default(false)
|
||||
emailSmtpUseSsl Boolean? @default(false)
|
||||
emailBackend String?
|
||||
mailgunApiKey String?
|
||||
sendgridApiKey String?
|
||||
enableOpenUserRegistration Boolean @default(true)
|
||||
serviceId String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
}
|
||||
|
||||
model Searxng {
|
||||
id String @id @default(cuid())
|
||||
secretKey String
|
||||
redisPassword String
|
||||
serviceId String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
}
|
||||
|
||||
model Weblate {
|
||||
id String @id @default(cuid())
|
||||
adminPassword String
|
||||
postgresqlHost String
|
||||
postgresqlPort Int
|
||||
postgresqlUser String
|
||||
postgresqlPassword String
|
||||
postgresqlDatabase String
|
||||
postgresqlPublicPort Int?
|
||||
serviceId String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
}
|
||||
|
||||
model Taiga {
|
||||
id String @id @default(cuid())
|
||||
secretKey String
|
||||
erlangSecret String
|
||||
djangoAdminPassword String
|
||||
djangoAdminUser String
|
||||
rabbitMQUser String
|
||||
rabbitMQPassword String
|
||||
postgresqlHost String
|
||||
postgresqlPort Int
|
||||
postgresqlUser String
|
||||
postgresqlPassword String
|
||||
postgresqlDatabase String
|
||||
postgresqlPublicPort Int?
|
||||
serviceId String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ const algorithm = 'aes-256-ctr';
|
||||
|
||||
async function main() {
|
||||
// Enable registration for the first user
|
||||
// Set initial HAProxy password
|
||||
const settingsFound = await prisma.setting.findFirst({});
|
||||
if (!settingsFound) {
|
||||
await prisma.setting.create({
|
||||
@@ -25,7 +24,8 @@ async function main() {
|
||||
isRegistrationEnabled: true,
|
||||
proxyPassword: encrypt(generatePassword()),
|
||||
proxyUser: cuid(),
|
||||
arch: process.arch
|
||||
arch: process.arch,
|
||||
DNSServers: '1.1.1.1,8.8.8.8'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -66,6 +66,34 @@ async function main() {
|
||||
}
|
||||
});
|
||||
}
|
||||
const github = await prisma.gitSource.findFirst({
|
||||
where: { htmlUrl: 'https://github.com', forPublic: true }
|
||||
});
|
||||
const gitlab = await prisma.gitSource.findFirst({
|
||||
where: { htmlUrl: 'https://gitlab.com', forPublic: true }
|
||||
});
|
||||
if (!github) {
|
||||
await prisma.gitSource.create({
|
||||
data: {
|
||||
apiUrl: 'https://api.github.com',
|
||||
htmlUrl: 'https://github.com',
|
||||
forPublic: true,
|
||||
name: 'Github Public',
|
||||
type: 'github'
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!gitlab) {
|
||||
await prisma.gitSource.create({
|
||||
data: {
|
||||
apiUrl: 'https://gitlab.com/api/v4',
|
||||
htmlUrl: 'https://gitlab.com',
|
||||
forPublic: true,
|
||||
name: 'Gitlab Public',
|
||||
type: 'gitlab'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
main()
|
||||
.catch((e) => {
|
||||
|
||||
@@ -5,9 +5,10 @@ import env from '@fastify/env';
|
||||
import cookie from '@fastify/cookie';
|
||||
import path, { join } from 'path';
|
||||
import autoLoad from '@fastify/autoload';
|
||||
import { asyncExecShell, isDev, listSettings, prisma } from './lib/common';
|
||||
import { asyncExecShell, createRemoteEngineConfiguration, getDomain, isDev, listSettings, prisma, version } from './lib/common';
|
||||
import { scheduler } from './lib/scheduler';
|
||||
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import Graceful from '@ladjs/graceful'
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
config: {
|
||||
@@ -16,123 +17,152 @@ declare module 'fastify' {
|
||||
COOLIFY_DATABASE_URL: string,
|
||||
COOLIFY_SENTRY_DSN: string,
|
||||
COOLIFY_IS_ON: string,
|
||||
COOLIFY_WHITE_LABELED: boolean,
|
||||
COOLIFY_WHITE_LABELED: string,
|
||||
COOLIFY_WHITE_LABELED_ICON: string | null,
|
||||
COOLIFY_AUTO_UPDATE: boolean,
|
||||
COOLIFY_AUTO_UPDATE: string,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const port = isDev ? 3001 : 3000;
|
||||
const host = '0.0.0.0';
|
||||
const fastify = Fastify({
|
||||
logger: false,
|
||||
trustProxy: true
|
||||
});
|
||||
const schema = {
|
||||
type: 'object',
|
||||
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
|
||||
properties: {
|
||||
COOLIFY_APP_ID: {
|
||||
type: 'string',
|
||||
},
|
||||
COOLIFY_SECRET_KEY: {
|
||||
type: 'string',
|
||||
},
|
||||
COOLIFY_DATABASE_URL: {
|
||||
type: 'string',
|
||||
default: 'file:../db/dev.db'
|
||||
},
|
||||
COOLIFY_SENTRY_DSN: {
|
||||
type: 'string',
|
||||
default: null
|
||||
},
|
||||
COOLIFY_IS_ON: {
|
||||
type: 'string',
|
||||
default: 'docker'
|
||||
},
|
||||
COOLIFY_WHITE_LABELED: {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
COOLIFY_WHITE_LABELED_ICON: {
|
||||
type: 'string',
|
||||
default: null
|
||||
},
|
||||
COOLIFY_AUTO_UPDATE: {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const options = {
|
||||
schema,
|
||||
dotenv: true
|
||||
};
|
||||
fastify.register(env, options);
|
||||
if (!isDev) {
|
||||
fastify.register(serve, {
|
||||
root: path.join(__dirname, './public'),
|
||||
preCompressed: true
|
||||
prisma.setting.findFirst().then(async (settings) => {
|
||||
const fastify = Fastify({
|
||||
logger: settings?.isAPIDebuggingEnabled || false,
|
||||
trustProxy: true
|
||||
});
|
||||
fastify.setNotFoundHandler(async function (request, reply) {
|
||||
if (request.raw.url && request.raw.url.startsWith('/api')) {
|
||||
return reply.status(404).send({
|
||||
success: false
|
||||
});
|
||||
const schema = {
|
||||
type: 'object',
|
||||
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
|
||||
properties: {
|
||||
COOLIFY_APP_ID: {
|
||||
type: 'string',
|
||||
},
|
||||
COOLIFY_SECRET_KEY: {
|
||||
type: 'string',
|
||||
},
|
||||
COOLIFY_DATABASE_URL: {
|
||||
type: 'string',
|
||||
default: 'file:../db/dev.db'
|
||||
},
|
||||
COOLIFY_SENTRY_DSN: {
|
||||
type: 'string',
|
||||
default: null
|
||||
},
|
||||
COOLIFY_IS_ON: {
|
||||
type: 'string',
|
||||
default: 'docker'
|
||||
},
|
||||
COOLIFY_WHITE_LABELED: {
|
||||
type: 'string',
|
||||
default: 'false'
|
||||
},
|
||||
COOLIFY_WHITE_LABELED_ICON: {
|
||||
type: 'string',
|
||||
default: null
|
||||
},
|
||||
COOLIFY_AUTO_UPDATE: {
|
||||
type: 'string',
|
||||
default: 'false'
|
||||
},
|
||||
|
||||
}
|
||||
return reply.status(200).sendFile('index.html');
|
||||
});
|
||||
}
|
||||
fastify.register(autoLoad, {
|
||||
dir: join(__dirname, 'plugins')
|
||||
});
|
||||
fastify.register(autoLoad, {
|
||||
dir: join(__dirname, 'routes')
|
||||
});
|
||||
};
|
||||
|
||||
fastify.register(cookie)
|
||||
fastify.register(cors);
|
||||
fastify.listen({ port, host }, async (err: any, address: any) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Coolify's API is listening on ${host}:${port}`);
|
||||
await initServer();
|
||||
await scheduler.start('deployApplication');
|
||||
await scheduler.start('cleanupStorage');
|
||||
await scheduler.start('checkProxies');
|
||||
|
||||
// Check if no build is running
|
||||
|
||||
// Check for update
|
||||
setInterval(async () => {
|
||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||
if (isAutoUpdateEnabled) {
|
||||
if (scheduler.workers.has('deployApplication')) {
|
||||
scheduler.workers.get('deployApplication').postMessage("status:autoUpdater");
|
||||
const options = {
|
||||
schema,
|
||||
dotenv: true
|
||||
};
|
||||
fastify.register(env, options);
|
||||
if (!isDev) {
|
||||
fastify.register(serve, {
|
||||
root: path.join(__dirname, './public'),
|
||||
preCompressed: true
|
||||
});
|
||||
fastify.setNotFoundHandler(async function (request, reply) {
|
||||
if (request.raw.url && request.raw.url.startsWith('/api')) {
|
||||
return reply.status(404).send({
|
||||
success: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 60000 * 15)
|
||||
|
||||
// Cleanup storage
|
||||
setInterval(async () => {
|
||||
if (scheduler.workers.has('deployApplication')) {
|
||||
scheduler.workers.get('deployApplication').postMessage("status:cleanupStorage");
|
||||
}
|
||||
}, 60000 * 10)
|
||||
|
||||
scheduler.on('worker deleted', async (name) => {
|
||||
if (name === 'autoUpdater' || name === 'cleanupStorage') {
|
||||
if (!scheduler.workers.has('deployApplication')) await scheduler.start('deployApplication');
|
||||
}
|
||||
return reply.status(200).sendFile('index.html');
|
||||
});
|
||||
}
|
||||
fastify.register(autoLoad, {
|
||||
dir: join(__dirname, 'plugins')
|
||||
});
|
||||
await getArch();
|
||||
await getIPAddress();
|
||||
});
|
||||
fastify.register(autoLoad, {
|
||||
dir: join(__dirname, 'routes')
|
||||
});
|
||||
|
||||
fastify.register(cookie)
|
||||
fastify.register(cors);
|
||||
fastify.addHook('onRequest', async (request, reply) => {
|
||||
let allowedList = ['coolify:3000'];
|
||||
const { ipv4, ipv6, fqdn } = await prisma.setting.findFirst({})
|
||||
|
||||
ipv4 && allowedList.push(`${ipv4}:3000`);
|
||||
ipv6 && allowedList.push(ipv6);
|
||||
fqdn && allowedList.push(getDomain(fqdn));
|
||||
isDev && allowedList.push('localhost:3000') && allowedList.push('localhost:3001') && allowedList.push('host.docker.internal:3001');
|
||||
const remotes = await prisma.destinationDocker.findMany({ where: { remoteEngine: true, remoteVerified: true } })
|
||||
if (remotes.length > 0) {
|
||||
remotes.forEach(remote => {
|
||||
allowedList.push(`${remote.remoteIpAddress}:3000`);
|
||||
})
|
||||
}
|
||||
if (!allowedList.includes(request.headers.host)) {
|
||||
// console.log('not allowed', request.headers.host)
|
||||
}
|
||||
})
|
||||
fastify.listen({ port, host }, async (err: any, address: any) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Coolify's API is listening on ${host}:${port}`);
|
||||
await initServer();
|
||||
|
||||
const graceful = new Graceful({ brees: [scheduler] });
|
||||
graceful.listen();
|
||||
|
||||
setInterval(async () => {
|
||||
if (!scheduler.workers.has('deployApplication')) {
|
||||
scheduler.run('deployApplication');
|
||||
}
|
||||
if (!scheduler.workers.has('infrastructure')) {
|
||||
scheduler.run('infrastructure');
|
||||
}
|
||||
}, 2000)
|
||||
|
||||
// autoUpdater
|
||||
setInterval(async () => {
|
||||
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:autoUpdater")
|
||||
}, isDev ? 5000 : 60000 * 15)
|
||||
|
||||
// cleanupStorage
|
||||
setInterval(async () => {
|
||||
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupStorage")
|
||||
}, isDev ? 6000 : 60000 * 10)
|
||||
|
||||
// checkProxies
|
||||
setInterval(async () => {
|
||||
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:checkProxies")
|
||||
}, 10000)
|
||||
|
||||
// cleanupPrismaEngines
|
||||
// setInterval(async () => {
|
||||
// scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupPrismaEngines")
|
||||
// }, 60000)
|
||||
|
||||
await Promise.all([
|
||||
getArch(),
|
||||
getIPAddress(),
|
||||
configureRemoteDockers(),
|
||||
])
|
||||
});
|
||||
})
|
||||
|
||||
async function getIPAddress() {
|
||||
const { publicIpv4, publicIpv6 } = await import('public-ip')
|
||||
try {
|
||||
@@ -153,6 +183,12 @@ async function initServer() {
|
||||
try {
|
||||
await asyncExecShell(`docker network create --attachable coolify`);
|
||||
} catch (error) { }
|
||||
try {
|
||||
const isOlder = compareVersions('3.8.1', version);
|
||||
if (isOlder === 1) {
|
||||
await prisma.build.updateMany({ where: { status: { in: ['running', 'queued'] } }, data: { status: 'failed' } });
|
||||
}
|
||||
} catch (error) { }
|
||||
}
|
||||
async function getArch() {
|
||||
try {
|
||||
@@ -163,4 +199,15 @@ async function getArch() {
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
|
||||
async function configureRemoteDockers() {
|
||||
try {
|
||||
const remoteDocker = await prisma.destinationDocker.findMany({
|
||||
where: { remoteVerified: true, remoteEngine: true }
|
||||
});
|
||||
if (remoteDocker.length > 0) {
|
||||
for (const docker of remoteDocker) {
|
||||
await createRemoteEngineConfiguration(docker.id)
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import compareVersions from 'compare-versions';
|
||||
import { parentPort } from 'node:worker_threads';
|
||||
import { asyncExecShell, asyncSleep, isDev, prisma, version } from '../lib/common';
|
||||
|
||||
(async () => {
|
||||
if (parentPort) {
|
||||
try {
|
||||
const currentVersion = version;
|
||||
const { data: versions } = await axios
|
||||
.get(
|
||||
`https://get.coollabs.io/versions.json`
|
||||
, {
|
||||
params: {
|
||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||
version: currentVersion
|
||||
}
|
||||
})
|
||||
const latestVersion = versions['coolify'].main.version;
|
||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||
if (isUpdateAvailable === 1) {
|
||||
const activeCount = 0
|
||||
if (activeCount === 0) {
|
||||
if (!isDev) {
|
||||
console.log(`Updating Coolify to ${latestVersion}.`);
|
||||
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
|
||||
await asyncExecShell(`env | grep COOLIFY > .env`);
|
||||
await asyncExecShell(
|
||||
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify && docker rm coolify && docker compose up -d --force-recreate"`
|
||||
);
|
||||
} else {
|
||||
console.log('Updating (not really in dev mode).');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
} else process.exit(0);
|
||||
})();
|
||||
@@ -1,90 +0,0 @@
|
||||
import { parentPort } from 'node:worker_threads';
|
||||
import { prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, executeDockerCmd } from '../lib/common';
|
||||
import { checkContainer } from '../lib/docker';
|
||||
|
||||
(async () => {
|
||||
if (parentPort) {
|
||||
// Coolify Proxy local
|
||||
const engine = '/var/run/docker.sock';
|
||||
const localDocker = await prisma.destinationDocker.findFirst({
|
||||
where: { engine, network: 'coolify' }
|
||||
});
|
||||
if (localDocker && localDocker.isCoolifyProxyUsed) {
|
||||
// Remove HAProxy
|
||||
const found = await checkContainer({ dockerId: localDocker.id, container: 'coolify-haproxy' });
|
||||
if (found) {
|
||||
await executeDockerCmd({
|
||||
dockerId: localDocker.id,
|
||||
command: `docker stop -t 0 coolify-haproxy && docker rm coolify-haproxy`
|
||||
})
|
||||
}
|
||||
await startTraefikProxy(localDocker.id);
|
||||
|
||||
}
|
||||
|
||||
// TCP Proxies
|
||||
const databasesWithPublicPort = await prisma.database.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
include: { settings: true, destinationDocker: true }
|
||||
});
|
||||
for (const database of databasesWithPublicPort) {
|
||||
const { destinationDockerId, destinationDocker, publicPort, id } = database;
|
||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||
const { privatePort } = generateDatabaseConfiguration(database);
|
||||
// Remove HAProxy
|
||||
const found = await checkContainer({
|
||||
dockerId: localDocker.id, container: `haproxy-for-${publicPort}`
|
||||
});
|
||||
if (found) {
|
||||
await executeDockerCmd({
|
||||
dockerId: localDocker.id,
|
||||
command: `docker stop -t 0 haproxy-for-${publicPort} && docker rm haproxy-for-${publicPort}`
|
||||
})
|
||||
}
|
||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||
|
||||
}
|
||||
}
|
||||
const wordpressWithFtp = await prisma.wordpress.findMany({
|
||||
where: { ftpPublicPort: { not: null } },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
for (const ftp of wordpressWithFtp) {
|
||||
const { service, ftpPublicPort } = ftp;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||
// Remove HAProxy
|
||||
const found = await checkContainer({ dockerId: localDocker.id, container: `haproxy-for-${ftpPublicPort}` });
|
||||
if (found) {
|
||||
await executeDockerCmd({
|
||||
dockerId: localDocker.id,
|
||||
command: `docker stop -t 0 haproxy -for-${ftpPublicPort} && docker rm haproxy-for-${ftpPublicPort}`
|
||||
})
|
||||
}
|
||||
await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp');
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP Proxies
|
||||
const minioInstances = await prisma.minio.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
for (const minio of minioInstances) {
|
||||
const { service, publicPort } = minio;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||
// Remove HAProxy
|
||||
const found = await checkContainer({ dockerId: localDocker.id, container: `${id}-${publicPort}` });
|
||||
if (found) {
|
||||
await executeDockerCmd({
|
||||
dockerId: localDocker.id,
|
||||
command: `docker stop -t 0 ${id}-${publicPort} && docker rm ${id}-${publicPort} `
|
||||
})
|
||||
}
|
||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
||||
}
|
||||
}
|
||||
await prisma.$disconnect();
|
||||
} else process.exit(0);
|
||||
})();
|
||||
@@ -1,60 +0,0 @@
|
||||
import { parentPort } from 'node:worker_threads';
|
||||
import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, version } from '../lib/common';
|
||||
|
||||
(async () => {
|
||||
if (parentPort) {
|
||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||
let enginesDone = new Set()
|
||||
for (const destination of destinationDockers) {
|
||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return
|
||||
if (destination.engine) enginesDone.add(destination.engine)
|
||||
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress)
|
||||
|
||||
let lowDiskSpace = false;
|
||||
try {
|
||||
let stdout = null
|
||||
if (!isDev) {
|
||||
const output = await executeDockerCmd({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'` })
|
||||
stdout = output.stdout;
|
||||
} else {
|
||||
const output = await asyncExecShell(
|
||||
`df -kPT /`
|
||||
);
|
||||
stdout = output.stdout;
|
||||
}
|
||||
let lines = stdout.trim().split('\n');
|
||||
let header = lines[0];
|
||||
let regex =
|
||||
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
||||
const boundaries = [];
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(header))) {
|
||||
boundaries.push(match[0].length);
|
||||
}
|
||||
|
||||
boundaries[boundaries.length - 1] = -1;
|
||||
const data = lines.slice(1).map((line) => {
|
||||
const cl = boundaries.map((boundary) => {
|
||||
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||
line = line.slice(boundary);
|
||||
return column.trim();
|
||||
});
|
||||
return {
|
||||
capacity: Number.parseInt(cl[5], 10) / 100
|
||||
};
|
||||
});
|
||||
if (data.length > 0) {
|
||||
const { capacity } = data[0];
|
||||
if (capacity > 0.8) {
|
||||
lowDiskSpace = true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
|
||||
}
|
||||
await prisma.$disconnect();
|
||||
} else process.exit(0);
|
||||
})();
|
||||
@@ -4,154 +4,92 @@ import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
|
||||
import { createDirectories, decrypt, executeDockerCmd, getDomain, prisma } from '../lib/common';
|
||||
import { createDirectories, decrypt, defaultComposeConfiguration, executeDockerCmd, getDomain, prisma, decryptApplication } from '../lib/common';
|
||||
import * as importers from '../lib/importers';
|
||||
import * as buildpacks from '../lib/buildPacks';
|
||||
|
||||
(async () => {
|
||||
if (parentPort) {
|
||||
const concurrency = 1
|
||||
const PQueue = await import('p-queue');
|
||||
const queue = new PQueue.default({ concurrency });
|
||||
parentPort.on('message', async (message) => {
|
||||
if (parentPort) {
|
||||
if (message === 'error') throw new Error('oops');
|
||||
if (message === 'cancel') {
|
||||
parentPort.postMessage('cancelled');
|
||||
return;
|
||||
}
|
||||
if (message === 'status:autoUpdater') {
|
||||
parentPort.postMessage({ size: queue.size, pending: queue.pending, caller: 'autoUpdater' });
|
||||
return;
|
||||
}
|
||||
if (message === 'status:cleanupStorage') {
|
||||
parentPort.postMessage({ size: queue.size, pending: queue.pending, caller: 'cleanupStorage' });
|
||||
return;
|
||||
}
|
||||
if (message === 'error') throw new Error('oops');
|
||||
if (message === 'cancel') {
|
||||
parentPort.postMessage('cancelled');
|
||||
await prisma.$disconnect()
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
const pThrottle = await import('p-throttle')
|
||||
const throttle = pThrottle.default({
|
||||
limit: 1,
|
||||
interval: 2000
|
||||
});
|
||||
|
||||
await queue.add(async () => {
|
||||
const {
|
||||
id: applicationId,
|
||||
repository,
|
||||
name,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
gitSource,
|
||||
build_id: buildId,
|
||||
configHash,
|
||||
fqdn,
|
||||
projectId,
|
||||
secrets,
|
||||
phpModules,
|
||||
type,
|
||||
pullmergeRequestId = null,
|
||||
sourceBranch = null,
|
||||
settings,
|
||||
persistentStorage,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
denoOptions,
|
||||
exposePort,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType,
|
||||
} = message
|
||||
let {
|
||||
branch,
|
||||
buildPack,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory,
|
||||
dockerFileLocation,
|
||||
denoMainFile
|
||||
} = message
|
||||
try {
|
||||
const { debug } = settings;
|
||||
if (concurrency === 1) {
|
||||
await prisma.build.updateMany({
|
||||
where: {
|
||||
status: { in: ['queued', 'running'] },
|
||||
id: { not: buildId },
|
||||
applicationId,
|
||||
createdAt: { lt: new Date(new Date().getTime() - 10 * 1000) }
|
||||
},
|
||||
data: { status: 'failed' }
|
||||
});
|
||||
}
|
||||
let imageId = applicationId;
|
||||
let domain = getDomain(fqdn);
|
||||
const volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
||||
}${storage.path}`;
|
||||
}) || [];
|
||||
// Previews, we need to get the source branch and set subdomain
|
||||
if (pullmergeRequestId) {
|
||||
branch = sourceBranch;
|
||||
domain = `${pullmergeRequestId}.${domain}`;
|
||||
imageId = `${applicationId}-${pullmergeRequestId}`;
|
||||
}
|
||||
|
||||
let deployNeeded = true;
|
||||
let destinationType;
|
||||
|
||||
if (destinationDockerId) {
|
||||
destinationType = 'docker';
|
||||
}
|
||||
if (destinationType === 'docker') {
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
||||
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
||||
const configuration = await setDefaultConfiguration(message);
|
||||
|
||||
buildPack = configuration.buildPack;
|
||||
port = configuration.port;
|
||||
installCommand = configuration.installCommand;
|
||||
startCommand = configuration.startCommand;
|
||||
buildCommand = configuration.buildCommand;
|
||||
publishDirectory = configuration.publishDirectory;
|
||||
baseDirectory = configuration.baseDirectory;
|
||||
dockerFileLocation = configuration.dockerFileLocation;
|
||||
denoMainFile = configuration.denoMainFile;
|
||||
const commit = await importers[gitSource.type]({
|
||||
applicationId,
|
||||
debug,
|
||||
workdir,
|
||||
repodir,
|
||||
githubAppId: gitSource.githubApp?.id,
|
||||
gitlabAppId: gitSource.gitlabApp?.id,
|
||||
customPort: gitSource.customPort,
|
||||
repository,
|
||||
branch,
|
||||
buildId,
|
||||
apiUrl: gitSource.apiUrl,
|
||||
htmlUrl: gitSource.htmlUrl,
|
||||
projectId,
|
||||
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
|
||||
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null
|
||||
});
|
||||
if (!commit) {
|
||||
throw new Error('No commit found?');
|
||||
}
|
||||
let tag = commit.slice(0, 7);
|
||||
if (pullmergeRequestId) {
|
||||
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
||||
}
|
||||
const th = throttle(async () => {
|
||||
try {
|
||||
const queuedBuilds = await prisma.build.findMany({ where: { status: { in: ['queued', 'running'] } }, orderBy: { createdAt: 'asc' } });
|
||||
const { concurrentBuilds } = await prisma.setting.findFirst({})
|
||||
if (queuedBuilds.length > 0) {
|
||||
parentPort.postMessage({ deploying: true });
|
||||
const concurrency = concurrentBuilds;
|
||||
const pAll = await import('p-all');
|
||||
const actions = []
|
||||
|
||||
for (const queueBuild of queuedBuilds) {
|
||||
actions.push(async () => {
|
||||
let application = await prisma.application.findUnique({ where: { id: queueBuild.applicationId }, include: { destinationDocker: true, gitSource: { include: { githubApp: true, gitlabApp: true } }, persistentStorage: true, secrets: true, settings: true, teams: true } })
|
||||
let { id: buildId, type, sourceBranch = null, pullmergeRequestId = null, forceRebuild } = queueBuild
|
||||
application = decryptApplication(application)
|
||||
try {
|
||||
await prisma.build.update({ where: { id: buildId }, data: { commit } });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
if (!pullmergeRequestId) {
|
||||
if (queueBuild.status === 'running') {
|
||||
await saveBuildLog({ line: 'Building halted, restarting...', buildId, applicationId: application.id });
|
||||
}
|
||||
const {
|
||||
id: applicationId,
|
||||
repository,
|
||||
name,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
gitSource,
|
||||
configHash,
|
||||
fqdn,
|
||||
projectId,
|
||||
secrets,
|
||||
phpModules,
|
||||
settings,
|
||||
persistentStorage,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
denoOptions,
|
||||
exposePort,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType,
|
||||
} = application
|
||||
let {
|
||||
branch,
|
||||
buildPack,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory,
|
||||
dockerFileLocation,
|
||||
denoMainFile
|
||||
} = application
|
||||
const currentHash = crypto
|
||||
//@ts-ignore
|
||||
.createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify({
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
deploymentType,
|
||||
denoOptions,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
buildPack,
|
||||
port,
|
||||
exposePort,
|
||||
@@ -165,42 +103,178 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
})
|
||||
)
|
||||
.digest('hex');
|
||||
|
||||
if (configHash !== currentHash) {
|
||||
await prisma.application.update({
|
||||
where: { id: applicationId },
|
||||
data: { configHash: currentHash }
|
||||
const { debug } = settings;
|
||||
if (concurrency === 1) {
|
||||
await prisma.build.updateMany({
|
||||
where: {
|
||||
status: { in: ['queued', 'running'] },
|
||||
id: { not: buildId },
|
||||
applicationId,
|
||||
createdAt: { lt: new Date(new Date().getTime() - 10 * 1000) }
|
||||
},
|
||||
data: { status: 'failed' }
|
||||
});
|
||||
deployNeeded = true;
|
||||
if (configHash) {
|
||||
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
|
||||
}
|
||||
} else {
|
||||
deployNeeded = false;
|
||||
}
|
||||
} else {
|
||||
deployNeeded = true;
|
||||
}
|
||||
let imageId = applicationId;
|
||||
let domain = getDomain(fqdn);
|
||||
const volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
||||
}${storage.path}`;
|
||||
}) || [];
|
||||
// Previews, we need to get the source branch and set subdomain
|
||||
if (pullmergeRequestId) {
|
||||
branch = sourceBranch;
|
||||
domain = `${pullmergeRequestId}.${domain}`;
|
||||
imageId = `${applicationId}-${pullmergeRequestId}`;
|
||||
}
|
||||
|
||||
let imageFound = false;
|
||||
try {
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker image inspect ${applicationId}:${tag}`
|
||||
})
|
||||
imageFound = true;
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
// if (!imageFound || deployNeeded) {
|
||||
if (true) {
|
||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
||||
if (buildpacks[buildPack])
|
||||
await buildpacks[buildPack]({
|
||||
dockerId: destinationDocker.id,
|
||||
buildId,
|
||||
let deployNeeded = true;
|
||||
let destinationType;
|
||||
|
||||
if (destinationDockerId) {
|
||||
destinationType = 'docker';
|
||||
}
|
||||
if (destinationType === 'docker') {
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
||||
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
||||
const configuration = await setDefaultConfiguration(application);
|
||||
|
||||
buildPack = configuration.buildPack;
|
||||
port = configuration.port;
|
||||
installCommand = configuration.installCommand;
|
||||
startCommand = configuration.startCommand;
|
||||
buildCommand = configuration.buildCommand;
|
||||
publishDirectory = configuration.publishDirectory;
|
||||
baseDirectory = configuration.baseDirectory;
|
||||
dockerFileLocation = configuration.dockerFileLocation;
|
||||
denoMainFile = configuration.denoMainFile;
|
||||
const commit = await importers[gitSource.type]({
|
||||
applicationId,
|
||||
domain,
|
||||
debug,
|
||||
workdir,
|
||||
repodir,
|
||||
githubAppId: gitSource.githubApp?.id,
|
||||
gitlabAppId: gitSource.gitlabApp?.id,
|
||||
customPort: gitSource.customPort,
|
||||
repository,
|
||||
branch,
|
||||
buildId,
|
||||
apiUrl: gitSource.apiUrl,
|
||||
htmlUrl: gitSource.htmlUrl,
|
||||
projectId,
|
||||
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
|
||||
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null,
|
||||
forPublic: gitSource.forPublic
|
||||
});
|
||||
if (!commit) {
|
||||
throw new Error('No commit found?');
|
||||
}
|
||||
let tag = commit.slice(0, 7);
|
||||
if (pullmergeRequestId) {
|
||||
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.build.update({ where: { id: buildId }, data: { commit } });
|
||||
} catch (err) { }
|
||||
|
||||
if (!pullmergeRequestId) {
|
||||
if (configHash !== currentHash) {
|
||||
deployNeeded = true;
|
||||
if (configHash) {
|
||||
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
|
||||
}
|
||||
} else {
|
||||
deployNeeded = false;
|
||||
}
|
||||
} else {
|
||||
deployNeeded = true;
|
||||
}
|
||||
|
||||
let imageFound = false;
|
||||
try {
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker image inspect ${applicationId}:${tag}`
|
||||
})
|
||||
imageFound = true;
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
||||
|
||||
if (forceRebuild) deployNeeded = true
|
||||
if (!imageFound || deployNeeded) {
|
||||
// if (true) {
|
||||
if (buildpacks[buildPack])
|
||||
await buildpacks[buildPack]({
|
||||
dockerId: destinationDocker.id,
|
||||
buildId,
|
||||
applicationId,
|
||||
domain,
|
||||
name,
|
||||
type,
|
||||
pullmergeRequestId,
|
||||
buildPack,
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
publishDirectory,
|
||||
debug,
|
||||
commit,
|
||||
tag,
|
||||
workdir,
|
||||
port: exposePort ? `${exposePort}:${port}` : port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
phpModules,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
dockerFileLocation,
|
||||
denoMainFile,
|
||||
denoOptions,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType
|
||||
});
|
||||
else {
|
||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||
throw new Error(`Build pack ${buildPack} not found.`);
|
||||
}
|
||||
} else {
|
||||
await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
|
||||
}
|
||||
try {
|
||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker stop -t 0 ${imageId}` })
|
||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker rm ${imageId}` })
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
const envs = [
|
||||
`PORT=${port}`
|
||||
];
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (pullmergeRequestId) {
|
||||
if (secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||
const labels = makeLabelForStandaloneApplication({
|
||||
applicationId,
|
||||
fqdn,
|
||||
name,
|
||||
type,
|
||||
pullmergeRequestId,
|
||||
@@ -208,150 +282,90 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
publishDirectory,
|
||||
debug,
|
||||
commit,
|
||||
tag,
|
||||
workdir,
|
||||
port: exposePort ? `${exposePort}:${port}` : port,
|
||||
commit,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
phpModules,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
dockerFileLocation,
|
||||
denoMainFile,
|
||||
denoOptions,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType
|
||||
publishDirectory
|
||||
});
|
||||
else {
|
||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||
throw new Error(`Build pack ${buildPack} not found.`);
|
||||
}
|
||||
} else {
|
||||
await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
|
||||
}
|
||||
try {
|
||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker stop -t 0 ${imageId}` })
|
||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker rm ${imageId}` })
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
const envs = [];
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (pullmergeRequestId) {
|
||||
if (secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
let envFound = false;
|
||||
try {
|
||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
});
|
||||
}
|
||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||
const labels = makeLabelForStandaloneApplication({
|
||||
applicationId,
|
||||
fqdn,
|
||||
name,
|
||||
type,
|
||||
pullmergeRequestId,
|
||||
buildPack,
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
port: exposePort ? `${exposePort}:${port}` : port,
|
||||
commit,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory
|
||||
});
|
||||
let envFound = false;
|
||||
try {
|
||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
try {
|
||||
await saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
}
|
||||
};
|
||||
});
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[imageId]: {
|
||||
image: `${applicationId}:${tag}`,
|
||||
container_name: imageId,
|
||||
volumes,
|
||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||
networks: [destinationDocker.network],
|
||||
labels,
|
||||
depends_on: [],
|
||||
restart: 'always',
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
// logging: {
|
||||
// driver: 'fluentd',
|
||||
// },
|
||||
deploy: {
|
||||
restart_policy: {
|
||||
condition: 'on-failure',
|
||||
delay: '5s',
|
||||
max_attempts: 3,
|
||||
window: '120s'
|
||||
try {
|
||||
await saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[destinationDocker.network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: Object.assign({}, ...composeVolumes)
|
||||
};
|
||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
||||
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||
} catch (error) {
|
||||
await saveBuildLog({ line: error, buildId, applicationId });
|
||||
await prisma.build.update({
|
||||
where: { id: message.build_id },
|
||||
};
|
||||
});
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[imageId]: {
|
||||
image: `${applicationId}:${tag}`,
|
||||
container_name: imageId,
|
||||
volumes,
|
||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||
labels,
|
||||
depends_on: [],
|
||||
expose: [port],
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
// logging: {
|
||||
// driver: 'fluentd',
|
||||
// },
|
||||
...defaultComposeConfiguration(destinationDocker.network),
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[destinationDocker.network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: Object.assign({}, ...composeVolumes)
|
||||
};
|
||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
||||
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
||||
} catch (error) {
|
||||
await saveBuildLog({ line: error, buildId, applicationId });
|
||||
await prisma.build.updateMany({
|
||||
where: { id: buildId, status: { in: ['queued', 'running'] } },
|
||||
data: { status: 'failed' }
|
||||
});
|
||||
throw new Error(error);
|
||||
}
|
||||
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
||||
if (!pullmergeRequestId) await prisma.application.update({
|
||||
where: { id: applicationId },
|
||||
data: { configHash: currentHash }
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
await prisma.build.updateMany({
|
||||
where: { id: buildId, status: { in: ['queued', 'running'] } },
|
||||
data: { status: 'failed' }
|
||||
});
|
||||
throw new Error(error);
|
||||
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
||||
}
|
||||
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
||||
await prisma.build.update({ where: { id: message.build_id }, data: { status: 'success' } });
|
||||
}
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
await prisma.build.update({
|
||||
where: { id: message.build_id },
|
||||
data: { status: 'failed' }
|
||||
});
|
||||
await saveBuildLog({ line: error, buildId, applicationId });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
});
|
||||
await prisma.$disconnect();
|
||||
await pAll.default(actions, { concurrency })
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
});
|
||||
})
|
||||
while (true) {
|
||||
await th()
|
||||
}
|
||||
} else process.exit(0);
|
||||
})();
|
||||
|
||||
226
apps/api/src/jobs/infrastructure.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import { parentPort } from 'node:worker_threads';
|
||||
import axios from 'axios';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, listSettings, version, createRemoteEngineConfiguration } from '../lib/common';
|
||||
|
||||
async function autoUpdater() {
|
||||
try {
|
||||
const currentVersion = version;
|
||||
const { data: versions } = await axios
|
||||
.get(
|
||||
`https://get.coollabs.io/versions.json`
|
||||
, {
|
||||
params: {
|
||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||
version: currentVersion
|
||||
}
|
||||
})
|
||||
const latestVersion = versions['coolify'].main.version;
|
||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||
if (isUpdateAvailable === 1) {
|
||||
const activeCount = 0
|
||||
if (activeCount === 0) {
|
||||
if (!isDev) {
|
||||
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
|
||||
await asyncExecShell(`env | grep COOLIFY > .env`);
|
||||
await asyncExecShell(
|
||||
`sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=true' .env`
|
||||
);
|
||||
await asyncExecShell(
|
||||
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify && docker rm coolify && docker compose up -d --force-recreate"`
|
||||
);
|
||||
} else {
|
||||
console.log('Updating (not really in dev mode).');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
}
|
||||
async function checkProxies() {
|
||||
try {
|
||||
const { default: isReachable } = await import('is-port-reachable');
|
||||
let portReachable;
|
||||
|
||||
const { arch, ipv4, ipv6 } = await listSettings();
|
||||
|
||||
// Coolify Proxy local
|
||||
const engine = '/var/run/docker.sock';
|
||||
const localDocker = await prisma.destinationDocker.findFirst({
|
||||
where: { engine, network: 'coolify', isCoolifyProxyUsed: true }
|
||||
});
|
||||
if (localDocker) {
|
||||
portReachable = await isReachable(80, { host: ipv4 || ipv6 })
|
||||
if (!portReachable) {
|
||||
await startTraefikProxy(localDocker.id);
|
||||
}
|
||||
}
|
||||
// Coolify Proxy remote
|
||||
const remoteDocker = await prisma.destinationDocker.findMany({
|
||||
where: { remoteEngine: true, remoteVerified: true }
|
||||
});
|
||||
if (remoteDocker.length > 0) {
|
||||
for (const docker of remoteDocker) {
|
||||
if (docker.isCoolifyProxyUsed) {
|
||||
portReachable = await isReachable(80, { host: docker.remoteIpAddress })
|
||||
if (!portReachable) {
|
||||
await startTraefikProxy(docker.id);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await createRemoteEngineConfiguration(docker.id)
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
// TCP Proxies
|
||||
const databasesWithPublicPort = await prisma.database.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
include: { settings: true, destinationDocker: true }
|
||||
});
|
||||
for (const database of databasesWithPublicPort) {
|
||||
const { destinationDockerId, destinationDocker, publicPort, id } = database;
|
||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||
const { privatePort } = generateDatabaseConfiguration(database, arch);
|
||||
portReachable = await isReachable(publicPort, { host: destinationDocker.remoteIpAddress || ipv4 || ipv6 })
|
||||
if (!portReachable) {
|
||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||
}
|
||||
}
|
||||
}
|
||||
const wordpressWithFtp = await prisma.wordpress.findMany({
|
||||
where: { ftpPublicPort: { not: null } },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
for (const ftp of wordpressWithFtp) {
|
||||
const { service, ftpPublicPort } = ftp;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||
portReachable = await isReachable(ftpPublicPort, { host: destinationDocker.remoteIpAddress || ipv4 || ipv6 })
|
||||
if (!portReachable) {
|
||||
await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP Proxies
|
||||
const minioInstances = await prisma.minio.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
for (const minio of minioInstances) {
|
||||
const { service, publicPort } = minio;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||
portReachable = await isReachable(publicPort, { host: destinationDocker.remoteIpAddress || ipv4 || ipv6 })
|
||||
if (!portReachable) {
|
||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
async function cleanupPrismaEngines() {
|
||||
if (!isDev) {
|
||||
try {
|
||||
const { stdout } = await asyncExecShell(`ps -ef | grep /app/prisma-engines/query-engine | grep -v grep | wc -l | xargs`)
|
||||
if (stdout.trim() != null && stdout.trim() != '' && Number(stdout.trim()) > 1) {
|
||||
await asyncExecShell(`killall -q -e /app/prisma-engines/query-engine -o 1m`)
|
||||
}
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
async function cleanupStorage() {
|
||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||
let enginesDone = new Set()
|
||||
for (const destination of destinationDockers) {
|
||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return
|
||||
if (destination.engine) enginesDone.add(destination.engine)
|
||||
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress)
|
||||
|
||||
let lowDiskSpace = false;
|
||||
try {
|
||||
let stdout = null
|
||||
if (!isDev) {
|
||||
const output = await executeDockerCmd({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'` })
|
||||
stdout = output.stdout;
|
||||
} else {
|
||||
const output = await asyncExecShell(
|
||||
`df -kPT /`
|
||||
);
|
||||
stdout = output.stdout;
|
||||
}
|
||||
let lines = stdout.trim().split('\n');
|
||||
let header = lines[0];
|
||||
let regex =
|
||||
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
||||
const boundaries = [];
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(header))) {
|
||||
boundaries.push(match[0].length);
|
||||
}
|
||||
|
||||
boundaries[boundaries.length - 1] = -1;
|
||||
const data = lines.slice(1).map((line) => {
|
||||
const cl = boundaries.map((boundary) => {
|
||||
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||
line = line.slice(boundary);
|
||||
return column.trim();
|
||||
});
|
||||
return {
|
||||
capacity: Number.parseInt(cl[5], 10) / 100
|
||||
};
|
||||
});
|
||||
if (data.length > 0) {
|
||||
const { capacity } = data[0];
|
||||
if (capacity > 0.8) {
|
||||
lowDiskSpace = true;
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
let status = {
|
||||
cleanupStorage: false,
|
||||
autoUpdater: false
|
||||
}
|
||||
if (parentPort) {
|
||||
parentPort.on('message', async (message) => {
|
||||
if (parentPort) {
|
||||
if (message === 'error') throw new Error('oops');
|
||||
if (message === 'cancel') {
|
||||
parentPort.postMessage('cancelled');
|
||||
process.exit(1);
|
||||
}
|
||||
if (message === 'action:cleanupStorage') {
|
||||
if (!status.autoUpdater) {
|
||||
status.cleanupStorage = true
|
||||
await cleanupStorage();
|
||||
status.cleanupStorage = false
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (message === 'action:cleanupPrismaEngines') {
|
||||
await cleanupPrismaEngines();
|
||||
return;
|
||||
}
|
||||
if (message === 'action:checkProxies') {
|
||||
await checkProxies();
|
||||
return;
|
||||
}
|
||||
if (message === 'action:autoUpdater') {
|
||||
if (!status.cleanupStorage) {
|
||||
status.autoUpdater = true
|
||||
await autoUpdater();
|
||||
status.autoUpdater = false
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else process.exit(0);
|
||||
})();
|
||||
@@ -89,6 +89,22 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
}
|
||||
];
|
||||
const phpVersions = [
|
||||
{
|
||||
value: 'webdevops/php-apache:8.2',
|
||||
label: 'webdevops/php-apache:8.2'
|
||||
},
|
||||
{
|
||||
value: 'webdevops/php-nginx:8.2',
|
||||
label: 'webdevops/php-nginx:8.2'
|
||||
},
|
||||
{
|
||||
value: 'webdevops/php-apache:8.1',
|
||||
label: 'webdevops/php-apache:8.1'
|
||||
},
|
||||
{
|
||||
value: 'webdevops/php-nginx:8.1',
|
||||
label: 'webdevops/php-nginx:8.1'
|
||||
},
|
||||
{
|
||||
value: 'webdevops/php-apache:8.0',
|
||||
label: 'webdevops/php-apache:8.0'
|
||||
@@ -145,6 +161,22 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
value: 'webdevops/php-nginx:5.6',
|
||||
label: 'webdevops/php-nginx:5.6'
|
||||
},
|
||||
{
|
||||
value: 'webdevops/php-apache:8.2-alpine',
|
||||
label: 'webdevops/php-apache:8.2-alpine'
|
||||
},
|
||||
{
|
||||
value: 'webdevops/php-nginx:8.2-alpine',
|
||||
label: 'webdevops/php-nginx:8.2-alpine'
|
||||
},
|
||||
{
|
||||
value: 'webdevops/php-apache:8.1-alpine',
|
||||
label: 'webdevops/php-apache:8.1-alpine'
|
||||
},
|
||||
{
|
||||
value: 'webdevops/php-nginx:8.1-alpine',
|
||||
label: 'webdevops/php-nginx:8.1-alpine'
|
||||
},
|
||||
{
|
||||
value: 'webdevops/php-apache:8.0-alpine',
|
||||
label: 'webdevops/php-apache:8.0-alpine'
|
||||
@@ -252,6 +284,20 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
label: 'python:3.7-slim-bullseye'
|
||||
}
|
||||
];
|
||||
const herokuVersions = [
|
||||
{
|
||||
value: 'heroku/builder:22',
|
||||
label: 'heroku/builder:22'
|
||||
},
|
||||
{
|
||||
value: 'heroku/buildpacks:20',
|
||||
label: 'heroku/buildpacks:20'
|
||||
},
|
||||
{
|
||||
value: 'heroku/builder-classic:22',
|
||||
label: 'heroku/builder-classic:22'
|
||||
},
|
||||
]
|
||||
let payload: any = {
|
||||
baseImage: null,
|
||||
baseBuildImage: null,
|
||||
@@ -291,14 +337,19 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
payload.baseImage = 'denoland/deno:latest';
|
||||
}
|
||||
if (buildPack === 'php') {
|
||||
payload.baseImage = 'webdevops/php-apache:8.0-alpine';
|
||||
payload.baseImage = 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseImages = phpVersions;
|
||||
}
|
||||
if (buildPack === 'laravel') {
|
||||
payload.baseImage = 'webdevops/php-apache:8.0-alpine';
|
||||
payload.baseImage = 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseBuildImage = 'node:18';
|
||||
payload.baseBuildImages = nodeVersions;
|
||||
}
|
||||
if (buildPack === 'heroku') {
|
||||
payload.baseImage = 'heroku/buildpacks:20';
|
||||
payload.baseImages = herokuVersions;
|
||||
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
@@ -493,7 +544,6 @@ export async function copyBaseConfigurationFiles(
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -522,9 +572,6 @@ export async function buildImage({
|
||||
} else {
|
||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
||||
}
|
||||
if (debug) {
|
||||
await saveBuildLog({ line: `\n###############\nIMPORTANT: Due to some issues during implementing Remote Docker Engine, the builds logs are not streamed at the moment. You will see the full build log when the build is finished!\n###############`, buildId, applicationId });
|
||||
}
|
||||
if (!debug && isCache) {
|
||||
await saveBuildLog({
|
||||
line: `Debug turned off. To see more details, allow it in the configuration.`,
|
||||
@@ -534,54 +581,11 @@ export async function buildImage({
|
||||
}
|
||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||
const { stderr } = await executeDockerCmd({ dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` })
|
||||
if (debug) {
|
||||
const array = stderr.split('\n')
|
||||
for (const line of array) {
|
||||
if (line !== '\n') {
|
||||
await saveBuildLog({
|
||||
line: `${line.replace('\n', '')}`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
}
|
||||
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` })
|
||||
const { status } = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
if (status === 'canceled') {
|
||||
throw new Error('Deployment canceled.')
|
||||
}
|
||||
|
||||
|
||||
// await new Promise((resolve, reject) => {
|
||||
// const command = spawn(`docker`, ['build', '-f', `${workdir}${dockerFile}`, '-t', `${cache}`,`${workdir}`], {
|
||||
// env: {
|
||||
// DOCKER_HOST: 'ssh://root@95.217.178.202',
|
||||
// DOCKER_BUILDKIT: '1'
|
||||
// }
|
||||
// });
|
||||
// command.stdout.on('data', function (data) {
|
||||
// console.log('stdout: ' + data);
|
||||
// });
|
||||
// command.stderr.on('data', function (data) {
|
||||
// console.log('stderr: ' + data);
|
||||
// });
|
||||
// command.on('error', function (error) {
|
||||
// console.log(error)
|
||||
// reject(error)
|
||||
// })
|
||||
// command.on('exit', function (code) {
|
||||
// console.log('exit code: ' + code);
|
||||
// resolve(code)
|
||||
// });
|
||||
// })
|
||||
|
||||
|
||||
// console.log({ stdout, stderr })
|
||||
// const stream = await docker.engine.buildImage(
|
||||
// { src: ['.'], context: workdir },
|
||||
// {
|
||||
// dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation,
|
||||
// t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||
// }
|
||||
// );
|
||||
// await streamEvents({ stream, docker, buildId, applicationId, debug });
|
||||
if (isCache) {
|
||||
await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId });
|
||||
} else {
|
||||
@@ -698,11 +702,10 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
if (isPnpm) {
|
||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
if (installCommand) {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''}/package.json ./`);
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
}
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
await buildImage({ ...data, isCache: true });
|
||||
|
||||
39
apps/api/src/lib/buildPacks/heroku.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { executeDockerCmd, prisma } from "../common"
|
||||
import { saveBuildLog } from "./common";
|
||||
|
||||
export default async function (data: any): Promise<void> {
|
||||
const { buildId, applicationId, tag, dockerId, debug, workdir } = data
|
||||
try {
|
||||
|
||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
||||
const { stdout } = await executeDockerCmd({
|
||||
dockerId,
|
||||
command: `pack build -p ${workdir} ${applicationId}:${tag} --builder heroku/buildpacks:20`
|
||||
})
|
||||
if (debug) {
|
||||
const array = stdout.split('\n')
|
||||
for (const line of array) {
|
||||
if (line !== '\n') {
|
||||
await saveBuildLog({
|
||||
line: `${line.replace('\n', '')}`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
|
||||
} catch (error) {
|
||||
const array = error.stdout.split('\n')
|
||||
for (const line of array) {
|
||||
if (line !== '\n') {
|
||||
await saveBuildLog({
|
||||
line: `${line.replace('\n', '')}`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import eleventy from './static';
|
||||
import python from './python';
|
||||
import deno from './deno';
|
||||
import laravel from './laravel';
|
||||
import heroku from './heroku';
|
||||
|
||||
export {
|
||||
node,
|
||||
@@ -33,5 +34,6 @@ export {
|
||||
eleventy,
|
||||
python,
|
||||
deno,
|
||||
laravel
|
||||
laravel,
|
||||
heroku
|
||||
};
|
||||
|
||||
@@ -71,13 +71,11 @@ export async function removeContainer({
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
|
||||
|
||||
if (JSON.parse(stdout).Running) {
|
||||
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
||||
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ export default async function ({
|
||||
htmlUrl,
|
||||
branch,
|
||||
buildId,
|
||||
customPort
|
||||
customPort,
|
||||
forPublic
|
||||
}: {
|
||||
applicationId: string;
|
||||
workdir: string;
|
||||
@@ -23,41 +24,55 @@ export default async function ({
|
||||
branch: string;
|
||||
buildId: string;
|
||||
customPort: number;
|
||||
forPublic?: boolean;
|
||||
}): Promise<string> {
|
||||
const { default: got } = await import('got')
|
||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
||||
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
||||
if (forPublic) {
|
||||
await saveBuildLog({
|
||||
line: `Cloning ${repository}:${branch} branch.`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
await asyncExecShell(
|
||||
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||
);
|
||||
|
||||
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
||||
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
|
||||
const { privateKey, appId, installationId } = body
|
||||
} else {
|
||||
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
||||
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
|
||||
const { privateKey, appId, installationId } = body
|
||||
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
||||
|
||||
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
||||
|
||||
const payload = {
|
||||
iat: Math.round(new Date().getTime() / 1000),
|
||||
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||
iss: appId
|
||||
};
|
||||
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
||||
algorithm: 'RS256'
|
||||
});
|
||||
const { token } = await got
|
||||
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwtToken}`,
|
||||
Accept: 'application/vnd.github.machine-man-preview+json'
|
||||
}
|
||||
})
|
||||
.json();
|
||||
await saveBuildLog({
|
||||
line: `Cloning ${repository}:${branch} branch.`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
await asyncExecShell(
|
||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||
);
|
||||
const payload = {
|
||||
iat: Math.round(new Date().getTime() / 1000),
|
||||
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||
iss: appId
|
||||
};
|
||||
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
||||
algorithm: 'RS256'
|
||||
});
|
||||
const { token } = await got
|
||||
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwtToken}`,
|
||||
Accept: 'application/vnd.github.machine-man-preview+json'
|
||||
}
|
||||
})
|
||||
.json();
|
||||
await saveBuildLog({
|
||||
line: `Cloning ${repository}:${branch} branch.`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
await asyncExecShell(
|
||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||
);
|
||||
}
|
||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||
|
||||
return commit.replace('\n', '');
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,51 +2,29 @@ import Bree from 'bree';
|
||||
import path from 'path';
|
||||
import Cabin from 'cabin';
|
||||
import TSBree from '@breejs/ts-worker';
|
||||
import { isDev } from './common';
|
||||
|
||||
export const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
Bree.extend(TSBree);
|
||||
|
||||
const options: any = {
|
||||
defaultExtension: 'js',
|
||||
// logger: new Cabin(),
|
||||
logger: false,
|
||||
workerMessageHandler: async ({ name, message }) => {
|
||||
if (name === 'deployApplication') {
|
||||
if (message.pending === 0 && message.size === 0) {
|
||||
if (message.caller === 'autoUpdater') {
|
||||
if (!scheduler.workers.has('autoUpdater')) {
|
||||
await scheduler.stop('deployApplication');
|
||||
await scheduler.run('autoUpdater')
|
||||
}
|
||||
}
|
||||
if (message.caller === 'cleanupStorage') {
|
||||
if (!scheduler.workers.has('cleanupStorage')) {
|
||||
await scheduler.stop('deployApplication');
|
||||
await scheduler.run('cleanupStorage')
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'deployApplication' && message?.deploying) {
|
||||
if (scheduler.workers.has('autoUpdater') || scheduler.workers.has('cleanupStorage')) {
|
||||
scheduler.workers.get('deployApplication').postMessage('cancel')
|
||||
}
|
||||
}
|
||||
},
|
||||
jobs: [
|
||||
{
|
||||
name: 'deployApplication'
|
||||
},
|
||||
{
|
||||
name: 'cleanupStorage',
|
||||
},
|
||||
{
|
||||
name: 'checkProxies',
|
||||
interval: '10s'
|
||||
},
|
||||
{
|
||||
name: 'autoUpdater',
|
||||
}
|
||||
{ name: 'infrastructure' },
|
||||
{ name: 'deployApplication' },
|
||||
],
|
||||
};
|
||||
if (isDev) options.root = path.join(__dirname, '../jobs');
|
||||
|
||||
|
||||
export const scheduler = new Bree(options);
|
||||
|
||||
|
||||
|
||||
20
apps/api/src/lib/services.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createDirectories, getServiceFromDB, getServiceImage, getServiceMainPort, makeLabelForServices } from "./common";
|
||||
|
||||
export async function defaultServiceConfigurations({ id, teamId }) {
|
||||
const service = await getServiceFromDB({ id, teamId });
|
||||
const { destinationDockerId, destinationDocker, type, serviceSecret } = service;
|
||||
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const port = getServiceMainPort(type);
|
||||
|
||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||
|
||||
const image = getServiceImage(type);
|
||||
let secrets = [];
|
||||
if (serviceSecret.length > 0) {
|
||||
serviceSecret.forEach((secret) => {
|
||||
secrets.push(`${secret.name}=${secret.value}`);
|
||||
});
|
||||
}
|
||||
return { ...service, network, port, workdir, image, secrets }
|
||||
}
|
||||
383
apps/api/src/lib/services/common.ts
Normal file
@@ -0,0 +1,383 @@
|
||||
|
||||
import cuid from 'cuid';
|
||||
import { encrypt, generatePassword, prisma } from '../common';
|
||||
|
||||
export const includeServices: any = {
|
||||
destinationDocker: true,
|
||||
persistentStorage: true,
|
||||
serviceSecret: true,
|
||||
minio: true,
|
||||
plausibleAnalytics: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true,
|
||||
ghost: true,
|
||||
meiliSearch: true,
|
||||
umami: true,
|
||||
hasura: true,
|
||||
fider: true,
|
||||
moodle: true,
|
||||
appwrite: true,
|
||||
glitchTip: true,
|
||||
searxng: true,
|
||||
weblate: true,
|
||||
taiga: true
|
||||
};
|
||||
export async function configureServiceType({
|
||||
id,
|
||||
type
|
||||
}: {
|
||||
id: string;
|
||||
type: string;
|
||||
}): Promise<void> {
|
||||
if (type === 'plausibleanalytics') {
|
||||
const password = encrypt(generatePassword({}));
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'plausibleanalytics';
|
||||
const secretKeyBase = encrypt(generatePassword({ length: 64 }));
|
||||
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
plausibleAnalytics: {
|
||||
create: {
|
||||
postgresqlDatabase,
|
||||
postgresqlUser,
|
||||
postgresqlPassword,
|
||||
password,
|
||||
secretKeyBase
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'nocodb') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: { type }
|
||||
});
|
||||
} else if (type === 'minio') {
|
||||
const rootUser = cuid();
|
||||
const rootUserPassword = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: { type, minio: { create: { rootUser, rootUserPassword } } }
|
||||
});
|
||||
} else if (type === 'vscodeserver') {
|
||||
const password = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: { type, vscodeserver: { create: { password } } }
|
||||
});
|
||||
} else if (type === 'wordpress') {
|
||||
const mysqlUser = cuid();
|
||||
const mysqlPassword = encrypt(generatePassword({}));
|
||||
const mysqlRootUser = cuid();
|
||||
const mysqlRootUserPassword = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } }
|
||||
}
|
||||
});
|
||||
} else if (type === 'vaultwarden') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type
|
||||
}
|
||||
});
|
||||
} else if (type === 'languagetool') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
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()}@example.com`;
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'meilisearch') {
|
||||
const masterKey = encrypt(generatePassword({ length: 32 }));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
meiliSearch: { create: { masterKey } }
|
||||
}
|
||||
});
|
||||
} else if (type === 'umami') {
|
||||
const umamiAdminPassword = encrypt(generatePassword({}));
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'umami';
|
||||
const hashSalt = encrypt(generatePassword({ length: 64 }));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
umami: {
|
||||
create: {
|
||||
umamiAdminPassword,
|
||||
postgresqlDatabase,
|
||||
postgresqlPassword,
|
||||
postgresqlUser,
|
||||
hashSalt
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'hasura') {
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'hasura';
|
||||
const graphQLAdminPassword = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
hasura: {
|
||||
create: {
|
||||
postgresqlDatabase,
|
||||
postgresqlPassword,
|
||||
postgresqlUser,
|
||||
graphQLAdminPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'fider') {
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'fider';
|
||||
const jwtSecret = encrypt(generatePassword({ length: 64, symbols: true }));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
fider: {
|
||||
create: {
|
||||
postgresqlDatabase,
|
||||
postgresqlPassword,
|
||||
postgresqlUser,
|
||||
jwtSecret
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'moodle') {
|
||||
const defaultUsername = cuid();
|
||||
const defaultPassword = encrypt(generatePassword({}));
|
||||
const defaultEmail = `${cuid()} @example.com`;
|
||||
const mariadbUser = cuid();
|
||||
const mariadbPassword = encrypt(generatePassword({}));
|
||||
const mariadbDatabase = 'moodle_db';
|
||||
const mariadbRootUser = cuid();
|
||||
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
moodle: {
|
||||
create: {
|
||||
defaultUsername,
|
||||
defaultPassword,
|
||||
defaultEmail,
|
||||
mariadbUser,
|
||||
mariadbPassword,
|
||||
mariadbDatabase,
|
||||
mariadbRootUser,
|
||||
mariadbRootUserPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'appwrite') {
|
||||
const opensslKeyV1 = encrypt(generatePassword({}));
|
||||
const executorSecret = encrypt(generatePassword({}));
|
||||
const redisPassword = encrypt(generatePassword({}));
|
||||
const mariadbHost = `${id}-mariadb`
|
||||
const mariadbUser = cuid();
|
||||
const mariadbPassword = encrypt(generatePassword({}));
|
||||
const mariadbDatabase = 'appwrite';
|
||||
const mariadbRootUser = cuid();
|
||||
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
appwrite: {
|
||||
create: {
|
||||
opensslKeyV1,
|
||||
executorSecret,
|
||||
redisPassword,
|
||||
mariadbHost,
|
||||
mariadbUser,
|
||||
mariadbPassword,
|
||||
mariadbDatabase,
|
||||
mariadbRootUser,
|
||||
mariadbRootUserPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'glitchTip') {
|
||||
const defaultUsername = cuid();
|
||||
const defaultEmail = `${defaultUsername}@example.com`;
|
||||
const defaultPassword = encrypt(generatePassword({}));
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'glitchTip';
|
||||
const secretKeyBase = encrypt(generatePassword({ length: 64 }));
|
||||
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
glitchTip: {
|
||||
create: {
|
||||
postgresqlDatabase,
|
||||
postgresqlUser,
|
||||
postgresqlPassword,
|
||||
secretKeyBase,
|
||||
defaultEmail,
|
||||
defaultUsername,
|
||||
defaultPassword,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'searxng') {
|
||||
const secretKey = encrypt(generatePassword({ length: 32, isHex: true }))
|
||||
const redisPassword = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
searxng: {
|
||||
create: {
|
||||
secretKey,
|
||||
redisPassword,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'weblate') {
|
||||
const adminPassword = encrypt(generatePassword({}))
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'weblate';
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
weblate: {
|
||||
create: {
|
||||
adminPassword,
|
||||
postgresqlHost: `${id}-postgresql`,
|
||||
postgresqlPort: 5432,
|
||||
postgresqlUser,
|
||||
postgresqlPassword,
|
||||
postgresqlDatabase,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'taiga') {
|
||||
const secretKey = encrypt(generatePassword({}))
|
||||
const erlangSecret = encrypt(generatePassword({}))
|
||||
const rabbitMQUser = cuid();
|
||||
const djangoAdminUser = cuid();
|
||||
const djangoAdminPassword = encrypt(generatePassword({}))
|
||||
const rabbitMQPassword = encrypt(generatePassword({}))
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'taiga';
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
taiga: {
|
||||
create: {
|
||||
secretKey,
|
||||
erlangSecret,
|
||||
djangoAdminUser,
|
||||
djangoAdminPassword,
|
||||
rabbitMQUser,
|
||||
rabbitMQPassword,
|
||||
postgresqlHost: `${id}-postgresql`,
|
||||
postgresqlPort: 5432,
|
||||
postgresqlUser,
|
||||
postgresqlPassword,
|
||||
postgresqlDatabase,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeService({ id }: { id: string }): Promise<void> {
|
||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.fider.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.umami.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.hasura.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.glitchTip.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.moodle.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.searxng.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.weblate.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.taiga.deleteMany({ where: { serviceId: id } });
|
||||
|
||||
await prisma.service.delete({ where: { id } });
|
||||
}
|
||||
2592
apps/api/src/lib/services/handlers.ts
Normal file
@@ -326,7 +326,7 @@ export const fider = [{
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
}, {
|
||||
name: 'postgreslUser',
|
||||
name: 'postgresqlUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
@@ -344,7 +344,7 @@ export const fider = [{
|
||||
{
|
||||
name: 'emailNoreply',
|
||||
isEditable: true,
|
||||
isLowerCase: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
@@ -352,7 +352,7 @@ export const fider = [{
|
||||
{
|
||||
name: 'emailSmtpHost',
|
||||
isEditable: true,
|
||||
isLowerCase: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
@@ -376,7 +376,7 @@ export const fider = [{
|
||||
{
|
||||
name: 'emailSmtpUser',
|
||||
isEditable: true,
|
||||
isLowerCase: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
@@ -476,4 +476,392 @@ export const moodle = [{
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
}]
|
||||
|
||||
export const appwrite = [{
|
||||
name: 'opensslKeyV1',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'executorSecret',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'redisPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'mariadbHost',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'mariadbPort',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'mariadbUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'mariadbPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'mariadbRootUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'mariadbRootUserPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'mariadbDatabase',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
}]
|
||||
|
||||
export const glitchTip = [{
|
||||
name: 'postgresqlUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'postgresqlPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'postgresqlDatabase',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'postgresqlPublicPort',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: true,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'secretKeyBase',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'emailSmtpHost',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'emailSmtpPassword',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'emailSmtpUseSsl',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: true,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'emailSmtpUseSsl',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: true,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'emailSmtpPort',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: true,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'emailSmtpUser',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'defaultEmail',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'defaultUsername',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'defaultPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'defaultEmailFrom',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'emailUrl',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'emailBackend',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'mailgunApiKey',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'sendgridApiKey',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'enableOpenUserRegistration',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: true,
|
||||
isEncrypted: false
|
||||
}]
|
||||
|
||||
export const searxng = [{
|
||||
name: 'secretKey',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'redisPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
}]
|
||||
|
||||
export const weblate = [{
|
||||
name: 'adminPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'postgresqlHost',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'postgresqlPort',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'postgresqlUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'postgresqlPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'postgresqlDatabase',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
}]
|
||||
export const taiga = [{
|
||||
name: 'secretKey',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'djangoAdminUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'djangoAdminPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'rabbitMQUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'rabbitMQPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'postgresqlHost',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'postgresqlPort',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'postgresqlUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'postgresqlPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'postgresqlDatabase',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
}]
|
||||
215
apps/api/src/lib/services/supportedVersions.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
export const supportedServiceTypesAndVersions = [
|
||||
{
|
||||
name: 'plausibleanalytics',
|
||||
fancyName: 'Plausible Analytics',
|
||||
baseImage: 'plausible/analytics',
|
||||
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
|
||||
versions: ['latest', 'stable'],
|
||||
recommendedVersion: 'stable',
|
||||
ports: {
|
||||
main: 8000
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'nocodb',
|
||||
fancyName: 'NocoDB',
|
||||
baseImage: 'nocodb/nocodb',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8080
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'minio',
|
||||
fancyName: 'MinIO',
|
||||
baseImage: 'minio/minio',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 9001
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'vscodeserver',
|
||||
fancyName: 'VSCode Server',
|
||||
baseImage: 'codercom/code-server',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8080
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'wordpress',
|
||||
fancyName: 'Wordpress',
|
||||
baseImage: 'wordpress',
|
||||
images: ['bitnami/mysql:5.7'],
|
||||
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'vaultwarden',
|
||||
fancyName: 'Vaultwarden',
|
||||
baseImage: 'vaultwarden/server',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'languagetool',
|
||||
fancyName: 'LanguageTool',
|
||||
baseImage: 'silviof/docker-languagetool',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n',
|
||||
fancyName: 'n8n',
|
||||
baseImage: 'n8nio/n8n',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 5678
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'uptimekuma',
|
||||
fancyName: 'Uptime Kuma',
|
||||
baseImage: 'louislam/uptime-kuma',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 3001
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ghost',
|
||||
fancyName: 'Ghost',
|
||||
baseImage: 'bitnami/ghost',
|
||||
images: ['bitnami/mariadb'],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 2368
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'meilisearch',
|
||||
fancyName: 'Meilisearch',
|
||||
baseImage: 'getmeili/meilisearch',
|
||||
images: [],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 7700
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'umami',
|
||||
fancyName: 'Umami',
|
||||
baseImage: 'ghcr.io/mikecao/umami',
|
||||
images: ['postgres:12-alpine'],
|
||||
versions: ['postgresql-latest'],
|
||||
recommendedVersion: 'postgresql-latest',
|
||||
ports: {
|
||||
main: 3000
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'hasura',
|
||||
fancyName: 'Hasura',
|
||||
baseImage: 'hasura/graphql-engine',
|
||||
images: ['postgres:12-alpine'],
|
||||
versions: ['latest', 'v2.10.0', 'v2.5.1'],
|
||||
recommendedVersion: 'v2.10.0',
|
||||
ports: {
|
||||
main: 8080
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'fider',
|
||||
fancyName: 'Fider',
|
||||
baseImage: 'getfider/fider',
|
||||
images: ['postgres:12-alpine'],
|
||||
versions: ['stable'],
|
||||
recommendedVersion: 'stable',
|
||||
ports: {
|
||||
main: 3000
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'appwrite',
|
||||
fancyName: 'Appwrite',
|
||||
baseImage: 'appwrite/appwrite',
|
||||
images: ['mariadb:10.7', 'redis:6.2-alpine', 'appwrite/telegraf:1.4.0'],
|
||||
versions: ['latest', '0.15.3'],
|
||||
recommendedVersion: '0.15.3',
|
||||
ports: {
|
||||
main: 80
|
||||
}
|
||||
},
|
||||
// {
|
||||
// name: 'moodle',
|
||||
// fancyName: 'Moodle',
|
||||
// baseImage: 'bitnami/moodle',
|
||||
// images: [],
|
||||
// versions: ['latest', 'v4.0.2'],
|
||||
// recommendedVersion: 'latest',
|
||||
// ports: {
|
||||
// main: 8080
|
||||
// }
|
||||
// }
|
||||
{
|
||||
name: 'glitchTip',
|
||||
fancyName: 'GlitchTip',
|
||||
baseImage: 'glitchtip/glitchtip',
|
||||
images: ['postgres:14-alpine', 'redis:7-alpine'],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8000
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'searxng',
|
||||
fancyName: 'SearXNG',
|
||||
baseImage: 'searxng/searxng',
|
||||
images: [],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8080
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'weblate',
|
||||
fancyName: 'Weblate',
|
||||
baseImage: 'weblate/weblate',
|
||||
images: ['postgres:14-alpine', 'redis:6-alpine'],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8080
|
||||
}
|
||||
},
|
||||
// {
|
||||
// name: 'taiga',
|
||||
// fancyName: 'Taiga',
|
||||
// baseImage: 'taigaio/taiga-front',
|
||||
// images: ['postgres:12.3', 'rabbitmq:3.8-management-alpine', 'taigaio/taiga-back', 'taigaio/taiga-events', 'taigaio/taiga-protected'],
|
||||
// versions: ['latest'],
|
||||
// recommendedVersion: 'latest',
|
||||
// ports: {
|
||||
// main: 80
|
||||
// }
|
||||
// },
|
||||
];
|
||||
@@ -21,7 +21,6 @@ export default fp<FastifyJWTOptions>(async (fastify, opts) => {
|
||||
try {
|
||||
await request.jwtVerify()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
reply.send(err)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,22 +3,29 @@ import crypto from 'node:crypto'
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import axios from 'axios';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
import { day } from '../../../../lib/dayjs';
|
||||
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
||||
import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
||||
import { makeLabelForStandaloneApplication, setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
||||
import { checkDomainsIsValidInDNS, checkDoubleBranch, checkExposedPort, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
||||
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
|
||||
import { scheduler } from '../../../../lib/scheduler';
|
||||
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication } from './types';
|
||||
import { OnlyId } from '../../../../types';
|
||||
|
||||
function filterObject(obj, callback) {
|
||||
return Object.fromEntries(Object.entries(obj).
|
||||
filter(([key, val]) => callback(val, key)));
|
||||
}
|
||||
|
||||
export async function listApplications(request: FastifyRequest) {
|
||||
try {
|
||||
const { teamId } = request.user
|
||||
const applications = await prisma.application.findMany({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { teams: true, destinationDocker: true }
|
||||
include: { teams: true, destinationDocker: true, settings: true }
|
||||
});
|
||||
const settings = await prisma.setting.findFirst()
|
||||
return {
|
||||
@@ -34,7 +41,7 @@ export async function getImages(request: FastifyRequest<GetImages>) {
|
||||
const { buildPack, deploymentType } = request.body
|
||||
let publishDirectory = undefined;
|
||||
let port = undefined
|
||||
const { baseImage, baseBuildImage, baseBuildImages, baseImages, } = setDefaultBaseImage(
|
||||
const { baseImage, baseBuildImage, baseBuildImages, baseImages } = setDefaultBaseImage(
|
||||
buildPack, deploymentType
|
||||
);
|
||||
if (buildPack === 'nextjs') {
|
||||
@@ -56,8 +63,7 @@ export async function getImages(request: FastifyRequest<GetImages>) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return { baseBuildImage, baseBuildImages, publishDirectory, port }
|
||||
return { baseImage, baseImages, baseBuildImage, baseBuildImages, publishDirectory, port }
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
@@ -75,7 +81,6 @@ export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
|
||||
isExited = await isContainerExited(application.destinationDocker.id, id);
|
||||
}
|
||||
return {
|
||||
isQueueActive: scheduler.workers.has('deployApplication'),
|
||||
isRunning,
|
||||
isExited,
|
||||
};
|
||||
@@ -90,10 +95,11 @@ export async function getApplication(request: FastifyRequest<OnlyId>) {
|
||||
const { teamId } = request.user
|
||||
const appId = process.env['COOLIFY_APP_ID'];
|
||||
const application: any = await getApplicationFromDB(id, teamId);
|
||||
|
||||
const settings = await listSettings();
|
||||
return {
|
||||
application,
|
||||
appId
|
||||
appId,
|
||||
settings
|
||||
};
|
||||
|
||||
} catch ({ status, message }) {
|
||||
@@ -150,7 +156,8 @@ export async function getApplicationFromDB(id: string, teamId: string) {
|
||||
settings: true,
|
||||
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||
secrets: true,
|
||||
persistentStorage: true
|
||||
persistentStorage: true,
|
||||
connectedDatabase: true
|
||||
}
|
||||
});
|
||||
if (!application) {
|
||||
@@ -177,32 +184,39 @@ export async function getApplicationFromDB(id: string, teamId: string) {
|
||||
}
|
||||
export async function getApplicationFromDBWebhook(projectId: number, branch: string) {
|
||||
try {
|
||||
let application = await prisma.application.findFirst({
|
||||
let applications = await prisma.application.findMany({
|
||||
where: { projectId, branch, settings: { autodeploy: true } },
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
settings: true,
|
||||
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||
secrets: true,
|
||||
persistentStorage: true
|
||||
persistentStorage: true,
|
||||
connectedDatabase: true
|
||||
}
|
||||
});
|
||||
if (!application) {
|
||||
if (applications.length === 0) {
|
||||
throw { status: 500, message: 'Application not configured.' }
|
||||
}
|
||||
application = decryptApplication(application);
|
||||
const { baseImage, baseBuildImage, baseBuildImages, baseImages } = setDefaultBaseImage(
|
||||
application.buildPack
|
||||
);
|
||||
applications = applications.map((application: any) => {
|
||||
application = decryptApplication(application);
|
||||
const { baseImage, baseBuildImage, baseBuildImages, baseImages } = setDefaultBaseImage(
|
||||
application.buildPack
|
||||
);
|
||||
|
||||
// Set default build images
|
||||
if (!application.baseImage) {
|
||||
application.baseImage = baseImage;
|
||||
}
|
||||
if (!application.baseBuildImage) {
|
||||
application.baseBuildImage = baseBuildImage;
|
||||
}
|
||||
return { ...application, baseBuildImages, baseImages };
|
||||
// Set default build images
|
||||
if (!application.baseImage) {
|
||||
application.baseImage = baseImage;
|
||||
}
|
||||
if (!application.baseBuildImage) {
|
||||
application.baseBuildImage = baseBuildImage;
|
||||
}
|
||||
application.baseBuildImages = baseBuildImages;
|
||||
application.baseImages = baseImages;
|
||||
return application
|
||||
})
|
||||
|
||||
return applications;
|
||||
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -230,13 +244,16 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
||||
denoOptions,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType
|
||||
deploymentType,
|
||||
baseDatabaseBranch
|
||||
} = request.body
|
||||
|
||||
if (port) port = Number(port);
|
||||
if (exposePort) {
|
||||
exposePort = Number(exposePort);
|
||||
}
|
||||
|
||||
const { destinationDocker: { id: dockerId, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
|
||||
if (denoOptions) denoOptions = denoOptions.trim();
|
||||
const defaultConfiguration = await setDefaultConfiguration({
|
||||
buildPack,
|
||||
@@ -249,22 +266,43 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
||||
dockerFileLocation,
|
||||
denoMainFile
|
||||
});
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name,
|
||||
fqdn,
|
||||
exposePort,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
denoOptions,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType,
|
||||
...defaultConfiguration
|
||||
}
|
||||
});
|
||||
if (baseDatabaseBranch) {
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name,
|
||||
fqdn,
|
||||
exposePort,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
denoOptions,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType,
|
||||
...defaultConfiguration,
|
||||
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name,
|
||||
fqdn,
|
||||
exposePort,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
denoOptions,
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType,
|
||||
...defaultConfiguration
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -275,15 +313,15 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
||||
export async function saveApplicationSettings(request: FastifyRequest<SaveApplicationSettings>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { debug, previews, dualCerts, autodeploy, branch, projectId } = request.body
|
||||
const isDouble = await checkDoubleBranch(branch, projectId);
|
||||
if (isDouble && autodeploy) {
|
||||
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
|
||||
throw { status: 500, message: 'Cannot activate automatic deployments until only one application is defined for this repository / branch.' }
|
||||
}
|
||||
const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot, isDBBranching } = request.body
|
||||
// const isDouble = await checkDoubleBranch(branch, projectId);
|
||||
// if (isDouble && autodeploy) {
|
||||
// await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
|
||||
// throw { status: 500, message: 'Cannot activate automatic deployments until only one application is defined for this repository / branch.' }
|
||||
// }
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
|
||||
data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot, isDBBranching } } },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
return reply.code(201).send();
|
||||
@@ -311,6 +349,113 @@ export async function stopPreviewApplication(request: FastifyRequest<StopPreview
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
|
||||
export async function restartApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { teamId } = request.user
|
||||
let application: any = await getApplicationFromDB(id, teamId);
|
||||
if (application?.destinationDockerId) {
|
||||
const buildId = cuid();
|
||||
const { id: dockerId, network } = application.destinationDocker;
|
||||
const { secrets, pullmergeRequestId, port, repository, persistentStorage, id: applicationId, buildPack, exposePort } = application;
|
||||
|
||||
const envs = [
|
||||
`PORT=${port}`
|
||||
];
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (pullmergeRequestId) {
|
||||
if (secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
const { workdir } = await createDirectories({ repository, buildId });
|
||||
const labels = []
|
||||
let image = null
|
||||
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` })
|
||||
const containersArray = container.trim().split('\n');
|
||||
for (const container of containersArray) {
|
||||
const containerObj = formatLabelsOnDocker(container);
|
||||
image = containerObj[0].Image
|
||||
Object.keys(containerObj[0].Labels).forEach(function (key) {
|
||||
if (key.startsWith('coolify')) {
|
||||
labels.push(`${key}=${containerObj[0].Labels[key]}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
let imageFound = false;
|
||||
try {
|
||||
await executeDockerCmd({
|
||||
dockerId,
|
||||
command: `docker image inspect ${image}`
|
||||
})
|
||||
imageFound = true;
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
if (!imageFound) {
|
||||
throw { status: 500, message: 'Image not found, cannot restart application.' }
|
||||
}
|
||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||
|
||||
let envFound = false;
|
||||
try {
|
||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
const volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
||||
}${storage.path}`;
|
||||
}) || [];
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
}
|
||||
};
|
||||
});
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[applicationId]: {
|
||||
image,
|
||||
container_name: applicationId,
|
||||
volumes,
|
||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||
labels,
|
||||
depends_on: [],
|
||||
expose: [port],
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
...defaultComposeConfiguration(network),
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: Object.assign({}, ...composeVolumes)
|
||||
};
|
||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
||||
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
||||
await executeDockerCmd({ dockerId, command: `docker compose --project-directory ${workdir} up -d` })
|
||||
return reply.code(201).send();
|
||||
}
|
||||
throw { status: 500, message: 'Application cannot be restarted.' }
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function stopApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
@@ -331,12 +476,14 @@ export async function stopApplication(request: FastifyRequest<OnlyId>, reply: Fa
|
||||
export async function deleteApplication(request: FastifyRequest<DeleteApplication>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { force } = request.body
|
||||
|
||||
const { teamId } = request.user
|
||||
const application = await prisma.application.findUnique({
|
||||
where: { id },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
if (application?.destinationDockerId && application.destinationDocker?.network) {
|
||||
if (!force && application?.destinationDockerId && application.destinationDocker?.network) {
|
||||
const { stdout: containers } = await executeDockerCmd({
|
||||
dockerId: application.destinationDocker.id,
|
||||
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
||||
@@ -355,6 +502,7 @@ export async function deleteApplication(request: FastifyRequest<DeleteApplicatio
|
||||
await prisma.build.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.secret.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.applicationConnectedDatabase.deleteMany({ where: { applicationId: id } });
|
||||
if (teamId === '0') {
|
||||
await prisma.application.deleteMany({ where: { id } });
|
||||
} else {
|
||||
@@ -378,31 +526,22 @@ export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
||||
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
|
||||
let { exposePort, fqdn, forceSave, dualCerts } = request.body
|
||||
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (!fqdn) {
|
||||
return {}
|
||||
} else {
|
||||
fqdn = fqdn.toLowerCase();
|
||||
}
|
||||
if (exposePort) exposePort = Number(exposePort);
|
||||
|
||||
const { destinationDocker: { id: dockerId, remoteIpAddress, remoteEngine }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||
const { isDNSCheckEnabled } = await prisma.setting.findFirst({});
|
||||
|
||||
const found = await isDomainConfigured({ id, fqdn, dockerId });
|
||||
const found = await isDomainConfigured({ id, fqdn, remoteIpAddress });
|
||||
if (found) {
|
||||
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
||||
}
|
||||
if (exposePort) {
|
||||
if (exposePort < 1024 || exposePort > 65535) {
|
||||
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
|
||||
}
|
||||
|
||||
if (configuredPort !== exposePort) {
|
||||
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
|
||||
if (availablePort.toString() !== exposePort.toString()) {
|
||||
throw { status: 500, message: `Port ${exposePort} is already in use.` }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
|
||||
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||
let hostname = request.hostname.split(':')[0];
|
||||
if (remoteEngine) hostname = remoteIpAddress;
|
||||
@@ -435,7 +574,7 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
||||
try {
|
||||
const { id } = request.params
|
||||
const teamId = request.user?.teamId;
|
||||
const { pullmergeRequestId = null, branch } = request.body
|
||||
const { pullmergeRequestId = null, branch, forceRebuild } = request.body
|
||||
const buildId = cuid();
|
||||
const application = await getApplicationFromDB(id, teamId);
|
||||
if (application) {
|
||||
@@ -459,7 +598,10 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
||||
data: {
|
||||
id: buildId,
|
||||
applicationId: id,
|
||||
sourceBranch: branch,
|
||||
branch: application.branch,
|
||||
pullmergeRequestId: pullmergeRequestId?.toString(),
|
||||
forceRebuild,
|
||||
destinationDockerId: application.destinationDocker?.id,
|
||||
gitSourceId: application.gitSource?.id,
|
||||
githubAppId: application.gitSource?.githubApp?.id,
|
||||
@@ -468,22 +610,6 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
||||
type: 'manual'
|
||||
}
|
||||
});
|
||||
if (pullmergeRequestId) {
|
||||
scheduler.workers.get('deployApplication').postMessage({
|
||||
build_id: buildId,
|
||||
type: 'manual',
|
||||
...application,
|
||||
sourceBranch: branch,
|
||||
pullmergeRequestId
|
||||
});
|
||||
} else {
|
||||
scheduler.workers.get('deployApplication').postMessage({
|
||||
build_id: buildId,
|
||||
type: 'manual',
|
||||
...application
|
||||
});
|
||||
|
||||
}
|
||||
return {
|
||||
buildId
|
||||
};
|
||||
@@ -498,11 +624,20 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
||||
export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { gitSourceId } = request.body
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: { gitSource: { connect: { id: gitSourceId } } }
|
||||
});
|
||||
const { gitSourceId, forPublic, type } = request.body
|
||||
if (forPublic) {
|
||||
const publicGit = await prisma.gitSource.findFirst({ where: { type, forPublic } });
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: { gitSource: { connect: { id: publicGit.id } } }
|
||||
});
|
||||
} else {
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: { gitSource: { connect: { id: gitSourceId } } }
|
||||
});
|
||||
}
|
||||
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -556,7 +691,7 @@ export async function checkRepository(request: FastifyRequest<CheckRepository>)
|
||||
export async function saveRepository(request, reply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
let { repository, branch, projectId, autodeploy, webhookToken } = request.body
|
||||
let { repository, branch, projectId, autodeploy, webhookToken, isPublicRepository = false } = request.body
|
||||
|
||||
repository = repository.toLowerCase();
|
||||
branch = branch.toLowerCase();
|
||||
@@ -564,18 +699,20 @@ export async function saveRepository(request, reply) {
|
||||
if (webhookToken) {
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy } } }
|
||||
data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy, isPublicRepository } } }
|
||||
});
|
||||
} else {
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
data: { repository, branch, projectId, settings: { update: { autodeploy } } }
|
||||
data: { repository, branch, projectId, settings: { update: { autodeploy, isPublicRepository } } }
|
||||
});
|
||||
}
|
||||
const isDouble = await checkDoubleBranch(branch, projectId);
|
||||
if (isDouble) {
|
||||
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
|
||||
}
|
||||
// if (!isPublicRepository) {
|
||||
// const isDouble = await checkDoubleBranch(branch, projectId);
|
||||
// if (isDouble) {
|
||||
// await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false, isPublicRepository } })
|
||||
// }
|
||||
// }
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -606,7 +743,8 @@ export async function getBuildPack(request) {
|
||||
projectId: application.projectId,
|
||||
repository: application.repository,
|
||||
branch: application.branch,
|
||||
apiUrl: application.gitSource.apiUrl
|
||||
apiUrl: application.gitSource.apiUrl,
|
||||
isPublicRepository: application.settings.isPublicRepository
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -623,6 +761,16 @@ export async function saveBuildPack(request, reply) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function saveConnectedDatabase(request, reply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { databaseId, type } = request.body
|
||||
await prisma.application.update({ where: { id }, data: { connectedDatabase: { upsert: { create: { database: { connect: { id: databaseId } }, hostedDatabaseType: type }, update: { database: { connect: { id: databaseId } }, hostedDatabaseType: type } } } } })
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSecrets(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
@@ -656,13 +804,13 @@ export async function saveSecret(request: FastifyRequest<SaveSecret>, reply: Fas
|
||||
if (found) {
|
||||
throw { status: 500, message: `Secret ${name} already exists.` }
|
||||
} else {
|
||||
value = encrypt(value);
|
||||
value = encrypt(value.trim());
|
||||
await prisma.secret.create({
|
||||
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
||||
});
|
||||
}
|
||||
} else {
|
||||
value = encrypt(value);
|
||||
value = encrypt(value.trim());
|
||||
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
||||
|
||||
if (found) {
|
||||
@@ -779,7 +927,6 @@ export async function getPreviews(request: FastifyRequest<OnlyId>) {
|
||||
})
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
console.log({ status, message })
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
@@ -872,8 +1019,13 @@ export async function getBuildIdLogs(request: FastifyRequest<GetBuildIdLogs>) {
|
||||
orderBy: { time: 'asc' }
|
||||
});
|
||||
const data = await prisma.build.findFirst({ where: { id: buildId } });
|
||||
const createdAt = day(data.createdAt).utc();
|
||||
return {
|
||||
logs,
|
||||
logs: logs.map(log => {
|
||||
log.time = Number(log.time)
|
||||
return log
|
||||
}),
|
||||
took: day().diff(createdAt) / 1000,
|
||||
status: data?.status || 'queued'
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
@@ -948,4 +1100,59 @@ export async function cancelDeployment(request: FastifyRequest<CancelDeployment>
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function createdBranchDatabase(database: any, baseDatabaseBranch: string, pullmergeRequestId: string) {
|
||||
try {
|
||||
if (!baseDatabaseBranch) return
|
||||
const { id, type, destinationDockerId, rootUser, rootUserPassword, dbUser } = database;
|
||||
if (destinationDockerId) {
|
||||
if (type === 'postgresql') {
|
||||
const decryptedRootUserPassword = decrypt(rootUserPassword);
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker exec ${id} pg_dump -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/${baseDatabaseBranch}" --encoding=UTF8 --schema-only -f /tmp/${baseDatabaseBranch}.dump`
|
||||
})
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "CREATE DATABASE branch_${pullmergeRequestId}"`
|
||||
})
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker exec ${id} psql -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/branch_${pullmergeRequestId}" -f /tmp/${baseDatabaseBranch}.dump`
|
||||
})
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "ALTER DATABASE branch_${pullmergeRequestId} OWNER TO ${dbUser}"`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function removeBranchDatabase(database: any, pullmergeRequestId: string) {
|
||||
try {
|
||||
const { id, type, destinationDockerId, rootUser, rootUserPassword } = database;
|
||||
if (destinationDockerId) {
|
||||
if (type === 'postgresql') {
|
||||
const decryptedRootUserPassword = decrypt(rootUserPassword);
|
||||
// Terminate all connections to the database
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'branch_${pullmergeRequestId}' AND pid <> pg_backend_pid();"`
|
||||
})
|
||||
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "DROP DATABASE branch_${pullmergeRequestId}"`
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { OnlyId } from '../../../../types';
|
||||
import { cancelDeployment, checkDNS, checkDomain, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication } from './handlers';
|
||||
import { cancelDeployment, checkDNS, checkDomain, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, restartApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication } from './handlers';
|
||||
|
||||
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
|
||||
|
||||
@@ -19,6 +19,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
|
||||
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
|
||||
|
||||
fastify.post<OnlyId>('/:id/restart', async (request, reply) => await restartApplication(request, reply));
|
||||
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
|
||||
fastify.post<StopPreviewApplication>('/:id/stop/preview', async (request, reply) => await stopPreviewApplication(request, reply));
|
||||
|
||||
@@ -54,6 +55,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get('/:id/configuration/buildpack', async (request) => await getBuildPack(request));
|
||||
fastify.post('/:id/configuration/buildpack', async (request, reply) => await saveBuildPack(request, reply));
|
||||
|
||||
fastify.post('/:id/configuration/database', async (request, reply) => await saveConnectedDatabase(request, reply));
|
||||
|
||||
fastify.get<OnlyId>('/:id/configuration/sshkey', async (request) => await getGitLabSSHKey(request));
|
||||
fastify.post<OnlyId>('/:id/configuration/sshkey', async (request, reply) => await saveGitLabSSHKey(request, reply));
|
||||
|
||||
|
||||
@@ -20,15 +20,17 @@ export interface SaveApplication extends OnlyId {
|
||||
denoOptions: string,
|
||||
baseImage: string,
|
||||
baseBuildImage: string,
|
||||
deploymentType: string
|
||||
deploymentType: string,
|
||||
baseDatabaseBranch: string
|
||||
}
|
||||
}
|
||||
export interface SaveApplicationSettings extends OnlyId {
|
||||
Querystring: { domain: string; };
|
||||
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; };
|
||||
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean };
|
||||
}
|
||||
export interface DeleteApplication extends OnlyId {
|
||||
Querystring: { domain: string; };
|
||||
Body: { force: boolean }
|
||||
}
|
||||
export interface CheckDomain extends OnlyId {
|
||||
Querystring: { domain: string; };
|
||||
@@ -44,13 +46,13 @@ export interface CheckDNS extends OnlyId {
|
||||
}
|
||||
export interface DeployApplication {
|
||||
Querystring: { domain: string }
|
||||
Body: { pullmergeRequestId: string | null, branch: string }
|
||||
Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean }
|
||||
}
|
||||
export interface GetImages {
|
||||
Body: { buildPack: string, deploymentType: string }
|
||||
}
|
||||
export interface SaveApplicationSource extends OnlyId {
|
||||
Body: { gitSourceId: string }
|
||||
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string }
|
||||
}
|
||||
export interface CheckRepository extends OnlyId {
|
||||
Querystring: { repository: string, branch: string }
|
||||
@@ -115,7 +117,8 @@ export interface CancelDeployment {
|
||||
export interface DeployApplication extends OnlyId {
|
||||
Body: {
|
||||
pullmergeRequestId: string | null,
|
||||
branch: string
|
||||
branch: string,
|
||||
forceRebuild?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,10 @@ import { FastifyReply } from 'fastify';
|
||||
import yaml from 'js-yaml';
|
||||
import fs from 'fs/promises';
|
||||
import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
|
||||
import { checkContainer } from '../../../../lib/docker';
|
||||
import { day } from '../../../../lib/dayjs';
|
||||
|
||||
import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
|
||||
import { SaveDatabaseType } from './types';
|
||||
import { DeleteDatabase, SaveDatabaseType } from './types';
|
||||
|
||||
export async function listDatabases(request: FastifyRequest) {
|
||||
try {
|
||||
@@ -30,9 +29,9 @@ export async function newDatabase(request: FastifyRequest, reply: FastifyReply)
|
||||
|
||||
const name = uniqueName();
|
||||
const dbUser = cuid();
|
||||
const dbUserPassword = encrypt(generatePassword());
|
||||
const dbUserPassword = encrypt(generatePassword({}));
|
||||
const rootUser = cuid();
|
||||
const rootUserPassword = encrypt(generatePassword());
|
||||
const rootUserPassword = encrypt(generatePassword({}));
|
||||
const defaultDatabase = cuid();
|
||||
|
||||
const { id } = await prisma.database.create({
|
||||
@@ -93,14 +92,15 @@ export async function getDatabase(request: FastifyRequest<OnlyId>) {
|
||||
if (!database) {
|
||||
throw { status: 404, message: 'Database not found.' }
|
||||
}
|
||||
const { arch } = await listSettings();
|
||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||
const configuration = generateDatabaseConfiguration(database);
|
||||
const configuration = generateDatabaseConfiguration(database, arch);
|
||||
const settings = await listSettings();
|
||||
return {
|
||||
privatePort: configuration?.privatePort,
|
||||
database,
|
||||
versions: await getDatabaseVersions(database.type),
|
||||
versions: await getDatabaseVersions(database.type, arch),
|
||||
settings
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
@@ -137,8 +137,10 @@ export async function getVersions(request: FastifyRequest<OnlyId>) {
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
const { arch } = await listSettings();
|
||||
const versions = getDatabaseVersions(type, arch);
|
||||
return {
|
||||
versions: supportedDatabaseTypesAndVersions.find((name) => name.name === type).versions
|
||||
versions
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -165,6 +167,7 @@ export async function saveDatabaseDestination(request: FastifyRequest<SaveDataba
|
||||
const { id } = request.params;
|
||||
const { destinationId } = request.body;
|
||||
|
||||
const { arch } = await listSettings();
|
||||
await prisma.database.update({
|
||||
where: { id },
|
||||
data: { destinationDocker: { connect: { id: destinationId } } }
|
||||
@@ -179,7 +182,7 @@ export async function saveDatabaseDestination(request: FastifyRequest<SaveDataba
|
||||
|
||||
if (destinationDockerId) {
|
||||
if (type && version) {
|
||||
const baseImage = getDatabaseImage(type);
|
||||
const baseImage = getDatabaseImage(type, arch);
|
||||
executeDockerCmd({ dockerId, command: `docker pull ${baseImage}:${version}` })
|
||||
}
|
||||
}
|
||||
@@ -219,6 +222,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
const { arch } = await listSettings();
|
||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||
const {
|
||||
@@ -228,8 +232,8 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
||||
publicPort,
|
||||
settings: { isPublic }
|
||||
} = database;
|
||||
const { privatePort, environmentVariables, image, volume, ulimits } =
|
||||
generateDatabaseConfiguration(database);
|
||||
const { privatePort, command, environmentVariables, image, volume, ulimits } =
|
||||
generateDatabaseConfiguration(database, arch);
|
||||
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const volumeName = volume.split(':')[0];
|
||||
@@ -243,6 +247,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
||||
[id]: {
|
||||
container_name: id,
|
||||
image,
|
||||
command,
|
||||
networks: [network],
|
||||
environment: environmentVariables,
|
||||
volumes: [volume],
|
||||
@@ -270,13 +275,12 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
try {
|
||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker volume create ${volumeName}` })
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
} catch (error) { }
|
||||
try {
|
||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` })
|
||||
if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||
@@ -354,19 +358,22 @@ export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>)
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function deleteDatabase(request: FastifyRequest<OnlyId>) {
|
||||
export async function deleteDatabase(request: FastifyRequest<DeleteDatabase>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.params;
|
||||
const { force } = request.body;
|
||||
const database = await prisma.database.findFirst({
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||
if (database.destinationDockerId) {
|
||||
const everStarted = await stopDatabaseContainer(database);
|
||||
if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
||||
if (!force) {
|
||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||
if (database.destinationDockerId) {
|
||||
const everStarted = await stopDatabaseContainer(database);
|
||||
if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
||||
}
|
||||
}
|
||||
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
||||
await prisma.database.delete({ where: { id } });
|
||||
@@ -427,9 +434,13 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
|
||||
const { id } = request.params;
|
||||
const { isPublic, appendOnly = true } = request.body;
|
||||
|
||||
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||
const publicPort = await getFreePublicPort(id, dockerId);
|
||||
let publicPort = null
|
||||
|
||||
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||
|
||||
if (isPublic) {
|
||||
publicPort = await getFreePublicPort(id, dockerId);
|
||||
}
|
||||
await prisma.database.update({
|
||||
where: { id },
|
||||
data: {
|
||||
@@ -440,11 +451,12 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
const { arch } = await listSettings();
|
||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||
|
||||
const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database;
|
||||
const { privatePort } = generateDatabaseConfiguration(database);
|
||||
const { privatePort } = generateDatabaseConfiguration(database, arch);
|
||||
|
||||
if (destinationDockerId) {
|
||||
if (isPublic) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { deleteDatabase, getDatabase, getDatabaseLogs, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
||||
|
||||
import type { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
|
||||
import type { DeleteDatabase, GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
|
||||
import type { SaveDatabaseType } from './types';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
@@ -13,7 +13,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
|
||||
fastify.get<OnlyId>('/:id', async (request) => await getDatabase(request));
|
||||
fastify.post<SaveDatabase>('/:id', async (request, reply) => await saveDatabase(request, reply));
|
||||
fastify.delete<OnlyId>('/:id', async (request) => await deleteDatabase(request));
|
||||
fastify.delete<DeleteDatabase>('/:id', async (request) => await deleteDatabase(request));
|
||||
|
||||
fastify.get<OnlyId>('/:id/status', async (request) => await getDatabaseStatus(request));
|
||||
|
||||
|
||||
@@ -2,4 +2,7 @@ import type { OnlyId } from "../../../../types";
|
||||
|
||||
export interface SaveDatabaseType extends OnlyId {
|
||||
Body: { type: string }
|
||||
}
|
||||
export interface DeleteDatabase extends OnlyId {
|
||||
Body: { force: string }
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import sshConfig from 'ssh-config'
|
||||
import fs from 'fs/promises'
|
||||
import os from 'os';
|
||||
|
||||
import { asyncExecShell, decrypt, errorHandler, executeDockerCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
|
||||
import { asyncExecShell, createRemoteEngineConfiguration, decrypt, errorHandler, executeDockerCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
|
||||
import { checkContainer } from '../../../../lib/docker';
|
||||
|
||||
import type { OnlyId } from '../../../../types';
|
||||
@@ -53,13 +53,13 @@ export async function getDestination(request: FastifyRequest<OnlyId>) {
|
||||
const teamId = request.user?.teamId;
|
||||
const destination = await prisma.destinationDocker.findFirst({
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { sshKey: true }
|
||||
include: { sshKey: true, application: true, service: true, database: true }
|
||||
});
|
||||
if (!destination && id !== 'new') {
|
||||
throw { status: 404, message: `Destination not found.` };
|
||||
}
|
||||
const settings = await listSettings();
|
||||
let payload = {
|
||||
const payload = {
|
||||
destination,
|
||||
settings
|
||||
};
|
||||
@@ -78,7 +78,6 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
|
||||
|
||||
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
|
||||
if (id === 'new') {
|
||||
console.log(engine)
|
||||
if (engine) {
|
||||
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`);
|
||||
if (stdout === '') {
|
||||
@@ -114,7 +113,6 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
|
||||
}
|
||||
|
||||
} catch ({ status, message }) {
|
||||
console.log({ status, message })
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
@@ -162,7 +160,6 @@ export async function startProxy(request: FastifyRequest<Proxy>) {
|
||||
await startTraefikProxy(id);
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
console.log({ status, message })
|
||||
await stopTraefikProxy(id);
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
@@ -205,41 +202,21 @@ export async function assignSSHKey(request: FastifyRequest) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function verifyRemoteDockerEngine(request: FastifyRequest, reply: FastifyReply) {
|
||||
export async function verifyRemoteDockerEngine(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const homedir = os.homedir();
|
||||
|
||||
const { sshKey: { privateKey }, remoteIpAddress, remotePort, remoteUser, network } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } })
|
||||
|
||||
await fs.writeFile(`/tmp/id_rsa_verification_${id}`, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 })
|
||||
|
||||
await createRemoteEngineConfiguration(id);
|
||||
const { remoteIpAddress, remoteUser, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
|
||||
const host = `ssh://${remoteUser}@${remoteIpAddress}`
|
||||
|
||||
const config = sshConfig.parse('')
|
||||
const found = config.find({ Host: remoteIpAddress })
|
||||
if (!found) {
|
||||
config.append({
|
||||
Host: remoteIpAddress,
|
||||
Port: remotePort.toString(),
|
||||
User: remoteUser,
|
||||
IdentityFile: `/tmp/id_rsa_verification_${id}`,
|
||||
StrictHostKeyChecking: 'no'
|
||||
})
|
||||
}
|
||||
try {
|
||||
await fs.stat(`${homedir}/.ssh/`)
|
||||
} catch (error) {
|
||||
await fs.mkdir(`${homedir}/.ssh/`)
|
||||
}
|
||||
await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config))
|
||||
|
||||
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`);
|
||||
|
||||
if (!stdout) {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
|
||||
}
|
||||
|
||||
const { stdout: coolifyNetwork } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"`);
|
||||
if (!coolifyNetwork) {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable coolify-infra`);
|
||||
}
|
||||
if (isCoolifyProxyUsed) await startTraefikProxy(id);
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
|
||||
return reply.code(201).send()
|
||||
|
||||
@@ -252,7 +229,7 @@ export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const destination = await prisma.destinationDocker.findUnique({ where: { id } })
|
||||
const isRunning = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy' })
|
||||
const isRunning = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy', remove: true })
|
||||
return {
|
||||
isRunning
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
|
||||
fastify.post('/:id/configuration/sshKey', async (request) => await assignSSHKey(request));
|
||||
|
||||
fastify.post('/:id/verify', async (request, reply) => await verifyRemoteDockerEngine(request, reply));
|
||||
fastify.post<OnlyId>('/:id/verify', async (request, reply) => await verifyRemoteDockerEngine(request, reply));
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import os from 'node:os';
|
||||
import osu from 'node-os-utils';
|
||||
import axios from 'axios';
|
||||
import compare from 'compare-versions';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import cuid from 'cuid';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, prisma, uniqueName, version } from '../../../lib/common';
|
||||
|
||||
import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, listSettings, prisma, uniqueName, version } from '../../../lib/common';
|
||||
import { supportedServiceTypesAndVersions } from '../../../lib/services/supportedVersions';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import type { Login, Update } from '.';
|
||||
import type { GetCurrentUser } from './types';
|
||||
@@ -31,11 +31,14 @@ export async function checkUpdate(request: FastifyRequest) {
|
||||
const { data: versions } = await axios.get(
|
||||
`https://get.coollabs.io/versions.json?appId=${process.env['COOLIFY_APP_ID']}&version=${currentVersion}`
|
||||
);
|
||||
const latestVersion =
|
||||
isStaging
|
||||
? versions['coolify'].next.version
|
||||
: versions['coolify'].main.version;
|
||||
const isUpdateAvailable = compare(latestVersion, currentVersion);
|
||||
const latestVersion = versions['coolify'].main.version
|
||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||
if (isStaging) {
|
||||
return {
|
||||
isUpdateAvailable: true,
|
||||
latestVersion: 'next'
|
||||
}
|
||||
}
|
||||
return {
|
||||
isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1,
|
||||
latestVersion
|
||||
@@ -62,7 +65,6 @@ export async function update(request: FastifyRequest<Update>) {
|
||||
);
|
||||
return {};
|
||||
} else {
|
||||
console.log(latestVersion);
|
||||
await asyncSleep(2000);
|
||||
return {};
|
||||
}
|
||||
@@ -70,6 +72,22 @@ export async function update(request: FastifyRequest<Update>) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function restartCoolify(request: FastifyRequest<any>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
if (teamId === '0') {
|
||||
if (!isDev) {
|
||||
asyncExecShell(`docker restart coolify`);
|
||||
return {};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
throw { status: 500, message: 'You are not authorized to restart Coolify.' };
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function showUsage() {
|
||||
try {
|
||||
return {
|
||||
@@ -93,34 +111,23 @@ export async function showDashboard(request: FastifyRequest) {
|
||||
try {
|
||||
const userId = request.user.userId;
|
||||
const teamId = request.user.teamId;
|
||||
const applicationsCount = await prisma.application.count({
|
||||
const applications = await prisma.application.findMany({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { settings: true }
|
||||
});
|
||||
const databases = await prisma.database.findMany({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { settings: true }
|
||||
});
|
||||
const services = await prisma.service.findMany({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const sourcesCount = await prisma.gitSource.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const destinationsCount = await prisma.destinationDocker.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const teamsCount = await prisma.permission.count({ where: { userId } });
|
||||
const databasesCount = await prisma.database.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const servicesCount = await prisma.service.count({
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||
});
|
||||
const teams = await prisma.permission.findMany({
|
||||
where: { userId },
|
||||
include: { team: { include: { _count: { select: { users: true } } } } }
|
||||
});
|
||||
const settings = await listSettings();
|
||||
return {
|
||||
teams,
|
||||
applicationsCount,
|
||||
sourcesCount,
|
||||
destinationsCount,
|
||||
teamsCount,
|
||||
databasesCount,
|
||||
servicesCount,
|
||||
applications,
|
||||
databases,
|
||||
services,
|
||||
settings,
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -291,6 +298,7 @@ export async function getCurrentUser(request: FastifyRequest<GetCurrentUser>, fa
|
||||
}
|
||||
return {
|
||||
settings: await prisma.setting.findFirst(),
|
||||
supportedServiceTypesAndVersions,
|
||||
token,
|
||||
...request.user
|
||||
}
|
||||
|
||||
@@ -158,8 +158,11 @@ export async function getTeam(request: FastifyRequest<OnlyId>, reply: FastifyRep
|
||||
});
|
||||
const team = await prisma.team.findUnique({ where: { id }, include: { permissions: true } });
|
||||
const invitations = await prisma.teamInvitation.findMany({ where: { teamId: team.id } });
|
||||
const { teams } = await prisma.user.findUnique({ where: { id: userId }, include: { teams: true } })
|
||||
return {
|
||||
currentTeam: teamId,
|
||||
team,
|
||||
teams,
|
||||
permissions,
|
||||
invitations
|
||||
};
|
||||
@@ -275,10 +278,10 @@ export async function inviteToTeam(request: FastifyRequest<InviteToTeam>, reply:
|
||||
if (!userFound) {
|
||||
throw {
|
||||
message: `No user found with '${email}' email address.`
|
||||
};
|
||||
};
|
||||
}
|
||||
const uid = userFound.id;
|
||||
if (uid === userId) {
|
||||
if (uid === userId) {
|
||||
throw {
|
||||
message: `Invitation to yourself? Whaaaaat?`
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { checkUpdate, login, showDashboard, update, showUsage, getCurrentUser, cleanupManually } from './handlers';
|
||||
import { checkUpdate, login, showDashboard, update, showUsage, getCurrentUser, cleanupManually, restartCoolify } from './handlers';
|
||||
import { GetCurrentUser } from './types';
|
||||
|
||||
export interface Update {
|
||||
@@ -47,6 +47,10 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
onRequest: [fastify.authenticate]
|
||||
}, async () => await showUsage());
|
||||
|
||||
fastify.post('/internal/restart', {
|
||||
onRequest: [fastify.authenticate]
|
||||
}, async (request) => await restartCoolify(request));
|
||||
|
||||
fastify.post('/internal/cleanup', {
|
||||
onRequest: [fastify.authenticate]
|
||||
}, async () => await cleanupManually());
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
activateWordpressFtp,
|
||||
checkService,
|
||||
checkServiceDomain,
|
||||
cleanupPlausibleLogs,
|
||||
deleteService,
|
||||
deleteServiceSecret,
|
||||
deleteServiceStorage,
|
||||
@@ -25,12 +26,11 @@ import {
|
||||
saveServiceType,
|
||||
saveServiceVersion,
|
||||
setSettingsService,
|
||||
startService,
|
||||
stopService
|
||||
} from './handlers';
|
||||
|
||||
import type { OnlyId } from '../../../../types';
|
||||
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
|
||||
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetGlitchTipSettings, SetWordpressSettings } from './types';
|
||||
import { startService, stopService } from '../../../../lib/services/handlers';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.addHook('onRequest', async (request) => {
|
||||
@@ -71,9 +71,10 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
|
||||
fastify.post<ServiceStartStop>('/:id/:type/start', async (request) => await startService(request));
|
||||
fastify.post<ServiceStartStop>('/:id/:type/stop', async (request) => await stopService(request));
|
||||
fastify.post<ServiceStartStop & SetWordpressSettings>('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply));
|
||||
fastify.post<ServiceStartStop & SetWordpressSettings & SetGlitchTipSettings>('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply));
|
||||
|
||||
fastify.post<OnlyId>('/:id/plausibleanalytics/activate', async (request, reply) => await activatePlausibleUsers(request, reply));
|
||||
fastify.post<OnlyId>('/:id/plausibleanalytics/cleanup', async (request, reply) => await cleanupPlausibleLogs(request, reply));
|
||||
fastify.post<ActivateWordpressFtp>('/:id/wordpress/ftp', async (request, reply) => await activateWordpressFtp(request, reply));
|
||||
};
|
||||
|
||||
|
||||
@@ -89,6 +89,10 @@ export interface ActivateWordpressFtp extends OnlyId {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export interface SetGlitchTipSettings extends OnlyId {
|
||||
Body: {
|
||||
enableOpenUserRegistration: boolean,
|
||||
emailSmtpUseSsl: boolean,
|
||||
emailSmtpUseTls: boolean
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,17 +28,19 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
||||
try {
|
||||
const {
|
||||
fqdn,
|
||||
isAPIDebuggingEnabled,
|
||||
isRegistrationEnabled,
|
||||
dualCerts,
|
||||
minPort,
|
||||
maxPort,
|
||||
isAutoUpdateEnabled,
|
||||
isDNSCheckEnabled
|
||||
isDNSCheckEnabled,
|
||||
DNSServers
|
||||
} = request.body
|
||||
const { id } = await listSettings();
|
||||
await prisma.setting.update({
|
||||
where: { id },
|
||||
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled }
|
||||
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers, isAPIDebuggingEnabled }
|
||||
});
|
||||
if (fqdn) {
|
||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||
@@ -54,6 +56,10 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
||||
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
|
||||
try {
|
||||
const { fqdn } = request.body
|
||||
const { DNSServers } = await listSettings();
|
||||
if (DNSServers) {
|
||||
dns.setServers([DNSServers]);
|
||||
}
|
||||
let ip;
|
||||
try {
|
||||
ip = await dns.resolve(fqdn);
|
||||
|
||||
@@ -3,12 +3,14 @@ import { OnlyId } from "../../../../types"
|
||||
export interface SaveSettings {
|
||||
Body: {
|
||||
fqdn: string,
|
||||
isAPIDebuggingEnabled: boolean,
|
||||
isRegistrationEnabled: boolean,
|
||||
dualCerts: boolean,
|
||||
minPort: number,
|
||||
maxPort: number,
|
||||
isAutoUpdateEnabled: boolean,
|
||||
isDNSCheckEnabled: boolean
|
||||
isDNSCheckEnabled: boolean,
|
||||
DNSServers: string
|
||||
}
|
||||
}
|
||||
export interface DeleteDomain {
|
||||
|
||||
@@ -3,8 +3,7 @@ import cuid from "cuid";
|
||||
import crypto from "crypto";
|
||||
import { encrypt, errorHandler, getUIUrl, isDev, prisma } from "../../../lib/common";
|
||||
import { checkContainer, removeContainer } from "../../../lib/docker";
|
||||
import { scheduler } from "../../../lib/scheduler";
|
||||
import { getApplicationFromDBWebhook } from "../../api/v1/applications/handlers";
|
||||
import { createdBranchDatabase, getApplicationFromDBWebhook, removeBranchDatabase } from "../../api/v1/applications/handlers";
|
||||
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import type { GitHubEvents, InstallGithub } from "./types";
|
||||
@@ -67,7 +66,6 @@ export async function configureGitHubApp(request, reply) {
|
||||
}
|
||||
export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promise<any> {
|
||||
try {
|
||||
const buildId = cuid();
|
||||
const allowedGithubEvents = ['push', 'pull_request'];
|
||||
const allowedActions = ['opened', 'reopened', 'synchronize', 'closed'];
|
||||
const githubEvent = request.headers['x-github-event']?.toString().toLowerCase();
|
||||
@@ -87,137 +85,139 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
||||
if (!projectId || !branch) {
|
||||
throw { status: 500, message: 'Cannot parse projectId or branch from the webhook?!' }
|
||||
}
|
||||
const applicationFound = await getApplicationFromDBWebhook(projectId, branch);
|
||||
if (applicationFound) {
|
||||
const webhookSecret = applicationFound.gitSource.githubApp.webhookSecret || null;
|
||||
//@ts-ignore
|
||||
const hmac = crypto.createHmac('sha256', webhookSecret);
|
||||
const digest = Buffer.from(
|
||||
'sha256=' + hmac.update(JSON.stringify(body)).digest('hex'),
|
||||
'utf8'
|
||||
);
|
||||
if (!isDev) {
|
||||
const checksum = Buffer.from(githubSignature, 'utf8');
|
||||
const applicationsFound = await getApplicationFromDBWebhook(projectId, branch);
|
||||
if (applicationsFound && applicationsFound.length > 0) {
|
||||
for (const application of applicationsFound) {
|
||||
const buildId = cuid();
|
||||
const webhookSecret = application.gitSource.githubApp.webhookSecret || null;
|
||||
//@ts-ignore
|
||||
if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) {
|
||||
throw { status: 500, message: 'SHA256 checksum failed. Are you doing something fishy?' }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (githubEvent === 'push') {
|
||||
if (!applicationFound.configHash) {
|
||||
const configHash = crypto
|
||||
//@ts-ignore
|
||||
.createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify({
|
||||
buildPack: applicationFound.buildPack,
|
||||
port: applicationFound.port,
|
||||
exposePort: applicationFound.exposePort,
|
||||
installCommand: applicationFound.installCommand,
|
||||
buildCommand: applicationFound.buildCommand,
|
||||
startCommand: applicationFound.startCommand
|
||||
})
|
||||
)
|
||||
.digest('hex');
|
||||
await prisma.application.updateMany({
|
||||
where: { branch, projectId },
|
||||
data: { configHash }
|
||||
});
|
||||
}
|
||||
await prisma.application.update({
|
||||
where: { id: applicationFound.id },
|
||||
data: { updatedAt: new Date() }
|
||||
});
|
||||
await prisma.build.create({
|
||||
data: {
|
||||
id: buildId,
|
||||
applicationId: applicationFound.id,
|
||||
destinationDockerId: applicationFound.destinationDocker.id,
|
||||
gitSourceId: applicationFound.gitSource.id,
|
||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
||||
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
|
||||
status: 'queued',
|
||||
type: 'webhook_commit'
|
||||
}
|
||||
});
|
||||
scheduler.workers.get('deployApplication').postMessage({
|
||||
build_id: buildId,
|
||||
type: 'webhook_commit',
|
||||
...applicationFound
|
||||
});
|
||||
|
||||
return {
|
||||
message: 'Queued. Thank you!'
|
||||
};
|
||||
} else if (githubEvent === 'pull_request') {
|
||||
const pullmergeRequestId = body.number;
|
||||
const pullmergeRequestAction = body.action;
|
||||
const sourceBranch = body.pull_request.head.ref.includes('/') ? body.pull_request.head.ref.split('/')[2] : body.pull_request.head.ref;
|
||||
if (!allowedActions.includes(pullmergeRequestAction)) {
|
||||
throw { status: 500, message: 'Action not allowed.' }
|
||||
const hmac = crypto.createHmac('sha256', webhookSecret);
|
||||
const digest = Buffer.from(
|
||||
'sha256=' + hmac.update(JSON.stringify(body)).digest('hex'),
|
||||
'utf8'
|
||||
);
|
||||
if (!isDev) {
|
||||
const checksum = Buffer.from(githubSignature, 'utf8');
|
||||
//@ts-ignore
|
||||
if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) {
|
||||
throw { status: 500, message: 'SHA256 checksum failed. Are you doing something fishy?' }
|
||||
};
|
||||
}
|
||||
|
||||
if (applicationFound.settings.previews) {
|
||||
if (applicationFound.destinationDockerId) {
|
||||
const isRunning = await checkContainer(
|
||||
{
|
||||
dockerId: applicationFound.destinationDocker.id,
|
||||
container: applicationFound.id
|
||||
}
|
||||
);
|
||||
if (!isRunning) {
|
||||
throw { status: 500, message: 'Application not running.' }
|
||||
}
|
||||
}
|
||||
if (
|
||||
pullmergeRequestAction === 'opened' ||
|
||||
pullmergeRequestAction === 'reopened' ||
|
||||
pullmergeRequestAction === 'synchronize'
|
||||
) {
|
||||
if (githubEvent === 'push') {
|
||||
if (!application.configHash) {
|
||||
const configHash = crypto
|
||||
//@ts-ignore
|
||||
.createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify({
|
||||
buildPack: application.buildPack,
|
||||
port: application.port,
|
||||
exposePort: application.exposePort,
|
||||
installCommand: application.installCommand,
|
||||
buildCommand: application.buildCommand,
|
||||
startCommand: application.startCommand
|
||||
})
|
||||
)
|
||||
.digest('hex');
|
||||
await prisma.application.update({
|
||||
where: { id: applicationFound.id },
|
||||
data: { updatedAt: new Date() }
|
||||
where: { id: application.id },
|
||||
data: { configHash }
|
||||
});
|
||||
await prisma.build.create({
|
||||
data: {
|
||||
id: buildId,
|
||||
applicationId: applicationFound.id,
|
||||
destinationDockerId: applicationFound.destinationDocker.id,
|
||||
gitSourceId: applicationFound.gitSource.id,
|
||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
||||
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
|
||||
status: 'queued',
|
||||
type: 'webhook_pr'
|
||||
}
|
||||
});
|
||||
scheduler.workers.get('deployApplication').postMessage({
|
||||
build_id: buildId,
|
||||
type: 'webhook_pr',
|
||||
...applicationFound,
|
||||
sourceBranch,
|
||||
pullmergeRequestId
|
||||
});
|
||||
|
||||
return {
|
||||
message: 'Queued. Thank you!'
|
||||
};
|
||||
} else if (pullmergeRequestAction === 'closed') {
|
||||
if (applicationFound.destinationDockerId) {
|
||||
const id = `${applicationFound.id}-${pullmergeRequestId}`;
|
||||
await removeContainer({ id, dockerId: applicationFound.destinationDocker.id });
|
||||
}
|
||||
return {
|
||||
message: 'Removed preview. Thank you!'
|
||||
};
|
||||
}
|
||||
} else {
|
||||
throw { status: 500, message: 'Pull request previews are not enabled.' }
|
||||
|
||||
await prisma.application.update({
|
||||
where: { id: application.id },
|
||||
data: { updatedAt: new Date() }
|
||||
});
|
||||
await prisma.build.create({
|
||||
data: {
|
||||
id: buildId,
|
||||
applicationId: application.id,
|
||||
destinationDockerId: application.destinationDocker.id,
|
||||
gitSourceId: application.gitSource.id,
|
||||
githubAppId: application.gitSource.githubApp?.id,
|
||||
gitlabAppId: application.gitSource.gitlabApp?.id,
|
||||
status: 'queued',
|
||||
type: 'webhook_commit'
|
||||
}
|
||||
});
|
||||
console.log(`Webhook for ${application.name} queued.`)
|
||||
|
||||
} else if (githubEvent === 'pull_request') {
|
||||
const pullmergeRequestId = body.number.toString();
|
||||
const pullmergeRequestAction = body.action;
|
||||
const sourceBranch = body.pull_request.head.ref.includes('/') ? body.pull_request.head.ref.split('/')[2] : body.pull_request.head.ref;
|
||||
if (!allowedActions.includes(pullmergeRequestAction)) {
|
||||
throw { status: 500, message: 'Action not allowed.' }
|
||||
}
|
||||
|
||||
if (application.settings.previews) {
|
||||
if (application.destinationDockerId) {
|
||||
const isRunning = await checkContainer(
|
||||
{
|
||||
dockerId: application.destinationDocker.id,
|
||||
container: application.id
|
||||
}
|
||||
);
|
||||
if (!isRunning) {
|
||||
throw { status: 500, message: 'Application not running.' }
|
||||
}
|
||||
}
|
||||
if (
|
||||
pullmergeRequestAction === 'opened' ||
|
||||
pullmergeRequestAction === 'reopened' ||
|
||||
pullmergeRequestAction === 'synchronize'
|
||||
) {
|
||||
await prisma.application.update({
|
||||
where: { id: application.id },
|
||||
data: { updatedAt: new Date() }
|
||||
});
|
||||
if (application.connectedDatabase && pullmergeRequestAction === 'opened' || pullmergeRequestAction === 'reopened') {
|
||||
// Coolify hosted database
|
||||
if (application.connectedDatabase.databaseId) {
|
||||
const databaseId = application.connectedDatabase.databaseId;
|
||||
const database = await prisma.database.findUnique({ where: { id: databaseId } });
|
||||
if (database) {
|
||||
await createdBranchDatabase(database, application.connectedDatabase.hostedDatabaseDBName, pullmergeRequestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
await prisma.build.create({
|
||||
data: {
|
||||
id: buildId,
|
||||
pullmergeRequestId,
|
||||
sourceBranch,
|
||||
applicationId: application.id,
|
||||
destinationDockerId: application.destinationDocker.id,
|
||||
gitSourceId: application.gitSource.id,
|
||||
githubAppId: application.gitSource.githubApp?.id,
|
||||
gitlabAppId: application.gitSource.gitlabApp?.id,
|
||||
status: 'queued',
|
||||
type: 'webhook_pr'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
} else if (pullmergeRequestAction === 'closed') {
|
||||
if (application.destinationDockerId) {
|
||||
const id = `${application.id}-${pullmergeRequestId}`;
|
||||
try {
|
||||
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
||||
} catch (error) { }
|
||||
}
|
||||
if (application.connectedDatabase.databaseId) {
|
||||
const databaseId = application.connectedDatabase.databaseId;
|
||||
const database = await prisma.database.findUnique({ where: { id: databaseId } });
|
||||
if (database) {
|
||||
await removeBranchDatabase(database, pullmergeRequestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw { status: 500, message: 'Not handled event.' }
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@ import axios from "axios";
|
||||
import cuid from "cuid";
|
||||
import crypto from "crypto";
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import { errorHandler, getAPIUrl, isDev, listSettings, prisma } from "../../../lib/common";
|
||||
import { errorHandler, getAPIUrl, getUIUrl, isDev, listSettings, prisma } from "../../../lib/common";
|
||||
import { checkContainer, removeContainer } from "../../../lib/docker";
|
||||
import { scheduler } from "../../../lib/scheduler";
|
||||
import { getApplicationFromDB, getApplicationFromDBWebhook } from "../../api/v1/applications/handlers";
|
||||
|
||||
import type { ConfigureGitLabApp, GitLabEvents } from "./types";
|
||||
@@ -30,7 +29,7 @@ export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLab
|
||||
});
|
||||
const { data } = await axios.post(`${htmlUrl}/oauth/token`, params)
|
||||
if (isDev) {
|
||||
return reply.redirect(`${getAPIUrl()}/webhooks/success?token=${data.access_token}`)
|
||||
return reply.redirect(`${getUIUrl()}/webhooks/success?token=${data.access_token}`)
|
||||
}
|
||||
return reply.redirect(`/webhooks/success?token=${data.access_token}`)
|
||||
} catch ({ status, message, ...other }) {
|
||||
@@ -40,65 +39,56 @@ export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLab
|
||||
export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
||||
const { object_kind: objectKind, ref, project_id } = request.body
|
||||
try {
|
||||
const buildId = cuid();
|
||||
|
||||
const allowedActions = ['opened', 'reopen', 'close', 'open', 'update'];
|
||||
|
||||
const webhookToken = request.headers['x-gitlab-token'];
|
||||
if (!webhookToken) {
|
||||
if (!webhookToken && !isDev) {
|
||||
throw { status: 500, message: 'Invalid webhookToken.' }
|
||||
}
|
||||
if (objectKind === 'push') {
|
||||
const projectId = Number(project_id);
|
||||
const branch = ref.split('/')[2];
|
||||
const applicationFound = await getApplicationFromDBWebhook(projectId, branch);
|
||||
if (applicationFound) {
|
||||
if (!applicationFound.configHash) {
|
||||
const configHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify({
|
||||
buildPack: applicationFound.buildPack,
|
||||
port: applicationFound.port,
|
||||
exposePort: applicationFound.exposePort,
|
||||
installCommand: applicationFound.installCommand,
|
||||
buildCommand: applicationFound.buildCommand,
|
||||
startCommand: applicationFound.startCommand
|
||||
})
|
||||
)
|
||||
.digest('hex');
|
||||
await prisma.application.updateMany({
|
||||
where: { branch, projectId },
|
||||
data: { configHash }
|
||||
const applicationsFound = await getApplicationFromDBWebhook(projectId, branch);
|
||||
if (applicationsFound && applicationsFound.length > 0) {
|
||||
for (const application of applicationsFound) {
|
||||
const buildId = cuid();
|
||||
if (!application.configHash) {
|
||||
const configHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(
|
||||
JSON.stringify({
|
||||
buildPack: application.buildPack,
|
||||
port: application.port,
|
||||
exposePort: application.exposePort,
|
||||
installCommand: application.installCommand,
|
||||
buildCommand: application.buildCommand,
|
||||
startCommand: application.startCommand
|
||||
})
|
||||
)
|
||||
.digest('hex');
|
||||
await prisma.application.update({
|
||||
where: { id: application.id },
|
||||
data: { configHash }
|
||||
});
|
||||
}
|
||||
await prisma.application.update({
|
||||
where: { id: application.id },
|
||||
data: { updatedAt: new Date() }
|
||||
});
|
||||
await prisma.build.create({
|
||||
data: {
|
||||
id: buildId,
|
||||
applicationId: application.id,
|
||||
destinationDockerId: application.destinationDocker.id,
|
||||
gitSourceId: application.gitSource.id,
|
||||
githubAppId: application.gitSource.githubApp?.id,
|
||||
gitlabAppId: application.gitSource.gitlabApp?.id,
|
||||
status: 'queued',
|
||||
type: 'webhook_commit'
|
||||
}
|
||||
});
|
||||
}
|
||||
await prisma.application.update({
|
||||
where: { id: applicationFound.id },
|
||||
data: { updatedAt: new Date() }
|
||||
});
|
||||
await prisma.build.create({
|
||||
data: {
|
||||
id: buildId,
|
||||
applicationId: applicationFound.id,
|
||||
destinationDockerId: applicationFound.destinationDocker.id,
|
||||
gitSourceId: applicationFound.gitSource.id,
|
||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
||||
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
|
||||
status: 'queued',
|
||||
type: 'webhook_commit'
|
||||
}
|
||||
});
|
||||
|
||||
scheduler.workers.get('deployApplication').postMessage({
|
||||
build_id: buildId,
|
||||
type: 'webhook_commit',
|
||||
...applicationFound
|
||||
});
|
||||
|
||||
return {
|
||||
message: 'Queued. Thank you!'
|
||||
};
|
||||
|
||||
}
|
||||
} else if (objectKind === 'merge_request') {
|
||||
const { object_attributes: { work_in_progress: isDraft, action, source_branch: sourceBranch, target_branch: targetBranch, iid: pullmergeRequestId }, project: { id } } = request.body
|
||||
@@ -111,71 +101,63 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
||||
throw { status: 500, message: 'Draft MR, do nothing.' }
|
||||
}
|
||||
|
||||
const applicationFound = await getApplicationFromDBWebhook(projectId, targetBranch);
|
||||
if (applicationFound) {
|
||||
if (applicationFound.settings.previews) {
|
||||
if (applicationFound.destinationDockerId) {
|
||||
const isRunning = await checkContainer(
|
||||
{
|
||||
dockerId: applicationFound.destinationDocker.id,
|
||||
container: applicationFound.id
|
||||
const applicationsFound = await getApplicationFromDBWebhook(projectId, targetBranch);
|
||||
if (applicationsFound && applicationsFound.length > 0) {
|
||||
for (const application of applicationsFound) {
|
||||
const buildId = cuid();
|
||||
if (application.settings.previews) {
|
||||
if (application.destinationDockerId) {
|
||||
const isRunning = await checkContainer(
|
||||
{
|
||||
dockerId: application.destinationDocker.id,
|
||||
container: application.id
|
||||
}
|
||||
);
|
||||
if (!isRunning) {
|
||||
throw { status: 500, message: 'Application not running.' }
|
||||
}
|
||||
);
|
||||
if (!isRunning) {
|
||||
throw { status: 500, message: 'Application not running.' }
|
||||
}
|
||||
}
|
||||
if (!isDev && applicationFound.gitSource.gitlabApp.webhookToken !== webhookToken) {
|
||||
throw { status: 500, message: 'Invalid webhookToken. Are you doing something nasty?!' }
|
||||
}
|
||||
if (
|
||||
action === 'opened' ||
|
||||
action === 'reopen' ||
|
||||
action === 'open' ||
|
||||
action === 'update'
|
||||
) {
|
||||
await prisma.application.update({
|
||||
where: { id: applicationFound.id },
|
||||
data: { updatedAt: new Date() }
|
||||
});
|
||||
await prisma.build.create({
|
||||
data: {
|
||||
id: buildId,
|
||||
applicationId: applicationFound.id,
|
||||
destinationDockerId: applicationFound.destinationDocker.id,
|
||||
gitSourceId: applicationFound.gitSource.id,
|
||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
||||
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
|
||||
status: 'queued',
|
||||
type: 'webhook_mr'
|
||||
if (!isDev && application.gitSource.gitlabApp.webhookToken !== webhookToken) {
|
||||
throw { status: 500, message: 'Invalid webhookToken. Are you doing something nasty?!' }
|
||||
}
|
||||
if (
|
||||
action === 'opened' ||
|
||||
action === 'reopen' ||
|
||||
action === 'open' ||
|
||||
action === 'update'
|
||||
) {
|
||||
await prisma.application.update({
|
||||
where: { id: application.id },
|
||||
data: { updatedAt: new Date() }
|
||||
});
|
||||
await prisma.build.create({
|
||||
data: {
|
||||
id: buildId,
|
||||
pullmergeRequestId: pullmergeRequestId.toString(),
|
||||
sourceBranch,
|
||||
applicationId: application.id,
|
||||
destinationDockerId: application.destinationDocker.id,
|
||||
gitSourceId: application.gitSource.id,
|
||||
githubAppId: application.gitSource.githubApp?.id,
|
||||
gitlabAppId: application.gitSource.gitlabApp?.id,
|
||||
status: 'queued',
|
||||
type: 'webhook_mr'
|
||||
}
|
||||
});
|
||||
return {
|
||||
message: 'Queued. Thank you!'
|
||||
};
|
||||
} else if (action === 'close') {
|
||||
if (application.destinationDockerId) {
|
||||
const id = `${application.id}-${pullmergeRequestId}`;
|
||||
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
||||
}
|
||||
});
|
||||
scheduler.workers.get('deployApplication').postMessage({
|
||||
build_id: buildId,
|
||||
type: 'webhook_mr',
|
||||
...applicationFound,
|
||||
sourceBranch,
|
||||
pullmergeRequestId
|
||||
});
|
||||
|
||||
return {
|
||||
message: 'Queued. Thank you!'
|
||||
};
|
||||
} else if (action === 'close') {
|
||||
if (applicationFound.destinationDockerId) {
|
||||
const id = `${applicationFound.id}-${pullmergeRequestId}`;
|
||||
const engine = applicationFound.destinationDocker.engine;
|
||||
await removeContainer({ id, dockerId: applicationFound.destinationDocker.id });
|
||||
}
|
||||
return {
|
||||
message: 'Removed preview. Thank you!'
|
||||
};
|
||||
}
|
||||
}
|
||||
throw { status: 500, message: 'Merge request previews are not enabled.' }
|
||||
}
|
||||
}
|
||||
throw { status: 500, message: 'Not handled event.' }
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { FastifyRequest } from "fastify";
|
||||
import { errorHandler, getDomain, isDev, prisma, supportedServiceTypesAndVersions, include, executeDockerCmd } from "../../../lib/common";
|
||||
import { errorHandler, getDomain, isDev, prisma, executeDockerCmd } from "../../../lib/common";
|
||||
import { supportedServiceTypesAndVersions } from "../../../lib/services/supportedVersions";
|
||||
import { includeServices } from "../../../lib/services/common";
|
||||
import { TraefikOtherConfiguration } from "./types";
|
||||
import { OnlyId } from "../../../types";
|
||||
|
||||
function configureMiddleware(
|
||||
{ id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type },
|
||||
@@ -23,7 +26,30 @@ function configureMiddleware(
|
||||
]
|
||||
}
|
||||
};
|
||||
if (type === 'appwrite') {
|
||||
traefik.http.routers[`${id}-realtime`] = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/v1/realtime\`)`,
|
||||
service: `${`${id}-realtime`}`,
|
||||
tls: {
|
||||
domains: {
|
||||
main: `${domain}`
|
||||
}
|
||||
},
|
||||
middlewares: []
|
||||
};
|
||||
|
||||
|
||||
traefik.http.services[`${id}-realtime`] = {
|
||||
loadbalancer: {
|
||||
servers: [
|
||||
{
|
||||
url: `http://${container}-realtime:${port}`
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
if (isDualCerts) {
|
||||
traefik.http.routers[`${id}-secure`] = {
|
||||
entrypoints: ['websecure'],
|
||||
@@ -110,6 +136,23 @@ function configureMiddleware(
|
||||
]
|
||||
}
|
||||
};
|
||||
if (type === 'appwrite') {
|
||||
traefik.http.routers[`${id}-realtime`] = {
|
||||
entrypoints: ['web'],
|
||||
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/v1/realtime\`)`,
|
||||
service: `${id}-realtime`,
|
||||
middlewares: []
|
||||
};
|
||||
traefik.http.services[`${id}-realtime`] = {
|
||||
loadbalancer: {
|
||||
servers: [
|
||||
{
|
||||
url: `http://${container}-realtime:${port}`
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!isDualCerts) {
|
||||
if (isWWW) {
|
||||
@@ -234,7 +277,7 @@ export async function traefikConfiguration(request, reply) {
|
||||
}
|
||||
const services: any = await prisma.service.findMany({
|
||||
where: { destinationDocker: { remoteEngine: false } },
|
||||
include,
|
||||
include: includeServices,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
@@ -484,12 +527,11 @@ export async function traefikOtherConfiguration(request: FastifyRequest<TraefikO
|
||||
}
|
||||
throw { status: 500 }
|
||||
} catch ({ status, message }) {
|
||||
console.log(status, message);
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
|
||||
export async function remoteTraefikConfiguration(request: FastifyRequest) {
|
||||
export async function remoteTraefikConfiguration(request: FastifyRequest<OnlyId>) {
|
||||
const { id } = request.params
|
||||
try {
|
||||
const traefik = {
|
||||
@@ -591,7 +633,7 @@ export async function remoteTraefikConfiguration(request: FastifyRequest) {
|
||||
}
|
||||
const services: any = await prisma.service.findMany({
|
||||
where: { destinationDocker: { id } },
|
||||
include,
|
||||
include: includeServices,
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { OnlyId } from '../../../types';
|
||||
import { remoteTraefikConfiguration, traefikConfiguration, traefikOtherConfiguration } from './handlers';
|
||||
import { TraefikOtherConfiguration } from './types';
|
||||
|
||||
@@ -6,7 +7,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply));
|
||||
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
|
||||
|
||||
fastify.get('/remote/:id', async (request) => remoteTraefikConfiguration(request));
|
||||
fastify.get<OnlyId>('/remote/:id', async (request) => remoteTraefikConfiguration(request));
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
@@ -36,4 +36,3 @@ export interface SaveDatabaseSettings extends OnlyId {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
4
apps/i18n/.env.example
Normal file
@@ -0,0 +1,4 @@
|
||||
WEBLATE_INSTANCE_URL=http://localhost
|
||||
WEBLATE_COMPONENT_NAME=coolify
|
||||
WEBLATE_TOKEN=
|
||||
TRANSLATION_DIR=
|
||||
1
apps/i18n/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
locales/*
|
||||
63
apps/i18n/index.mjs
Normal file
@@ -0,0 +1,63 @@
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url';
|
||||
import Gettext from 'node-gettext'
|
||||
import { po } from 'gettext-parser'
|
||||
import got from 'got';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const weblateInstanceURL = process.env.WEBLATE_INSTANCE_URL;
|
||||
const weblateComponentName = process.env.WEBLATE_COMPONENT_NAME
|
||||
const token = process.env.WEBLATE_TOKEN;
|
||||
|
||||
const translationsDir = process.env.TRANSLATION_DIR;
|
||||
const translationsPODir = './locales';
|
||||
const locales = []
|
||||
const domain = 'locale'
|
||||
|
||||
const translations = await got(`${weblateInstanceURL}/api/components/${weblateComponentName}/glossary/translations/?format=json`, {
|
||||
headers: {
|
||||
"Authorization": `Token ${token}`
|
||||
}
|
||||
}).json()
|
||||
for (const translation of translations.results) {
|
||||
const code = translation.language_code
|
||||
locales.push(code)
|
||||
|
||||
const fileUrl = translation.file_url.replace('=json', '=po')
|
||||
const file = await got(fileUrl, {
|
||||
headers: {
|
||||
"Authorization": `Token ${token}`
|
||||
}
|
||||
}).text()
|
||||
fs.writeFileSync(path.join(__dirname, translationsPODir, domain + '-' + code + '.po'), file)
|
||||
}
|
||||
|
||||
|
||||
const gt = new Gettext()
|
||||
|
||||
locales.forEach((locale) => {
|
||||
let json = {}
|
||||
const fileName = `${domain}-${locale}.po`
|
||||
const translationsFilePath = path.join(translationsPODir, fileName)
|
||||
const translationsContent = fs.readFileSync(translationsFilePath)
|
||||
|
||||
const parsedTranslations = po.parse(translationsContent)
|
||||
const a = gt.gettext(parsedTranslations)
|
||||
for (const [key, value] of Object.entries(a)) {
|
||||
if (key === 'translations') {
|
||||
for (const [key1, value1] of Object.entries(value)) {
|
||||
if (key1 !== '') {
|
||||
for (const [key2, value2] of Object.entries(value1)) {
|
||||
json[value2.msgctxt] = value2.msgstr[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(`${translationsDir}/${locale}.json`, JSON.stringify(json))
|
||||
})
|
||||
15
apps/i18n/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "i18n-converter",
|
||||
"description": "Convert Weblate translations to sveltekit-i18n",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"translate": "node index.mjs"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"node-gettext": "3.0.0",
|
||||
"gettext-parser": "6.0.0",
|
||||
"got": "12.3.1",
|
||||
"dotenv": "16.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm init svelte
|
||||
|
||||
# create a new project in my-app
|
||||
npm init svelte my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||
@@ -1,13 +1,12 @@
|
||||
{
|
||||
"name": "coolify-ui",
|
||||
"name": "ui",
|
||||
"description": "Coolify's SvelteKit UI",
|
||||
"license": "AGPL-3.0",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"package": "svelte-kit package",
|
||||
"preview": "svelte-kit preview",
|
||||
"prepare": "svelte-kit sync",
|
||||
"test": "playwright test",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
@@ -15,32 +14,38 @@
|
||||
"format": "prettier --write --plugin-search-dir=. ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.23.4",
|
||||
"@sveltejs/kit": "1.0.0-next.377",
|
||||
"@floating-ui/dom": "1.0.1",
|
||||
"@playwright/test": "1.25.1",
|
||||
"@popperjs/core": "2.11.6",
|
||||
"@sveltejs/kit": "1.0.0-next.405",
|
||||
"@types/js-cookie": "3.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.30.6",
|
||||
"@typescript-eslint/parser": "5.30.6",
|
||||
"autoprefixer": "10.4.7",
|
||||
"eslint": "8.20.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.36.1",
|
||||
"@typescript-eslint/parser": "5.36.1",
|
||||
"autoprefixer": "10.4.8",
|
||||
"classnames": "2.3.1",
|
||||
"eslint": "8.23.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-svelte3": "4.0.0",
|
||||
"postcss": "8.4.14",
|
||||
"flowbite": "1.5.2",
|
||||
"flowbite-svelte": "0.26.2",
|
||||
"postcss": "8.4.16",
|
||||
"prettier": "2.7.1",
|
||||
"prettier-plugin-svelte": "2.7.0",
|
||||
"svelte": "3.49.0",
|
||||
"svelte-check": "2.8.0",
|
||||
"svelte": "3.50.0",
|
||||
"svelte-check": "2.9.0",
|
||||
"svelte-preprocess": "4.10.7",
|
||||
"tailwindcss": "3.1.6",
|
||||
"tailwindcss": "3.1.8",
|
||||
"tailwindcss-scrollbar": "0.1.0",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4",
|
||||
"vite": "3.0.1"
|
||||
"typescript": "4.8.2",
|
||||
"vite": "3.1.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@sveltejs/adapter-static": "1.0.0-next.37",
|
||||
"@zerodevx/svelte-toast": "0.7.2",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.39",
|
||||
"@tailwindcss/typography": "^0.5.7",
|
||||
"cuid": "2.1.8",
|
||||
"daisyui": "2.24.2",
|
||||
"js-cookie": "3.0.1",
|
||||
"p-limit": "4.0.0",
|
||||
"svelte-select": "4.4.7",
|
||||
|
||||
1
apps/ui/src/app.d.ts
vendored
@@ -21,4 +21,5 @@ declare namespace App {
|
||||
}
|
||||
|
||||
declare const GITPOD_WORKSPACE_URL: string
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-theme="coollabs" lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
|
||||
@@ -7,10 +7,12 @@ export function getAPIUrl() {
|
||||
const newURL = href.replace('https://', 'https://3001-').replace(/\/$/, '')
|
||||
return newURL
|
||||
}
|
||||
if (CODESANDBOX_HOST) {
|
||||
return `https://${CODESANDBOX_HOST.replace(/\$PORT/,'3001')}`
|
||||
}
|
||||
return dev ? 'http://localhost:3001' : 'http://localhost:3000';
|
||||
}
|
||||
export function getWebhookUrl(type: string) {
|
||||
console.log(GITPOD_WORKSPACE_URL)
|
||||
if (GITPOD_WORKSPACE_URL) {
|
||||
const { href } = new URL(GITPOD_WORKSPACE_URL)
|
||||
const newURL = href.replace('https://', 'https://3001-').replace(/\/$/, '')
|
||||
@@ -21,6 +23,15 @@ export function getWebhookUrl(type: string) {
|
||||
return `${newURL}/webhooks/gitlab/events`
|
||||
}
|
||||
}
|
||||
if (CODESANDBOX_HOST) {
|
||||
const newURL = `https://${CODESANDBOX_HOST.replace(/\$PORT/,'3001')}`
|
||||
if (type === 'github') {
|
||||
return `${newURL}/webhooks/github/events`
|
||||
}
|
||||
if (type === 'gitlab') {
|
||||
return `${newURL}/webhooks/gitlab/events`
|
||||
}
|
||||
}
|
||||
return `https://webhook.site/0e5beb2c-4e9b-40e2-a89e-32295e570c21/events`;
|
||||
}
|
||||
async function send({
|
||||
|
||||
@@ -1,165 +1,4 @@
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
|
||||
export const supportedServiceTypesAndVersions = [
|
||||
{
|
||||
name: 'plausibleanalytics',
|
||||
fancyName: 'Plausible Analytics',
|
||||
baseImage: 'plausible/analytics',
|
||||
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
|
||||
versions: ['latest', 'stable'],
|
||||
recommendedVersion: 'stable',
|
||||
ports: {
|
||||
main: 8000
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'nocodb',
|
||||
fancyName: 'NocoDB',
|
||||
baseImage: 'nocodb/nocodb',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8080
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'minio',
|
||||
fancyName: 'MinIO',
|
||||
baseImage: 'minio/minio',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 9001
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'vscodeserver',
|
||||
fancyName: 'VSCode Server',
|
||||
baseImage: 'codercom/code-server',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8080
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'wordpress',
|
||||
fancyName: 'Wordpress',
|
||||
baseImage: 'wordpress',
|
||||
images: ['bitnami/mysql:5.7'],
|
||||
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'vaultwarden',
|
||||
fancyName: 'Vaultwarden',
|
||||
baseImage: 'vaultwarden/server',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'languagetool',
|
||||
fancyName: 'LanguageTool',
|
||||
baseImage: 'silviof/docker-languagetool',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n',
|
||||
fancyName: 'n8n',
|
||||
baseImage: 'n8nio/n8n',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 5678
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'uptimekuma',
|
||||
fancyName: 'Uptime Kuma',
|
||||
baseImage: 'louislam/uptime-kuma',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 3001
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ghost',
|
||||
fancyName: 'Ghost',
|
||||
baseImage: 'bitnami/ghost',
|
||||
images: ['bitnami/mariadb'],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 2368
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'meilisearch',
|
||||
fancyName: 'Meilisearch',
|
||||
baseImage: 'getmeili/meilisearch',
|
||||
images: [],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 7700
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'umami',
|
||||
fancyName: 'Umami',
|
||||
baseImage: 'ghcr.io/mikecao/umami',
|
||||
images: ['postgres:12-alpine'],
|
||||
versions: ['postgresql-latest'],
|
||||
recommendedVersion: 'postgresql-latest',
|
||||
ports: {
|
||||
main: 3000
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'hasura',
|
||||
fancyName: 'Hasura',
|
||||
baseImage: 'hasura/graphql-engine',
|
||||
images: ['postgres:12-alpine'],
|
||||
versions: ['latest', 'v2.5.1'],
|
||||
recommendedVersion: 'v2.5.1',
|
||||
ports: {
|
||||
main: 8080
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'fider',
|
||||
fancyName: 'Fider',
|
||||
baseImage: 'getfider/fider',
|
||||
images: ['postgres:12-alpine'],
|
||||
versions: ['stable'],
|
||||
recommendedVersion: 'stable',
|
||||
ports: {
|
||||
main: 3000
|
||||
}
|
||||
},
|
||||
// {
|
||||
// name: 'moodle',
|
||||
// fancyName: 'Moodle',
|
||||
// baseImage: 'bitnami/moodle',
|
||||
// images: [],
|
||||
// versions: ['latest', 'v4.0.2'],
|
||||
// recommendedVersion: 'latest',
|
||||
// ports: {
|
||||
// main: 8080
|
||||
// }
|
||||
// }
|
||||
];
|
||||
import { addToast } from '$lib/store';
|
||||
|
||||
export const asyncSleep = (delay: number) =>
|
||||
new Promise((resolve) => setTimeout(resolve, delay));
|
||||
@@ -167,12 +6,20 @@ export const asyncSleep = (delay: number) =>
|
||||
export function errorNotification(error: any): void {
|
||||
if (error.message) {
|
||||
if (error.message === 'Cannot read properties of undefined (reading \'postMessage\')') {
|
||||
toast.push('Currently there is background process in progress. Please try again later.');
|
||||
return;
|
||||
return addToast({
|
||||
message: 'Currently there is background process in progress. Please try again later.',
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
toast.push(error.message);
|
||||
addToast({
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
});
|
||||
} else {
|
||||
toast.push('Ooops, something is not okay, are you okay?');
|
||||
addToast({
|
||||
message: 'Ooops, something is not okay, are you okay?',
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
console.error(JSON.stringify(error));
|
||||
}
|
||||
@@ -210,7 +57,7 @@ export const staticDeployments = [
|
||||
'astro',
|
||||
'eleventy'
|
||||
];
|
||||
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel'];
|
||||
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel', 'heroku'];
|
||||
|
||||
|
||||
export function generateRemoteEngine(destination: any) {
|
||||
@@ -224,44 +71,6 @@ export function changeQueryParams(buildId: string) {
|
||||
return history.pushState(null, null, '?' + queryParams.toString());
|
||||
}
|
||||
|
||||
export const supportedDatabaseTypesAndVersions = [
|
||||
{
|
||||
name: 'mongodb',
|
||||
fancyName: 'MongoDB',
|
||||
baseImage: 'bitnami/mongodb',
|
||||
versions: ['5.0', '4.4', '4.2']
|
||||
},
|
||||
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0', '5.7'] },
|
||||
{
|
||||
name: 'mariadb',
|
||||
fancyName: 'MariaDB',
|
||||
baseImage: 'bitnami/mariadb',
|
||||
versions: ['10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
|
||||
},
|
||||
{
|
||||
name: 'postgresql',
|
||||
fancyName: 'PostgreSQL',
|
||||
baseImage: 'bitnami/postgresql',
|
||||
versions: ['14.2.0', '13.6.0', '12.10.0 ', '11.15.0', '10.20.0']
|
||||
},
|
||||
{
|
||||
name: 'redis',
|
||||
fancyName: 'Redis',
|
||||
baseImage: 'bitnami/redis',
|
||||
versions: ['6.2', '6.0', '5.0']
|
||||
},
|
||||
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
|
||||
];
|
||||
|
||||
export const getServiceMainPort = (service: string) => {
|
||||
const serviceType = supportedServiceTypesAndVersions.find((s) => s.name === service);
|
||||
if (serviceType) {
|
||||
return serviceType.ports.main;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export function handlerNotFoundLoad(error: any, url: URL) {
|
||||
if (error?.status === 404) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/env';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { addToast } from '$lib/store';
|
||||
let showPassword = false;
|
||||
|
||||
export let value: string;
|
||||
@@ -9,7 +9,7 @@
|
||||
export let readonly = false;
|
||||
export let textarea = false;
|
||||
export let required = false;
|
||||
export let pattern: string|null|undefined = null;
|
||||
export let pattern: string | null | undefined = null;
|
||||
export let id: string;
|
||||
export let name: string;
|
||||
export let placeholder = '';
|
||||
@@ -20,7 +20,10 @@
|
||||
function copyToClipboard() {
|
||||
if (isHttps && navigator.clipboard) {
|
||||
navigator.clipboard.writeText(value);
|
||||
toast.push('Copied to clipboard.');
|
||||
addToast({
|
||||
message: 'Copied to clipboard.',
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
32
apps/ui/src/lib/components/DocLink.svelte
Normal file
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
export let url = 'https://docs.coollabs.io';
|
||||
let id =
|
||||
'cool-' +
|
||||
url
|
||||
.split('')
|
||||
.map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
.slice(-16);
|
||||
</script>
|
||||
|
||||
<a {id} href={url} target="_blank" class="icons inline-block text-pink-500 cursor-pointer text-xs">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M6 4h11a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-11a1 1 0 0 1 -1 -1v-14a1 1 0 0 1 1 -1m3 0v18"
|
||||
/>
|
||||
<line x1="13" y1="8" x2="15" y2="8" />
|
||||
<line x1="13" y1="12" x2="15" y2="12" />
|
||||
</svg>
|
||||
</a>
|
||||
<Tooltip triggeredBy={`#${id}`}>See details in the documentation</Tooltip>
|
||||
@@ -1,6 +1,31 @@
|
||||
<script lang="ts">
|
||||
export let text: string;
|
||||
export let customClass = 'max-w-[24rem]';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
export let explanation = '';
|
||||
let id: any;
|
||||
let self: any;
|
||||
onMount(() => {
|
||||
id = `info-${self.offsetLeft}-${self.offsetTop}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="p-2 text-xs text-stone-400 {customClass}">{@html text}</div>
|
||||
<div {id} class="inline-block mx-2 text-pink-500 cursor-pointer" bind:this={self}>
|
||||
<svg
|
||||
fill="none"
|
||||
height="18"
|
||||
shape-rendering="geometricPrecision"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
width="18"
|
||||
><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z" /><path
|
||||
d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3"
|
||||
/><circle cx="12" cy="17" r=".5" />
|
||||
</svg>
|
||||
</div>
|
||||
{#if id}
|
||||
<Tooltip triggeredBy={`#${id}`}>{@html explanation}</Tooltip>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import Explaner from './Explainer.svelte';
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
|
||||
export let id: any;
|
||||
export let setting: any;
|
||||
export let title: any;
|
||||
export let description: any;
|
||||
@@ -8,22 +10,18 @@
|
||||
export let disabled = false;
|
||||
export let dataTooltip: any = null;
|
||||
export let loading = false;
|
||||
let triggeredBy = `#${id}`;
|
||||
</script>
|
||||
|
||||
<div class="flex items-center py-4 pr-8">
|
||||
<div class="flex w-96 flex-col">
|
||||
<div class="text-xs font-bold text-stone-100 md:text-base">{title}</div>
|
||||
<Explainer text={description} />
|
||||
<div class="text-xs font-bold text-stone-100 md:text-base">
|
||||
{title}<Explaner explanation={description} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class:tooltip={dataTooltip}
|
||||
class:text-center={isCenter}
|
||||
data-tooltip={dataTooltip}
|
||||
class="flex justify-center"
|
||||
>
|
||||
<div class:text-center={isCenter} class="flex justify-center">
|
||||
<div
|
||||
type="button"
|
||||
on:click
|
||||
aria-pressed="false"
|
||||
class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
||||
@@ -31,6 +29,7 @@
|
||||
class:bg-green-600={!loading && setting}
|
||||
class:bg-stone-700={!loading && !setting}
|
||||
class:bg-yellow-500={loading}
|
||||
{id}
|
||||
>
|
||||
<span class="sr-only">Use setting</span>
|
||||
<span
|
||||
@@ -71,3 +70,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if dataTooltip}
|
||||
<Tooltip {triggeredBy} placement="top">{dataTooltip}</Tooltip>
|
||||
{/if}
|
||||
|
||||
6
apps/ui/src/lib/components/SimpleExplainer.svelte
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts">
|
||||
export let text: string;
|
||||
export let customClass = 'max-w-[24rem]';
|
||||
</script>
|
||||
|
||||
<div class="p-2 text-xs text-stone-400 {customClass}">{@html text}</div>
|
||||
59
apps/ui/src/lib/components/Toast.svelte
Normal file
@@ -0,0 +1,59 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
export let type = 'info';
|
||||
</script>
|
||||
|
||||
<div
|
||||
on:click={() => dispatch('click')}
|
||||
on:mouseover={() => dispatch('pause')}
|
||||
on:focus={() => dispatch('pause')}
|
||||
on:mouseout={() => dispatch('resume')}
|
||||
on:blur={() => dispatch('resume')}
|
||||
class="alert shadow-lg text-white rounded hover:scale-105 transition-all duration-100 cursor-pointer"
|
||||
class:bg-coollabs={type === 'success'}
|
||||
class:alert-error={type === 'error'}
|
||||
class:alert-info={type === 'info'}
|
||||
>
|
||||
{#if type === 'success'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current flex-shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/></svg
|
||||
>
|
||||
{:else if type === 'error'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current flex-shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/></svg
|
||||
>
|
||||
{:else if type === 'info'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
class="stroke-current flex-shrink-0 w-6 h-6"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/></svg
|
||||
>
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
27
apps/ui/src/lib/components/Toasts.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import Toast from './Toast.svelte';
|
||||
|
||||
import { dismissToast, pauseToast, resumeToast, toasts } from '$lib/store';
|
||||
</script>
|
||||
|
||||
{#if $toasts}
|
||||
<section>
|
||||
<article class="toast toast-top toast-end rounded-none" role="alert" transition:fade>
|
||||
{#each $toasts as toast (toast.id)}
|
||||
<Toast
|
||||
type={toast.type}
|
||||
on:resume={() => resumeToast(toast.id)}
|
||||
on:pause={() => pauseToast(toast.id)}
|
||||
on:click={() => dismissToast(toast.id)}>{@html toast.message}</Toast
|
||||
>
|
||||
{/each}
|
||||
</article>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
section {
|
||||
@apply fixed top-0 left-0 right-0 w-full flex flex-col mt-4 justify-center z-[1000];
|
||||
}
|
||||
</style>
|
||||
8
apps/ui/src/lib/components/Tooltip.svelte
Normal file
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Tooltip } from 'flowbite-svelte';
|
||||
export let placement = 'bottom';
|
||||
export let color = 'bg-coollabs text-left';
|
||||
export let triggeredBy = '#tooltip-default';
|
||||
</script>
|
||||
|
||||
<Tooltip {triggeredBy} {placement} arrow={false} {color} style="custom"><slot /></Tooltip>
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { dev } from '$app/env';
|
||||
import { get, post } from '$lib/api';
|
||||
import { appSession, features } from '$lib/store';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { addToast, appSession, features } from '$lib/store';
|
||||
import { asyncSleep, errorNotification } from '$lib/common';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
@@ -17,12 +16,15 @@
|
||||
updateStatus.loading = true;
|
||||
try {
|
||||
if (dev) {
|
||||
console.log(`updating to ${latestVersion}`);
|
||||
await asyncSleep(4000);
|
||||
return window.location.reload();
|
||||
} else {
|
||||
await post(`/update`, { type: 'update', latestVersion });
|
||||
toast.push('Update completed.<br><br>Waiting for the new version to start...');
|
||||
addToast({
|
||||
message: 'Update completed.<br><br>Waiting for the new version to start...',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
let reachable = false;
|
||||
let tries = 0;
|
||||
do {
|
||||
@@ -36,7 +38,10 @@
|
||||
if (reachable) break;
|
||||
tries++;
|
||||
} while (!reachable || tries < 120);
|
||||
toast.push('New version reachable. Reloading...');
|
||||
addToast({
|
||||
message: 'New version reachable. Reloading...',
|
||||
type: 'success'
|
||||
});
|
||||
updateStatus.loading = false;
|
||||
updateStatus.success = true;
|
||||
await asyncSleep(3000);
|
||||
@@ -75,9 +80,9 @@
|
||||
{#if isUpdateAvailable}
|
||||
<button
|
||||
disabled={updateStatus.success === false}
|
||||
title="Update available"
|
||||
on:click={update}
|
||||
class="icons tooltip-right bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
|
||||
class="icons tooltip tooltip-right tooltip-primary bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
|
||||
data-tip="Update Available!"
|
||||
>
|
||||
{#if updateStatus.loading}
|
||||
<svg
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
};
|
||||
let usageInterval: any;
|
||||
let loading = {
|
||||
usage: false
|
||||
usage: false,
|
||||
cleanup: false
|
||||
};
|
||||
import { appSession } from '$lib/store';
|
||||
import { addToast, appSession } from '$lib/store';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { get, post } from '$lib/api';
|
||||
import { errorNotification } from '$lib/common';
|
||||
import Trend from './Trend.svelte';
|
||||
async function getStatus() {
|
||||
if (loading.usage) return;
|
||||
loading.usage = true;
|
||||
@@ -33,6 +33,7 @@
|
||||
usage = data.usage;
|
||||
loading.usage = false;
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(usageInterval);
|
||||
});
|
||||
@@ -48,107 +49,99 @@
|
||||
return errorNotification(error);
|
||||
}
|
||||
});
|
||||
|
||||
let warning = {
|
||||
memory: false,
|
||||
cpu: false,
|
||||
disk: false
|
||||
};
|
||||
let trends = {
|
||||
memory: 'stable',
|
||||
cpu: 'stable',
|
||||
disk: 'stable'
|
||||
};
|
||||
async function manuallyCleanupStorage() {
|
||||
return await post('/internal/cleanup', {});
|
||||
try {
|
||||
loading.cleanup = true;
|
||||
await post('/internal/cleanup', {});
|
||||
return addToast({
|
||||
message: 'Cleanup done.',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
loading.cleanup = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $appSession.teamId === '0'}
|
||||
<div class="px-6 text-2xl font-bold">Server Usage</div>
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Total Memory</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{(usage?.memory.totalMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
||||
</dd>
|
||||
<div class="w-full">
|
||||
<div class="flex lg:flex-row flex-col gap-4">
|
||||
<h1 class="title lg:text-3xl">Hardware Details</h1>
|
||||
<div class="flex lg:flex-row flex-col space-x-0 lg:space-x-2 space-y-2 lg:space-y-0">
|
||||
{#if $appSession.teamId === '0'}
|
||||
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
|
||||
>Cleanup Storage</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider" />
|
||||
<div class="grid grid-flow-col gap-4 grid-rows-3 justify-start lg:justify-center lg:grid-rows-1">
|
||||
<div class="stats stats-vertical min-w-[16rem] mb-5 rounded bg-transparent">
|
||||
<div class="stat">
|
||||
<div class="stat-title">Total Memory</div>
|
||||
<div class="stat-value text-2xl">
|
||||
{(usage?.memory.totalMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">Used Memory</div>
|
||||
<div class="stat-value text-2xl">
|
||||
{(usage?.memory.usedMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">Free Memory</div>
|
||||
<div class="stat-value text-2xl">
|
||||
{usage?.memory.freeMemPercentage}<span class="text-sm">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Used Memory</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white ">
|
||||
{(usage?.memory.usedMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="stats stats-vertical min-w-[20rem] mb-5 bg-transparent rounded">
|
||||
<div class="stat">
|
||||
<div class="stat-title">Total CPU</div>
|
||||
<div class="stat-value text-2xl">
|
||||
{usage?.cpu.count}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
|
||||
class:bg-red-500={warning.memory}
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">Free Memory</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.memory.freeMemPercentage}<span class="text-sm">%</span>
|
||||
{#if !warning.memory}
|
||||
<Trend trend={trends.memory} />
|
||||
{/if}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Total CPUs</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.cpu.count}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
|
||||
class:bg-red-500={warning.cpu}
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">CPU Usage</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.cpu.usage}<span class="text-sm">%</span>
|
||||
{#if !warning.cpu}
|
||||
<Trend trend={trends.cpu} />
|
||||
{/if}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Load Average (5/10/30mins)</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.cpu.load.join('/')}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<dl class="relative mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Total Disk</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.disk.totalGb}<span class="text-sm">GB</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left">
|
||||
<dt class="truncate text-sm font-medium text-white">Used Disk</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.disk.usedGb}<span class="text-sm">GB</span>
|
||||
</dd>
|
||||
<button on:click={manuallyCleanupStorage} class="bg-coollabs hover:bg-coollabs-100"
|
||||
>Cleanup Storage</button
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="overflow-hidden rounded px-4 py-5 text-center sm:p-6 sm:text-left"
|
||||
class:bg-red-500={warning.disk}
|
||||
>
|
||||
<dt class="truncate text-sm font-medium text-white">Free Disk</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-white">
|
||||
{usage?.disk.freePercentage}<span class="text-sm">%</span>
|
||||
{#if !warning.disk}
|
||||
<Trend trend={trends.disk} />
|
||||
{/if}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="stat">
|
||||
<div class="stat-title">CPU Usage</div>
|
||||
<div class="stat-value text-2xl">
|
||||
{usage?.cpu.usage}<span class="text-sm">%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-6 pt-20 text-2xl font-bold">Resources</div>
|
||||
{/if}
|
||||
<div class="stat">
|
||||
<div class="stat-title">Load Average (5,10,30mins)</div>
|
||||
<div class="stat-value text-2xl">{usage?.cpu.load}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats stats-vertical min-w-[16rem] mb-5 bg-transparent rounded">
|
||||
<div class="stat">
|
||||
<div class="stat-title">Total Disk</div>
|
||||
<div class="stat-value text-2xl">
|
||||
{usage?.disk.totalGb}<span class="text-sm">GB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">Used Disk</div>
|
||||
<div class="stat-value text-2xl">
|
||||
{usage?.disk.usedGb}<span class="text-sm">GB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">Free Disk</div>
|
||||
<div class="stat-value text-2xl">
|
||||
{usage?.disk.freePercentage}<span class="text-sm">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import * as Icons from '$lib/components/svg/applications';
|
||||
export let application: any;
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
{#if application.buildPack?.toLowerCase() === 'rust'}
|
||||
<Icons.Rust {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'node'}
|
||||
<Icons.Nodejs {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'react'}
|
||||
<Icons.React {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'svelte'}
|
||||
<Icons.Svelte {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'vuejs'}
|
||||
<Icons.Vuejs {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'php'}
|
||||
<Icons.Php {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'python'}
|
||||
<Icons.Python {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'static'}
|
||||
<Icons.Static {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'nestjs'}
|
||||
<Icons.Nestjs {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'nuxtjs'}
|
||||
<Icons.Nuxtjs {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'nextjs'}
|
||||
<Icons.Nextjs {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'gatsby'}
|
||||
<Icons.Gatsby {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'docker'}
|
||||
<Icons.Docker {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'astro'}
|
||||
<Icons.Astro {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'eleventy'}
|
||||
<Icons.Eleventy {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'deno'}
|
||||
<Icons.Deno {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'laravel'}
|
||||
<Icons.Laravel {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'heroku'}
|
||||
<Icons.Heroku {isAbsolute} />
|
||||
{/if}
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class="absolute top-0 left-0 -m-6 h-14 w-14"
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-6 h-14 w-14' : 'mx-auto w-8 h-8'}
|
||||
viewBox="0 0 256 256"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,5 +1,11 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class="text-white-500 absolute top-0 left-0 -m-4 h-10 w-10"
|
||||
class={isAbsolute
|
||||
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-white-500'
|
||||
: 'mx-auto w-8 h-8 text-white-500'}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
|
||||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -1,73 +1,9 @@
|
||||
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-8 h-16 w-16">
|
||||
<g
|
||||
><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#3A4D54"
|
||||
d="M73.8 50.8h11.3v11.5h5.7c2.6 0 5.3-.5 7.8-1.3 1.2-.4 2.6-1 3.8-1.7-1.6-2.1-2.4-4.7-2.6-7.3-.3-3.5.4-8.1 2.8-10.8l1.2-1.4 1.4 1.1c3.6 2.9 6.5 6.8 7.1 11.4 4.3-1.3 9.3-1 13.1 1.2l1.5.9-.8 1.6c-3.2 6.2-9.9 8.2-16.4 7.8-9.8 24.3-31 35.8-56.8 35.8-13.3 0-25.5-5-32.5-16.8l-.1-.2-1-2.1c-2.4-5.2-3.1-10.9-2.6-16.6l.2-1.7h9.6v-11.4h11.3v-11.2h22.5v-11.3h13.5v22.5z"
|
||||
/><path
|
||||
fill="#00AADA"
|
||||
d="M110.4 55.1c.8-5.9-3.6-10.5-6.4-12.7-3.1 3.6-3.6 13.2 1.3 17.2-2.8 2.4-8.5 4.7-14.5 4.7h-72.2c-.6 6.2.5 11.9 3 16.8l.8 1.5c.5.9 1.1 1.7 1.7 2.6 3 .2 5.7.3 8.2.2 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5 1.1-8.3 1.3h-.6000000000000001c-1.3.1-2.7.1-4.2.1-1.6 0-3.1 0-4.9-.1 6 6.8 15.4 10.8 27.2 10.8 25 0 46.2-11.1 55.5-35.9 6.7.7 13.1-1 16-6.7-4.5-2.7-10.5-1.8-13.9-.1z"
|
||||
/><path
|
||||
fill="#28B8EB"
|
||||
d="M110.4 55.1c.8-5.9-3.6-10.5-6.4-12.7-3.1 3.6-3.6 13.2 1.3 17.2-2.8 2.4-8.5 4.7-14.5 4.7h-68c-.3 9.5 3.2 16.7 9.5 21 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.5 1.4l-.1-.1c8.5 4.4 20.8 4.3 35-1.1 15.8-6.1 30.6-17.7 40.9-30.9-.2.1-.4.1-.5.2z"
|
||||
/><path
|
||||
fill="#028BB8"
|
||||
d="M18.7 71.8c.4 3.3 1.4 6.4 2.9 9.3l.8 1.5c.5.9 1.1 1.7 1.7 2.6 3 .2 5.7.3 8.2.2 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.5 1.4h-.4c-1.3.1-2.7.1-4.1.1-1.6 0-3.2 0-4.9-.1 6 6.8 15.5 10.8 27.3 10.8 21.4 0 40-8.1 50.8-26h-85.1v-.1z"
|
||||
/><path
|
||||
fill="#019BC6"
|
||||
d="M23.5 71.8c1.3 5.8 4.3 10.4 8.8 13.5 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.6 1.4 8.5 4.4 20.8 4.3 34.9-1.1 8.5-3.3 16.8-8.2 24.2-14.1h-70.6z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#00ACD3"
|
||||
d="M28.4 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zM39.6 41.5h9.8v9.8h-9.8v-9.8zm.9.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#23C2EE"
|
||||
d="M39.6 52.7h9.8v9.8h-9.8v-9.8zm.9.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#00ACD3"
|
||||
d="M50.9 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#23C2EE"
|
||||
d="M50.9 41.5h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zM62.2 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#00ACD3"
|
||||
d="M62.2 41.5h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#23C2EE"
|
||||
d="M62.2 30.2h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#00ACD3"
|
||||
d="M73.5 52.7h9.8v9.8h-9.8v-9.8zm.8.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#D4EEF1"
|
||||
d="M48.8 78.3c1.5 0 2.7 1.2 2.7 2.7 0 1.5-1.2 2.7-2.7 2.7-1.5 0-2.7-1.2-2.7-2.7 0-1.5 1.2-2.7 2.7-2.7"
|
||||
/><path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fill="#3A4D54"
|
||||
d="M48.8 79.1c.2 0 .5 0 .7.1-.2.1-.4.4-.4.7 0 .4.4.8.8.8.3 0 .6-.2.7-.4.1.2.1.5.1.7 0 1.1-.9 1.9-1.9 1.9-1.1 0-1.9-.9-1.9-1.9 0-1 .8-1.9 1.9-1.9M1.1 72.8h125.4c-2.7-.7-8.6-1.6-7.7-5.2-5 5.7-16.9 4-20 1.2-3.4 4.9-23 3-24.3-.8-4.2 5-17.3 5-21.5 0-1.4 3.8-21 5.7-24.3.8-3 2.8-15 4.5-20-1.2 1.1 3.5-4.9 4.5-7.6 5.2"
|
||||
/><path
|
||||
fill="#BFDBE0"
|
||||
d="M56 97.8c-6.7-3.2-10.3-7.5-12.4-12.2-2.5.7-5.5 1.2-8.9 1.4-1.3.1-2.7.1-4.1.1-1.7 0-3.4 0-5.2-.1 6 6 13.6 10.7 27.5 10.8h3.1z"
|
||||
/><path
|
||||
fill="#D4EEF1"
|
||||
d="M46.1 89.9c-.9-1.3-1.8-2.8-2.5-4.3-2.5.7-5.5 1.2-8.9 1.4 2.3 1.2 5.7 2.4 11.4 2.9z"
|
||||
/></g
|
||||
>
|
||||
</svg>
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
|
||||
<svg viewBox="0 0 128 128" class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-12 w-12' : 'mx-auto w-10 h-10'}>
|
||||
<path d="M124.8 52.1c-4.3-2.5-10-2.8-14.8-1.4-.6-5.2-4-9.7-8-12.9l-1.6-1.3-1.4 1.6c-2.7 3.1-3.5 8.3-3.1 12.3.3 2.9 1.2 5.9 3 8.3-1.4.8-2.9 1.9-4.3 2.4-2.8 1-5.9 2-8.9 2H79V49H66V24H51v12H26v13H13v14H1.8l-.2 1.5c-.5 6.4.3 12.6 3 18.5l1.1 2.2.1.2c7.9 13.4 21.7 19 36.8 19 29.2 0 53.3-13.1 64.3-40.6 7.4.4 15-1.8 18.6-8.9l.9-1.8-1.6-1zM28 39h10v11H28V39zm13.1 44.2c0 1.7-1.4 3.1-3.1 3.1-1.7 0-3.1-1.4-3.1-3.1 0-1.7 1.4-3.1 3.1-3.1 1.7.1 3.1 1.4 3.1 3.1zM28 52h10v11H28V52zm-13 0h11v11H15V52zm27.7 50.2c-15.8-.1-24.3-5.4-31.3-12.4 2.1.1 4.1.2 5.9.2 1.6 0 3.2 0 4.7-.1 3.9-.2 7.3-.7 10.1-1.5 2.3 5.3 6.5 10.2 14 13.8h-3.4zM51 63H40V52h11v11zm0-13H40V39h11v11zm13 13H53V52h11v11zm0-13H53V39h11v11zm0-13H53V26h11v11zm13 26H66V52h11v11zM38.8 81.2c-.2-.1-.5-.2-.8-.2-1.2 0-2.2 1-2.2 2.2 0 1.2 1 2.2 2.2 2.2s2.2-1 2.2-2.2c0-.3-.1-.6-.2-.8-.2.3-.4.5-.8.5-.5 0-.9-.4-.9-.9.1-.4.3-.7.5-.8z" fill="#019BC6"></path>
|
||||
</svg>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,4 +1,11 @@
|
||||
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-8 h-16 w-16">
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
viewBox="0 0 128 128"
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-8 h-16 w-16' : 'mx-auto w-8 h-8'}
|
||||
>
|
||||
<path fill="transparent" d="M18 0h92v128H18z" /><path
|
||||
d="M55.3 36.3h.4c1.1 0 1.5.9 1.5 2.3v41.8c0 1.8-.4 3-1.6 3l-4.8-.1c-1.2 0-1.6-1-1.6-3V45.5l-2.1.5c-1 0-1.5-.8-1.5-2.2v-3c0-1.2.4-2 1.4-2.2l8.3-2.2zm16 36.1l.1 3 .6 1.3.6.6.8.1h2.2c1 0 1.7.8 1.7 2v1.9c0 1.2-.6 2-1.8 2h-3.2l-2.3-.1c-.7-.2-1.4-.5-2.2-1a5.7 5.7 0 01-2-1.9c-.4-.8-.8-1.9-1-3.2-.4-1.4-.5-3-.5-4.8v-16h-1.5c-1.1 0-1.6-1-1.6-2.4v-1.7c0-1.4.5-2.3 1.6-2.3h1.5v-.1l.6-12.3c0-1.5.5-2.5 1.6-2.5h3.1c1.2 0 1.6 1 1.6 2.5v12.3h3.6c1.1 0 1.6 1 1.6 2.4V54c0 1.4-.5 2.3-1.6 2.3h-3.6v16.2zm9.4 15.7c0-2 .3-3.2 1.5-3.2.2 0 .4 0 1.4.4l1.1.3.2-.1.4-.7c.3-.6.4-1.6.4-3l-.6-3.3L79 52.7v-.9c0-1.2.3-2 1.2-2H84c.5 0 1 .3 1.3.6.3.4.5.9.6 1.6l3.4 18 2.6-17.8c.1-.8.3-1.3.6-1.7.3-.4.8-.6 1.3-.6h2.6c1 0 1.4.8 1.4 2l-.1.8L92 82.2c-.5 2.5-1 4.4-1.5 5.7a6.6 6.6 0 01-2 3c-.9.6-1.9.9-3.1.9h-.3c-2 0-3.3-.6-4.1-1.7-.3-.4-.4-1-.4-2zM31.3 38.8l8.2-2.1h.5c1 0 1.4.8 1.4 2.2v41.9c0 1.8-.4 2.9-1.6 2.9h-4.7c-1.2 0-1.6-1.1-1.6-3v-35l-2 .6c-1.2 0-1.6-.9-1.6-2.2v-3c0-1.2.4-2 1.4-2.3z"
|
||||
fill="#FFF"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,4 +1,11 @@
|
||||
<svg class="absolute top-0 left-0 -m-4 h-10 w-10" viewBox="0 0 128 128">
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<path
|
||||
fill="#64328B"
|
||||
d="M64,0C28.7,0,0,28.7,0,64v0c0,35.3,28.7,64,64,64s64-28.7,64-64v0C128,28.7,99.3,0,64,0z M13.2,64L64,114.8 C35.9,114.8,13.2,92.1,13.2,64z M75.4,113.5l-60.9-61C19.7,30,39.9,13.2,64,13.2c16.6,0,31.3,7.9,40.5,20.2l-7.5,7.2 C89.7,30.2,77.7,23.5,64,23.5c-17.6,0-32.5,11.2-38.1,26.8C33.1,57,75.4,98.8,78.1,102c12.7-4.7,22.3-15.5,25.4-28.9H81.9v-9.4 l33,0.2C114.8,88.2,98,108.4,75.4,113.5z"
|
||||
|
||||
|
Before Width: | Height: | Size: 494 B After Width: | Height: | Size: 593 B |
15
apps/ui/src/lib/components/svg/applications/Heroku.svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8 '}
|
||||
viewBox="0 0 72 80"
|
||||
>
|
||||
<path
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#430098"
|
||||
d="M64.8,0 L7.2,0 C3.224,0 0,3.224 0,7.2 L0,72.8 C0,76.776 3.224,80 7.2,80 L64.8,80 C68.776,80 72,76.776 72,72.8 L72,7.2 C72,3.224 68.776,0 64.8,0 Z M18,68 L18,52 L27,60 L18,68 Z M46,68 L46,44.11 C45.961,42.243 45.062,40 41,40 C32.866,40 23.742,44.091 23.651,44.132 L18,46.692 L18,12 L26,12 L26,34.711 C29.994,33.411 35.577,32 41,32 C45.945,32 48.905,33.944 50.517,35.575 C53.958,39.055 54.005,43.488 54.0002258,44 L54.0002258,68 L46,68 Z M48,25 L40,25 C43.144,20.875 45.118,16.534 46,12 L54,12 C53.46,16.544 51.618,20.9 48,25 Z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class="absolute top-0 left-0 -m-4 h-10 w-10"
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||
viewBox="0 0 50 52"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><title>Logomark</title><path
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -1,4 +1,13 @@
|
||||
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 fill-current text-blue-500" viewBox="0 0 128 128">
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute
|
||||
? 'absolute top-0 left-0 -m-4 h-10 w-10 fill-current text-blue-500'
|
||||
: 'mx-auto w-8 h-8 fill-current text-blue-500'}
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<path
|
||||
d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64c11.2 0 21.7-2.9 30.8-7.9L48.4 55.3v36.6h-6.8V41.8h6.8l50.5 75.8C116.4 106.2 128 86.5 128 64c0-35.3-28.7-64-64-64zm22.1 84.6l-7.5-11.3V41.8h7.5v42.8z"
|
||||
/>
|
||||
|
||||
|
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 442 B |
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class="absolute top-0 left-0 -m-4 h-10 w-10 text-green-500"
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10 text-green-500' : 'mx-auto w-8 h-8 text-green-500'}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class="absolute top-0 left-0 -m-4 h-10 w-10"
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 400 400"
|
||||
>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,4 +1,13 @@
|
||||
<svg viewBox="0 0 128 128" class="absolute top-0 left-0 -m-6 h-14 w-14 text-white">
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
viewBox="0 0 128 128"
|
||||
class={isAbsolute
|
||||
? 'absolute top-0 left-0 -m-6 h-14 w-14 text-white'
|
||||
: 'mx-auto w-8 h-8 text-white'}
|
||||
>
|
||||
<path
|
||||
fill="#6181B6"
|
||||
d="M64 33.039c-33.74 0-61.094 13.862-61.094 30.961s27.354 30.961 61.094 30.961 61.094-13.862 61.094-30.961-27.354-30.961-61.094-30.961zm-15.897 36.993c-1.458 1.364-3.077 1.927-4.86 2.507-1.783.581-4.052.461-6.811.461h-6.253l-1.733 10h-7.301l6.515-34h14.04c4.224 0 7.305 1.215 9.242 3.432 1.937 2.217 2.519 5.364 1.747 9.337-.319 1.637-.856 3.159-1.614 4.515-.759 1.357-1.75 2.624-2.972 3.748zm21.311 2.968l2.881-14.42c.328-1.688.208-2.942-.361-3.555-.57-.614-1.782-1.025-3.635-1.025h-5.79l-3.731 19h-7.244l6.515-33h7.244l-1.732 9h6.453c4.061 0 6.861.815 8.402 2.231s2.003 3.356 1.387 6.528l-3.031 15.241h-7.358zm40.259-11.178c-.318 1.637-.856 3.133-1.613 4.488-.758 1.357-1.748 2.598-2.971 3.722-1.458 1.364-3.078 1.927-4.86 2.507-1.782.581-4.053.461-6.812.461h-6.253l-1.732 10h-7.301l6.514-34h14.041c4.224 0 7.305 1.215 9.241 3.432 1.935 2.217 2.518 5.418 1.746 9.39zM95.919 54h-5.001l-2.727 14h4.442c2.942 0 5.136-.29 6.576-1.4 1.442-1.108 2.413-2.828 2.918-5.421.484-2.491.264-4.434-.66-5.458-.925-1.024-2.774-1.721-5.548-1.721zM38.934 54h-5.002l-2.727 14h4.441c2.943 0 5.136-.29 6.577-1.4 1.441-1.108 2.413-2.828 2.917-5.421.484-2.491.264-4.434-.66-5.458s-2.772-1.721-5.546-1.721z"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,4 +1,11 @@
|
||||
<svg class="absolute top-0 left-0 -m-6 h-14 w-14" viewBox="0 0 128 128">
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-6 h-14 w-14' : 'mx-auto w-8 h-8'}
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<linearGradient
|
||||
id="a"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -1,4 +1,13 @@
|
||||
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 text-blue-500" viewBox="0 0 128 128">
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute
|
||||
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-blue-500'
|
||||
: 'mx-auto w-8 h-8 text-blue-500'}
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<g fill="#61DAFB"
|
||||
><circle cx="64" cy="64" r="11.4" /><path
|
||||
d="M107.3 45.2c-2.2-.8-4.5-1.6-6.9-2.3.6-2.4 1.1-4.8 1.5-7.1 2.1-13.2-.2-22.5-6.6-26.1-1.9-1.1-4-1.6-6.4-1.6-7 0-15.9 5.2-24.9 13.9-9-8.7-17.9-13.9-24.9-13.9-2.4 0-4.5.5-6.4 1.6-6.4 3.7-8.7 13-6.6 26.1.4 2.3.9 4.7 1.5 7.1-2.4.7-4.7 1.4-6.9 2.3-12.5 4.8-19.3 11.4-19.3 18.8s6.9 14 19.3 18.8c2.2.8 4.5 1.6 6.9 2.3-.6 2.4-1.1 4.8-1.5 7.1-2.1 13.2.2 22.5 6.6 26.1 1.9 1.1 4 1.6 6.4 1.6 7.1 0 16-5.2 24.9-13.9 9 8.7 17.9 13.9 24.9 13.9 2.4 0 4.5-.5 6.4-1.6 6.4-3.7 8.7-13 6.6-26.1-.4-2.3-.9-4.7-1.5-7.1 2.4-.7 4.7-1.4 6.9-2.3 12.5-4.8 19.3-11.4 19.3-18.8s-6.8-14-19.3-18.8zm-14.8-30.5c4.1 2.4 5.5 9.8 3.8 20.3-.3 2.1-.8 4.3-1.4 6.6-5.2-1.2-10.7-2-16.5-2.5-3.4-4.8-6.9-9.1-10.4-13 7.4-7.3 14.9-12.3 21-12.3 1.3 0 2.5.3 3.5.9zm-11.2 59.3c-1.8 3.2-3.9 6.4-6.1 9.6-3.7.3-7.4.4-11.2.4-3.9 0-7.6-.1-11.2-.4-2.2-3.2-4.2-6.4-6-9.6-1.9-3.3-3.7-6.7-5.3-10 1.6-3.3 3.4-6.7 5.3-10 1.8-3.2 3.9-6.4 6.1-9.6 3.7-.3 7.4-.4 11.2-.4 3.9 0 7.6.1 11.2.4 2.2 3.2 4.2 6.4 6 9.6 1.9 3.3 3.7 6.7 5.3 10-1.7 3.3-3.4 6.6-5.3 10zm8.3-3.3c1.5 3.5 2.7 6.9 3.8 10.3-3.4.8-7 1.4-10.8 1.9 1.2-1.9 2.5-3.9 3.6-6 1.2-2.1 2.3-4.2 3.4-6.2zm-25.6 27.1c-2.4-2.6-4.7-5.4-6.9-8.3 2.3.1 4.6.2 6.9.2 2.3 0 4.6-.1 6.9-.2-2.2 2.9-4.5 5.7-6.9 8.3zm-18.6-15c-3.8-.5-7.4-1.1-10.8-1.9 1.1-3.3 2.3-6.8 3.8-10.3 1.1 2 2.2 4.1 3.4 6.1 1.2 2.2 2.4 4.1 3.6 6.1zm-7-25.5c-1.5-3.5-2.7-6.9-3.8-10.3 3.4-.8 7-1.4 10.8-1.9-1.2 1.9-2.5 3.9-3.6 6-1.2 2.1-2.3 4.2-3.4 6.2zm25.6-27.1c2.4 2.6 4.7 5.4 6.9 8.3-2.3-.1-4.6-.2-6.9-.2-2.3 0-4.6.1-6.9.2 2.2-2.9 4.5-5.7 6.9-8.3zm22.2 21l-3.6-6c3.8.5 7.4 1.1 10.8 1.9-1.1 3.3-2.3 6.8-3.8 10.3-1.1-2.1-2.2-4.2-3.4-6.2zm-54.5-16.2c-1.7-10.5-.3-17.9 3.8-20.3 1-.6 2.2-.9 3.5-.9 6 0 13.5 4.9 21 12.3-3.5 3.8-7 8.2-10.4 13-5.8.5-11.3 1.4-16.5 2.5-.6-2.3-1-4.5-1.4-6.6zm-24.7 29c0-4.7 5.7-9.7 15.7-13.4 2-.8 4.2-1.5 6.4-2.1 1.6 5 3.6 10.3 6 15.6-2.4 5.3-4.5 10.5-6 15.5-13.8-4-22.1-10-22.1-15.6zm28.5 49.3c-4.1-2.4-5.5-9.8-3.8-20.3.3-2.1.8-4.3 1.4-6.6 5.2 1.2 10.7 2 16.5 2.5 3.4 4.8 6.9 9.1 10.4 13-7.4 7.3-14.9 12.3-21 12.3-1.3 0-2.5-.3-3.5-.9zm60.8-20.3c1.7 10.5.3 17.9-3.8 20.3-1 .6-2.2.9-3.5.9-6 0-13.5-4.9-21-12.3 3.5-3.8 7-8.2 10.4-13 5.8-.5 11.3-1.4 16.5-2.5.6 2.3 1 4.5 1.4 6.6zm9-15.6c-2 .8-4.2 1.5-6.4 2.1-1.6-5-3.6-10.3-6-15.6 2.4-5.3 4.5-10.5 6-15.5 13.8 4 22.1 10 22.1 15.6 0 4.7-5.8 9.7-15.7 13.4z"
|
||||
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
@@ -1,5 +1,11 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class="absolute top-0 left-0 -m-4 h-10 w-10 text-white"
|
||||
class={isAbsolute
|
||||
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-white'
|
||||
: 'mx-auto w-8 h-8 text-white'}
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class="absolute top-0 left-0 -m-4 h-10 w-10"
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,4 +1,13 @@
|
||||
<svg class="absolute top-0 left-0 -m-4 h-10 w-10 text-green-500" viewBox="0 0 128 128">
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute
|
||||
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-green-500'
|
||||
: 'mx-auto w-8 h-8 text-green-500'}
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<path
|
||||
d="m-2.3125e-8 8.9337 49.854 0.1586 14.167 24.47 14.432-24.47 49.547-0.1577-63.834 110.14zm126.98 0.6374-24.36 0.0207-38.476 66.052-38.453-66.052-24.749-0.0194 63.211 107.89zm-25.149-0.008-22.745 0.16758l-15.053 24.647-14.817-24.647-22.794-0.1679 37.731 64.476zM25.997 9.3929l23.002 0.0087M25.997 9.3929l23.002 0.0087"
|
||||
fill="none"
|
||||
|
||||
|
Before Width: | Height: | Size: 676 B After Width: | Height: | Size: 794 B |
19
apps/ui/src/lib/components/svg/applications/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
//@ts-nocheck
|
||||
export { default as Rust } from './Rust.svelte';
|
||||
export { default as Nodejs } from './Nodejs.svelte';
|
||||
export { default as React } from './React.svelte';
|
||||
export { default as Svelte } from './Svelte.svelte';
|
||||
export { default as Vuejs } from './Vuejs.svelte';
|
||||
export { default as Php } from './PHP.svelte';
|
||||
export { default as Python } from './Python.svelte';
|
||||
export { default as Static } from './Static.svelte';
|
||||
export { default as Nestjs } from './Nestjs.svelte';
|
||||
export { default as Nuxtjs } from './Nuxtjs.svelte';
|
||||
export { default as Nextjs } from './Nextjs.svelte';
|
||||
export { default as Gatsby } from './Gatsby.svelte';
|
||||
export { default as Docker } from './Docker.svelte';
|
||||
export { default as Astro } from './Astro.svelte';
|
||||
export { default as Eleventy } from './Eleventy.svelte';
|
||||
export { default as Deno } from './Deno.svelte';
|
||||
export { default as Laravel } from './Laravel.svelte';
|
||||
export { default as Heroku } from './Heroku.svelte';
|
||||