mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 12:33:35 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31fdbdf8c9 | ||
|
|
2741d0ab2a | ||
|
|
79b0187b58 | ||
|
|
bf5659d0e2 | ||
|
|
f6314cab69 | ||
|
|
4f5fe3d383 | ||
|
|
1c720d587c | ||
|
|
c46dc99224 | ||
|
|
5e43d4f20d | ||
|
|
359434bfd3 | ||
|
|
e755a2d4ec | ||
|
|
0b416cd03e | ||
|
|
857e0f251b | ||
|
|
f040c7c742 | ||
|
|
2e82c9d312 | ||
|
|
126923c33e | ||
|
|
26528d8bec | ||
|
|
8c30472472 | ||
|
|
2962aa6166 | ||
|
|
d80f760c92 | ||
|
|
ce2c887469 | ||
|
|
4908463722 | ||
|
|
26d0ef9ac9 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
.pnpm-store
|
||||||
build
|
build
|
||||||
.svelte-kit
|
.svelte-kit
|
||||||
package
|
package
|
||||||
|
|||||||
2
.gitpod.Dockerfile
vendored
2
.gitpod.Dockerfile
vendored
@@ -1,2 +1,2 @@
|
|||||||
FROM gitpod/workspace-node:2022-06-20-19-54-55
|
FROM gitpod/workspace-full:2022-08-17-18-37-55
|
||||||
RUN brew install buildpacks/tap/pack
|
RUN brew install buildpacks/tap/pack
|
||||||
10
.gitpod.yml
10
.gitpod.yml
@@ -1,11 +1,11 @@
|
|||||||
# This configuration file was automatically generated by Gitpod.
|
# This configuration file was automatically generated by Gitpod.
|
||||||
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
||||||
# and commit this file to your remote git repository to share the goodness with others.
|
# and commit this file to your remote git repository to share the goodness with others.
|
||||||
image:
|
#image:
|
||||||
file: .gitpod.Dockerfile
|
# file: .gitpod.Dockerfile
|
||||||
tasks:
|
#tasks:
|
||||||
- init: pnpm install && pnpm db:push && pnpm db:seed
|
# - init: pnpm install && pnpm db:push && pnpm db:seed
|
||||||
command: pnpm dev
|
# command: pnpm dev
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- port: 3001
|
- port: 3001
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ Deploy your resource to:
|
|||||||
- [Umami](https://github.com/mikecao/umami)
|
- [Umami](https://github.com/mikecao/umami)
|
||||||
- [Fider](https://fider.io)
|
- [Fider](https://fider.io)
|
||||||
- [Hasura](https://hasura.io)
|
- [Hasura](https://hasura.io)
|
||||||
|
- [GlitchTip](https://glitchtip.com)
|
||||||
|
|
||||||
## Migration from v1
|
## Migration from v1
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@breejs/ts-worker": "2.0.0",
|
"@breejs/ts-worker": "2.0.0",
|
||||||
"@fastify/autoload": "5.2.0",
|
"@fastify/autoload": "5.2.0",
|
||||||
"@fastify/cookie": "7.3.1",
|
"@fastify/cookie": "8.0.0",
|
||||||
"@fastify/cors": "8.1.0",
|
"@fastify/cors": "8.1.0",
|
||||||
"@fastify/env": "4.1.0",
|
"@fastify/env": "4.1.0",
|
||||||
"@fastify/jwt": "6.3.2",
|
"@fastify/jwt": "6.3.2",
|
||||||
@@ -29,13 +29,13 @@
|
|||||||
"cabin": "9.1.2",
|
"cabin": "9.1.2",
|
||||||
"compare-versions": "4.1.3",
|
"compare-versions": "4.1.3",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
"dayjs": "1.11.4",
|
"dayjs": "1.11.5",
|
||||||
"dockerode": "3.3.3",
|
"dockerode": "3.3.4",
|
||||||
"dotenv-extended": "2.9.0",
|
"dotenv-extended": "2.9.0",
|
||||||
"fastify": "4.4.0",
|
"execa": "6.1.0",
|
||||||
"fastify-plugin": "4.1.0",
|
"fastify": "4.5.2",
|
||||||
|
"fastify-plugin": "4.2.0",
|
||||||
"generate-password": "1.7.0",
|
"generate-password": "1.7.0",
|
||||||
"get-port": "6.1.2",
|
|
||||||
"got": "12.3.1",
|
"got": "12.3.1",
|
||||||
"is-ip": "5.0.0",
|
"is-ip": "5.0.0",
|
||||||
"is-port-reachable": "4.0.0",
|
"is-port-reachable": "4.0.0",
|
||||||
@@ -50,12 +50,12 @@
|
|||||||
"unique-names-generator": "4.7.1"
|
"unique-names-generator": "4.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "18.6.5",
|
"@types/node": "18.7.11",
|
||||||
"@types/node-os-utils": "1.3.0",
|
"@types/node-os-utils": "1.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "5.33.0",
|
"@typescript-eslint/eslint-plugin": "5.34.0",
|
||||||
"@typescript-eslint/parser": "5.33.0",
|
"@typescript-eslint/parser": "5.34.0",
|
||||||
"esbuild": "0.15.0",
|
"esbuild": "0.15.5",
|
||||||
"eslint": "8.21.0",
|
"eslint": "8.22.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"nodemon": "2.0.19",
|
"nodemon": "2.0.19",
|
||||||
|
|||||||
@@ -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,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");
|
||||||
@@ -316,33 +316,34 @@ model DatabaseSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Service {
|
model Service {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
fqdn String?
|
fqdn String?
|
||||||
exposePort Int?
|
exposePort Int?
|
||||||
dualCerts Boolean @default(false)
|
dualCerts Boolean @default(false)
|
||||||
type String?
|
type String?
|
||||||
version String?
|
version String?
|
||||||
destinationDockerId String?
|
destinationDockerId String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
|
persistentStorage ServicePersistentStorage[]
|
||||||
|
serviceSecret ServiceSecret[]
|
||||||
|
teams Team[]
|
||||||
|
|
||||||
fider Fider?
|
fider Fider?
|
||||||
ghost Ghost?
|
ghost Ghost?
|
||||||
|
glitchTip GlitchTip?
|
||||||
hasura Hasura?
|
hasura Hasura?
|
||||||
meiliSearch MeiliSearch?
|
meiliSearch MeiliSearch?
|
||||||
minio Minio?
|
minio Minio?
|
||||||
moodle Moodle?
|
moodle Moodle?
|
||||||
plausibleAnalytics PlausibleAnalytics?
|
plausibleAnalytics PlausibleAnalytics?
|
||||||
persistentStorage ServicePersistentStorage[]
|
|
||||||
serviceSecret ServiceSecret[]
|
|
||||||
umami Umami?
|
umami Umami?
|
||||||
vscodeserver Vscodeserver?
|
vscodeserver Vscodeserver?
|
||||||
wordpress Wordpress?
|
wordpress Wordpress?
|
||||||
appwrite Appwrite?
|
appwrite Appwrite?
|
||||||
|
searxng Searxng?
|
||||||
teams Team[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model PlausibleAnalytics {
|
model PlausibleAnalytics {
|
||||||
@@ -517,3 +518,40 @@ model Appwrite {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
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])
|
||||||
|
}
|
||||||
|
|||||||
@@ -541,9 +541,6 @@ export async function buildImage({
|
|||||||
} else {
|
} else {
|
||||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
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 - but will be soon! You will see the full build log when the build is finished!\n###############`, buildId, applicationId });
|
|
||||||
}
|
|
||||||
if (!debug && isCache) {
|
if (!debug && isCache) {
|
||||||
await saveBuildLog({
|
await saveBuildLog({
|
||||||
line: `Debug turned off. To see more details, allow it in the configuration.`,
|
line: `Debug turned off. To see more details, allow it in the configuration.`,
|
||||||
@@ -553,54 +550,7 @@ export async function buildImage({
|
|||||||
}
|
}
|
||||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
||||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||||
const { stderr } = await executeDockerCmd({ dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` })
|
await executeDockerCmd({ debug, buildId, applicationId, 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 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) {
|
if (isCache) {
|
||||||
await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import child from 'child_process';
|
import { exec } from 'node:child_process'
|
||||||
import util from 'util';
|
import util from 'util';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
@@ -16,8 +16,9 @@ import sshConfig from 'ssh-config'
|
|||||||
import { checkContainer, removeContainer } from './docker';
|
import { checkContainer, removeContainer } from './docker';
|
||||||
import { day } from './dayjs';
|
import { day } from './dayjs';
|
||||||
import * as serviceFields from './serviceFields'
|
import * as serviceFields from './serviceFields'
|
||||||
|
import { saveBuildLog } from './buildPacks/common';
|
||||||
|
|
||||||
export const version = '3.6.0';
|
export const version = '3.8.0';
|
||||||
export const isDev = process.env.NODE_ENV === 'development';
|
export const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
const algorithm = 'aes-256-ctr';
|
const algorithm = 'aes-256-ctr';
|
||||||
@@ -79,11 +80,58 @@ export const include: any = {
|
|||||||
hasura: true,
|
hasura: true,
|
||||||
fider: true,
|
fider: true,
|
||||||
moodle: true,
|
moodle: true,
|
||||||
appwrite: true
|
appwrite: true,
|
||||||
|
glitchTip: true,
|
||||||
|
searxng: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
|
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
|
||||||
export const asyncExecShell = util.promisify(child.exec);
|
export const asyncExecShell = util.promisify(exec);
|
||||||
|
export const asyncExecShellStream = async ({ debug, buildId, applicationId, command, engine }: { debug: boolean, buildId: string, applicationId: string, command: string, engine: string }) => {
|
||||||
|
return await new Promise(async (resolve, reject) => {
|
||||||
|
const { execaCommand } = await import('execa')
|
||||||
|
const subprocess = execaCommand(command, { env: { DOCKER_BUILDKIT: "1", DOCKER_HOST: engine } })
|
||||||
|
if (debug) {
|
||||||
|
await saveBuildLog({ line: `=========================`, buildId, applicationId });
|
||||||
|
subprocess.stdout.on('data', async (data) => {
|
||||||
|
const stdout = data.toString();
|
||||||
|
const array = stdout.split('\n')
|
||||||
|
for (const line of array) {
|
||||||
|
if (line !== '\n' && line !== '') {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `${line.replace('\n', '')}`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
subprocess.stderr.on('data', async (data) => {
|
||||||
|
const stderr = data.toString();
|
||||||
|
const array = stderr.split('\n')
|
||||||
|
for (const line of array) {
|
||||||
|
if (line !== '\n' && line !== '') {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `${line.replace('\n', '')}`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
subprocess.on('exit', async (code) => {
|
||||||
|
await asyncSleep(1000);
|
||||||
|
await saveBuildLog({ line: `=========================`, buildId, applicationId });
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(code)
|
||||||
|
} else {
|
||||||
|
reject(code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const asyncSleep = (delay: number): Promise<unknown> =>
|
export const asyncSleep = (delay: number): Promise<unknown> =>
|
||||||
new Promise((resolve) => setTimeout(resolve, delay));
|
new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
export const prisma = new PrismaClient({
|
export const prisma = new PrismaClient({
|
||||||
@@ -287,7 +335,7 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
ports: {
|
ports: {
|
||||||
main: 80
|
main: 80
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
// {
|
// {
|
||||||
// name: 'moodle',
|
// name: 'moodle',
|
||||||
// fancyName: 'Moodle',
|
// fancyName: 'Moodle',
|
||||||
@@ -299,6 +347,28 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
// main: 8080
|
// 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
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
|
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
|
||||||
@@ -533,21 +603,38 @@ export const supportedDatabaseTypesAndVersions = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function getFreeSSHLocalPort(id: string): Promise<number> {
|
export async function getFreeSSHLocalPort(id: string): Promise<number | boolean> {
|
||||||
const { default: getPort, portNumbers } = await import('get-port');
|
const { default: isReachable } = await import('is-port-reachable');
|
||||||
const { remoteIpAddress, sshLocalPort } = await prisma.destinationDocker.findUnique({ where: { id } })
|
const { remoteIpAddress, sshLocalPort } = await prisma.destinationDocker.findUnique({ where: { id } })
|
||||||
if (sshLocalPort) {
|
if (sshLocalPort) {
|
||||||
return Number(sshLocalPort)
|
return Number(sshLocalPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = await prisma.setting.findFirst();
|
||||||
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
const ports = await prisma.destinationDocker.findMany({ where: { sshLocalPort: { not: null }, remoteIpAddress: { not: remoteIpAddress } } })
|
const ports = await prisma.destinationDocker.findMany({ where: { sshLocalPort: { not: null }, remoteIpAddress: { not: remoteIpAddress } } })
|
||||||
const alreadyConfigured = await prisma.destinationDocker.findFirst({ where: { remoteIpAddress, id: { not: id }, sshLocalPort: { not: null } } })
|
|
||||||
|
const alreadyConfigured = await prisma.destinationDocker.findFirst({
|
||||||
|
where: {
|
||||||
|
remoteIpAddress, id: { not: id }, sshLocalPort: { not: null }
|
||||||
|
}
|
||||||
|
})
|
||||||
if (alreadyConfigured?.sshLocalPort) {
|
if (alreadyConfigured?.sshLocalPort) {
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: alreadyConfigured.sshLocalPort } })
|
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: alreadyConfigured.sshLocalPort } })
|
||||||
return Number(alreadyConfigured.sshLocalPort)
|
return Number(alreadyConfigured.sshLocalPort)
|
||||||
}
|
}
|
||||||
const availablePort = await getPort({ port: portNumbers(10000, 10100), exclude: ports.map(p => p.sshLocalPort) })
|
const range = generateRangeArray(minPort, maxPort)
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: Number(availablePort) } })
|
console.log({ ports })
|
||||||
return Number(availablePort)
|
const availablePorts = range.filter(port => !ports.map(p => p.sshLocalPort).includes(port))
|
||||||
|
for (const port of availablePorts) {
|
||||||
|
const found = await isReachable(port, { host: 'localhost' })
|
||||||
|
if (!found) {
|
||||||
|
await prisma.destinationDocker.update({ where: { id }, data: { sshLocalPort: Number(port) } })
|
||||||
|
return Number(port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createRemoteEngineConfiguration(id: string) {
|
export async function createRemoteEngineConfiguration(id: string) {
|
||||||
@@ -579,7 +666,7 @@ export async function createRemoteEngineConfiguration(id: string) {
|
|||||||
config.append({
|
config.append({
|
||||||
Host: remoteIpAddress,
|
Host: remoteIpAddress,
|
||||||
Hostname: 'localhost',
|
Hostname: 'localhost',
|
||||||
Port: Number(localPort),
|
Port: localPort.toString(),
|
||||||
User: remoteUser,
|
User: remoteUser,
|
||||||
IdentityFile: sshKeyFile,
|
IdentityFile: sshKeyFile,
|
||||||
StrictHostKeyChecking: 'no'
|
StrictHostKeyChecking: 'no'
|
||||||
@@ -592,7 +679,7 @@ export async function createRemoteEngineConfiguration(id: string) {
|
|||||||
}
|
}
|
||||||
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config))
|
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config))
|
||||||
}
|
}
|
||||||
export async function executeDockerCmd({ dockerId, command }: { dockerId: string, command: string }) {
|
export async function executeDockerCmd({ debug, buildId, applicationId, dockerId, command }: { debug?: boolean, buildId?: string, applicationId?: string, dockerId: string, command: string }): Promise<any> {
|
||||||
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
||||||
if (remoteEngine) {
|
if (remoteEngine) {
|
||||||
await createRemoteEngineConfiguration(dockerId)
|
await createRemoteEngineConfiguration(dockerId)
|
||||||
@@ -605,6 +692,9 @@ export async function executeDockerCmd({ dockerId, command }: { dockerId: string
|
|||||||
command = command.replace(/docker compose/gi, 'docker-compose')
|
command = command.replace(/docker compose/gi, 'docker-compose')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (command.startsWith(`docker build --progress plain`)) {
|
||||||
|
return await asyncExecShellStream({ debug, buildId, applicationId, command, engine });
|
||||||
|
}
|
||||||
return await asyncExecShell(
|
return await asyncExecShell(
|
||||||
`DOCKER_BUILDKIT=1 DOCKER_HOST="${engine}" ${command}`
|
`DOCKER_BUILDKIT=1 DOCKER_HOST="${engine}" ${command}`
|
||||||
);
|
);
|
||||||
@@ -724,13 +814,18 @@ export async function listSettings(): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function generatePassword(length = 24, symbols = false): string {
|
export function generatePassword({ length = 24, symbols = false, isHex = false }: { length?: number, symbols?: boolean, isHex?: boolean } | null): string {
|
||||||
return generator.generate({
|
if (isHex) {
|
||||||
|
return crypto.randomBytes(length).toString("hex");
|
||||||
|
}
|
||||||
|
const password = generator.generate({
|
||||||
length,
|
length,
|
||||||
numbers: true,
|
numbers: true,
|
||||||
strict: true,
|
strict: true,
|
||||||
symbols
|
symbols
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateDatabaseConfiguration(database: any, arch: string):
|
export function generateDatabaseConfiguration(database: any, arch: string):
|
||||||
@@ -1196,7 +1291,7 @@ export async function checkExposedPort({ id, configuredPort, exposePort, dockerI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress) {
|
export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress) {
|
||||||
const { default: getPort } = await import('get-port');
|
const { default: checkPort } = await import('is-port-reachable');
|
||||||
const applicationUsed = await (
|
const applicationUsed = await (
|
||||||
await prisma.application.findMany({
|
await prisma.application.findMany({
|
||||||
where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
||||||
@@ -1210,22 +1305,23 @@ export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddre
|
|||||||
})
|
})
|
||||||
).map((a) => a.exposePort);
|
).map((a) => a.exposePort);
|
||||||
const usedPorts = [...applicationUsed, ...serviceUsed];
|
const usedPorts = [...applicationUsed, ...serviceUsed];
|
||||||
if (remoteIpAddress) {
|
if (usedPorts.includes(exposePort)) {
|
||||||
const { default: checkPort } = await import('is-port-reachable');
|
|
||||||
const found = await checkPort(exposePort, { host: remoteIpAddress });
|
|
||||||
if (!found) {
|
|
||||||
return exposePort
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return await getPort({ port: Number(exposePort), exclude: usedPorts });
|
const found = await checkPort(exposePort, { host: remoteIpAddress || 'localhost' });
|
||||||
|
if (!found) {
|
||||||
|
return exposePort
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
export function generateRangeArray(start, end) {
|
||||||
|
return Array.from({ length: (end - start) }, (v, k) => k + start);
|
||||||
|
}
|
||||||
export async function getFreePublicPort(id, dockerId) {
|
export async function getFreePublicPort(id, dockerId) {
|
||||||
const { default: getPort, portNumbers } = await import('get-port');
|
const { default: isReachable } = await import('is-port-reachable');
|
||||||
const data = await prisma.setting.findFirst();
|
const data = await prisma.setting.findFirst();
|
||||||
const { minPort, maxPort } = data;
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
const dbUsed = await (
|
const dbUsed = await (
|
||||||
await prisma.database.findMany({
|
await prisma.database.findMany({
|
||||||
where: { publicPort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
where: { publicPort: { not: null }, id: { not: id }, destinationDockerId: dockerId },
|
||||||
@@ -1251,7 +1347,15 @@ export async function getFreePublicPort(id, dockerId) {
|
|||||||
})
|
})
|
||||||
).map((a) => a.publicPort);
|
).map((a) => a.publicPort);
|
||||||
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed];
|
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed];
|
||||||
return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
|
const range = generateRangeArray(minPort, maxPort)
|
||||||
|
const availablePorts = range.filter(port => !usedPorts.includes(port))
|
||||||
|
for (const port of availablePorts) {
|
||||||
|
const found = await isReachable(port, { host: 'localhost' })
|
||||||
|
if (!found) {
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startTraefikTCPProxy(
|
export async function startTraefikTCPProxy(
|
||||||
@@ -1380,11 +1484,11 @@ export async function configureServiceType({
|
|||||||
type: string;
|
type: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
if (type === 'plausibleanalytics') {
|
if (type === 'plausibleanalytics') {
|
||||||
const password = encrypt(generatePassword());
|
const password = encrypt(generatePassword({}));
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'plausibleanalytics';
|
const postgresqlDatabase = 'plausibleanalytics';
|
||||||
const secretKeyBase = encrypt(generatePassword(64));
|
const secretKeyBase = encrypt(generatePassword({ length: 64 }));
|
||||||
|
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -1408,22 +1512,22 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'minio') {
|
} else if (type === 'minio') {
|
||||||
const rootUser = cuid();
|
const rootUser = cuid();
|
||||||
const rootUserPassword = encrypt(generatePassword());
|
const rootUserPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { type, minio: { create: { rootUser, rootUserPassword } } }
|
data: { type, minio: { create: { rootUser, rootUserPassword } } }
|
||||||
});
|
});
|
||||||
} else if (type === 'vscodeserver') {
|
} else if (type === 'vscodeserver') {
|
||||||
const password = encrypt(generatePassword());
|
const password = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { type, vscodeserver: { create: { password } } }
|
data: { type, vscodeserver: { create: { password } } }
|
||||||
});
|
});
|
||||||
} else if (type === 'wordpress') {
|
} else if (type === 'wordpress') {
|
||||||
const mysqlUser = cuid();
|
const mysqlUser = cuid();
|
||||||
const mysqlPassword = encrypt(generatePassword());
|
const mysqlPassword = encrypt(generatePassword({}));
|
||||||
const mysqlRootUser = cuid();
|
const mysqlRootUser = cuid();
|
||||||
const mysqlRootUserPassword = encrypt(generatePassword());
|
const mysqlRootUserPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1461,11 +1565,11 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'ghost') {
|
} else if (type === 'ghost') {
|
||||||
const defaultEmail = `${cuid()}@example.com`;
|
const defaultEmail = `${cuid()}@example.com`;
|
||||||
const defaultPassword = encrypt(generatePassword());
|
const defaultPassword = encrypt(generatePassword({}));
|
||||||
const mariadbUser = cuid();
|
const mariadbUser = cuid();
|
||||||
const mariadbPassword = encrypt(generatePassword());
|
const mariadbPassword = encrypt(generatePassword({}));
|
||||||
const mariadbRootUser = cuid();
|
const mariadbRootUser = cuid();
|
||||||
const mariadbRootUserPassword = encrypt(generatePassword());
|
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||||
|
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -1484,7 +1588,7 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'meilisearch') {
|
} else if (type === 'meilisearch') {
|
||||||
const masterKey = encrypt(generatePassword(32));
|
const masterKey = encrypt(generatePassword({ length: 32 }));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1493,11 +1597,11 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'umami') {
|
} else if (type === 'umami') {
|
||||||
const umamiAdminPassword = encrypt(generatePassword());
|
const umamiAdminPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'umami';
|
const postgresqlDatabase = 'umami';
|
||||||
const hashSalt = encrypt(generatePassword(64));
|
const hashSalt = encrypt(generatePassword({ length: 64 }));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1515,9 +1619,9 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'hasura') {
|
} else if (type === 'hasura') {
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'hasura';
|
const postgresqlDatabase = 'hasura';
|
||||||
const graphQLAdminPassword = encrypt(generatePassword());
|
const graphQLAdminPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1534,9 +1638,9 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'fider') {
|
} else if (type === 'fider') {
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
const postgresqlPassword = encrypt(generatePassword());
|
const postgresqlPassword = encrypt(generatePassword({}));
|
||||||
const postgresqlDatabase = 'fider';
|
const postgresqlDatabase = 'fider';
|
||||||
const jwtSecret = encrypt(generatePassword(64, true));
|
const jwtSecret = encrypt(generatePassword({ length: 64, symbols: true }));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1553,13 +1657,13 @@ export async function configureServiceType({
|
|||||||
});
|
});
|
||||||
} else if (type === 'moodle') {
|
} else if (type === 'moodle') {
|
||||||
const defaultUsername = cuid();
|
const defaultUsername = cuid();
|
||||||
const defaultPassword = encrypt(generatePassword());
|
const defaultPassword = encrypt(generatePassword({}));
|
||||||
const defaultEmail = `${cuid()} @example.com`;
|
const defaultEmail = `${cuid()} @example.com`;
|
||||||
const mariadbUser = cuid();
|
const mariadbUser = cuid();
|
||||||
const mariadbPassword = encrypt(generatePassword());
|
const mariadbPassword = encrypt(generatePassword({}));
|
||||||
const mariadbDatabase = 'moodle_db';
|
const mariadbDatabase = 'moodle_db';
|
||||||
const mariadbRootUser = cuid();
|
const mariadbRootUser = cuid();
|
||||||
const mariadbRootUserPassword = encrypt(generatePassword());
|
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1579,15 +1683,15 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'appwrite') {
|
} else if (type === 'appwrite') {
|
||||||
const opensslKeyV1 = encrypt(generatePassword());
|
const opensslKeyV1 = encrypt(generatePassword({}));
|
||||||
const executorSecret = encrypt(generatePassword());
|
const executorSecret = encrypt(generatePassword({}));
|
||||||
const redisPassword = encrypt(generatePassword());
|
const redisPassword = encrypt(generatePassword({}));
|
||||||
const mariadbHost = `${id}-mariadb`
|
const mariadbHost = `${id}-mariadb`
|
||||||
const mariadbUser = cuid();
|
const mariadbUser = cuid();
|
||||||
const mariadbPassword = encrypt(generatePassword());
|
const mariadbPassword = encrypt(generatePassword({}));
|
||||||
const mariadbDatabase = 'appwrite';
|
const mariadbDatabase = 'appwrite';
|
||||||
const mariadbRootUser = cuid();
|
const mariadbRootUser = cuid();
|
||||||
const mariadbRootUserPassword = encrypt(generatePassword());
|
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -1607,6 +1711,47 @@ export async function configureServiceType({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} 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 {
|
} else {
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -1629,8 +1774,10 @@ export async function removeService({ id }: { id: string }): Promise<void> {
|
|||||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.wordpress.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.moodle.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
||||||
|
await prisma.searxng.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.service.delete({ where: { id } });
|
await prisma.service.delete({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1730,11 +1877,11 @@ export async function stopBuild(buildId, applicationId) {
|
|||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
if (count > 100) {
|
if (count > 50) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
return reject(new Error('Build canceled'));
|
return reject(new Error('Build canceled'));
|
||||||
}
|
}
|
||||||
const { stdout: buildContainers } = await executeDockerCmd({ dockerId, command: `docker container ls--filter "label=coolify.buildId=${buildId}" --format '{{json .}}'` })
|
const { stdout: buildContainers } = await executeDockerCmd({ dockerId, command: `docker container ls --filter "label=coolify.buildId=${buildId}" --format '{{json .}}'` })
|
||||||
if (buildContainers) {
|
if (buildContainers) {
|
||||||
const containersArray = buildContainers.trim().split('\n');
|
const containersArray = buildContainers.trim().split('\n');
|
||||||
for (const container of containersArray) {
|
for (const container of containersArray) {
|
||||||
@@ -1742,14 +1889,15 @@ export async function stopBuild(buildId, applicationId) {
|
|||||||
const id = containerObj.ID;
|
const id = containerObj.ID;
|
||||||
if (!containerObj.Names.startsWith(`${applicationId} `)) {
|
if (!containerObj.Names.startsWith(`${applicationId} `)) {
|
||||||
await removeContainer({ id, dockerId });
|
await removeContainer({ id, dockerId });
|
||||||
await cleanupDB(buildId);
|
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
} catch (error) { }
|
} catch (error) { } finally {
|
||||||
|
await cleanupDB(buildId);
|
||||||
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1769,7 +1917,7 @@ export function convertTolOldVolumeNames(type) {
|
|||||||
// export async function getAvailableServices(): Promise<any> {
|
// export async function getAvailableServices(): Promise<any> {
|
||||||
// const { data } = await axios.get(`https://gist.githubusercontent.com/andrasbacsai/4aac36d8d6214dbfc34fa78110554a50/raw/5b27e6c37d78aaeedc1148d797112c827a2f43cf/availableServices.json`)
|
// const { data } = await axios.get(`https://gist.githubusercontent.com/andrasbacsai/4aac36d8d6214dbfc34fa78110554a50/raw/5b27e6c37d78aaeedc1148d797112c827a2f43cf/availableServices.json`)
|
||||||
// return data
|
// return data
|
||||||
//
|
//
|
||||||
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
|
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
|
||||||
// Cleanup old coolify images
|
// Cleanup old coolify images
|
||||||
try {
|
try {
|
||||||
@@ -1832,8 +1980,6 @@ export function persistentVolumes(id, persistentStorage, config) {
|
|||||||
...composeVolumes
|
...composeVolumes
|
||||||
) || {}
|
) || {}
|
||||||
return { volumes, volumeMounts }
|
return { volumes, volumeMounts }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
export function defaultComposeConfiguration(network: string): any {
|
export function defaultComposeConfiguration(network: string): any {
|
||||||
return {
|
return {
|
||||||
@@ -1848,4 +1994,4 @@ export function defaultComposeConfiguration(network: string): any {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -557,4 +557,134 @@ export const appwrite = [{
|
|||||||
isNumber: false,
|
isNumber: false,
|
||||||
isBoolean: false,
|
isBoolean: false,
|
||||||
isEncrypted: 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: '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: 'defaultFromEmail',
|
||||||
|
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
|
||||||
}]
|
}]
|
||||||
@@ -239,8 +239,8 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
exposePort = Number(exposePort);
|
exposePort = Number(exposePort);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { destinationDockerId } = await prisma.application.findUnique({ where: { id } })
|
const { destinationDocker: { id: dockerId, remoteIpAddress } } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
if (exposePort) await checkExposedPort({ id, exposePort, dockerId: destinationDockerId })
|
if (exposePort) await checkExposedPort({ id, exposePort, dockerId, remoteIpAddress })
|
||||||
if (denoOptions) denoOptions = denoOptions.trim();
|
if (denoOptions) denoOptions = denoOptions.trim();
|
||||||
const defaultConfiguration = await setDefaultConfiguration({
|
const defaultConfiguration = await setDefaultConfiguration({
|
||||||
buildPack,
|
buildPack,
|
||||||
@@ -395,7 +395,7 @@ export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
|||||||
if (found) {
|
if (found) {
|
||||||
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
||||||
}
|
}
|
||||||
await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
|
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
|
||||||
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||||
let hostname = request.hostname.split(':')[0];
|
let hostname = request.hostname.split(':')[0];
|
||||||
if (remoteEngine) hostname = remoteIpAddress;
|
if (remoteEngine) hostname = remoteIpAddress;
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ export async function newDatabase(request: FastifyRequest, reply: FastifyReply)
|
|||||||
|
|
||||||
const name = uniqueName();
|
const name = uniqueName();
|
||||||
const dbUser = cuid();
|
const dbUser = cuid();
|
||||||
const dbUserPassword = encrypt(generatePassword());
|
const dbUserPassword = encrypt(generatePassword({}));
|
||||||
const rootUser = cuid();
|
const rootUser = cuid();
|
||||||
const rootUserPassword = encrypt(generatePassword());
|
const rootUserPassword = encrypt(generatePassword({}));
|
||||||
const defaultDatabase = cuid();
|
const defaultDatabase = cuid();
|
||||||
|
|
||||||
const { id } = await prisma.database.create({
|
const { id } = await prisma.database.create({
|
||||||
@@ -433,9 +433,13 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { isPublic, appendOnly = true } = request.body;
|
const { isPublic, appendOnly = true } = request.body;
|
||||||
|
|
||||||
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
|
let publicPort = null
|
||||||
const publicPort = await getFreePublicPort(id, dockerId);
|
|
||||||
|
|
||||||
|
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
|
|
||||||
|
if (isPublic) {
|
||||||
|
publicPort = await getFreePublicPort(id, dockerId);
|
||||||
|
}
|
||||||
await prisma.database.update({
|
await prisma.database.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { defaultServiceConfigurations } from '../../../../lib/services';
|
|||||||
// serviceSecret.forEach((secret) => {
|
// serviceSecret.forEach((secret) => {
|
||||||
// environmentVariables[secret.name] = secret.value;
|
// environmentVariables[secret.name] = secret.value;
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
// config.newVolumes = {}
|
// config.newVolumes = {}
|
||||||
// for (const service of Object.entries(config.services)) {
|
// for (const service of Object.entries(config.services)) {
|
||||||
// const name = service[0]
|
// const name = service[0]
|
||||||
@@ -98,7 +98,7 @@ import { defaultServiceConfigurations } from '../../../../lib/services';
|
|||||||
|
|
||||||
// }
|
// }
|
||||||
// console.log(config.services)
|
// console.log(config.services)
|
||||||
// console.log(config.volumes)
|
// console.log(config.volumes)
|
||||||
|
|
||||||
// // config.services[id] = JSON.parse(JSON.stringify(config.services[type]))
|
// // config.services[id] = JSON.parse(JSON.stringify(config.services[type]))
|
||||||
// // config.services[id].container_name = id
|
// // config.services[id].container_name = id
|
||||||
@@ -378,7 +378,7 @@ export async function checkService(request: FastifyRequest<CheckService>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
|
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
|
||||||
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||||
let hostname = request.hostname.split(':')[0];
|
let hostname = request.hostname.split(':')[0];
|
||||||
if (remoteEngine) hostname = remoteIpAddress;
|
if (remoteEngine) hostname = remoteIpAddress;
|
||||||
@@ -580,6 +580,12 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
if (type === 'appwrite') {
|
if (type === 'appwrite') {
|
||||||
return await startAppWriteService(request)
|
return await startAppWriteService(request)
|
||||||
}
|
}
|
||||||
|
if (type === 'glitchTip') {
|
||||||
|
return await startGlitchTipService(request)
|
||||||
|
}
|
||||||
|
if (type === 'searxng') {
|
||||||
|
return await startSearXNGService(request)
|
||||||
|
}
|
||||||
throw `Service type ${type} not supported.`
|
throw `Service type ${type} not supported.`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw { status: 500, message: error?.message || error }
|
throw { status: 500, message: error?.message || error }
|
||||||
@@ -588,53 +594,6 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
export async function stopService(request: FastifyRequest<ServiceStartStop>) {
|
export async function stopService(request: FastifyRequest<ServiceStartStop>) {
|
||||||
try {
|
try {
|
||||||
return await stopServiceContainers(request)
|
return await stopServiceContainers(request)
|
||||||
// const { type } = request.params
|
|
||||||
// if (type === 'plausibleanalytics') {
|
|
||||||
// return await stopPlausibleAnalyticsService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'nocodb') {
|
|
||||||
// return await stopNocodbService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'minio') {
|
|
||||||
// return await stopMinioService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'vscodeserver') {
|
|
||||||
// return await stopVscodeService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'wordpress') {
|
|
||||||
// return await stopWordpressService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'vaultwarden') {
|
|
||||||
// return await stopVaultwardenService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'languagetool') {
|
|
||||||
// return await stopLanguageToolService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'n8n') {
|
|
||||||
// return await stopN8nService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'uptimekuma') {
|
|
||||||
// return await stopUptimekumaService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'ghost') {
|
|
||||||
// return await stopGhostService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'meilisearch') {
|
|
||||||
// return await stopMeilisearchService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'umami') {
|
|
||||||
// return await stopUmamiService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'hasura') {
|
|
||||||
// return await stopHasuraService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'fider') {
|
|
||||||
// return await stopFiderService(request)
|
|
||||||
// }
|
|
||||||
// if (type === 'moodle') {
|
|
||||||
// return await stopMoodleService(request)
|
|
||||||
// }
|
|
||||||
// throw `Service type ${type} not supported.`
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw { status: 500, message: error?.message || error }
|
throw { status: 500, message: error?.message || error }
|
||||||
}
|
}
|
||||||
@@ -1105,7 +1064,7 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
|
|
||||||
const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config.wordpress)
|
const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config.wordpress)
|
||||||
|
|
||||||
let composeFile: ComposeFile = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
[id]: {
|
[id]: {
|
||||||
@@ -2409,6 +2368,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
async function startServiceContainers(dockerId, composeFileDestination) {
|
async function startServiceContainers(dockerId, composeFileDestination) {
|
||||||
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} pull` })
|
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
|
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} build --no-cache` })
|
||||||
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} create` })
|
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} create` })
|
||||||
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} start` })
|
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} start` })
|
||||||
await asyncSleep(1000);
|
await asyncSleep(1000);
|
||||||
@@ -2563,6 +2523,255 @@ async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function startGlitchTipService(request: FastifyRequest<ServiceStartStop>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
version,
|
||||||
|
fqdn,
|
||||||
|
destinationDockerId,
|
||||||
|
destinationDocker,
|
||||||
|
serviceSecret,
|
||||||
|
persistentStorage,
|
||||||
|
exposePort,
|
||||||
|
glitchTip: {
|
||||||
|
postgresqlDatabase,
|
||||||
|
postgresqlPassword,
|
||||||
|
postgresqlUser,
|
||||||
|
secretKeyBase,
|
||||||
|
defaultEmail,
|
||||||
|
defaultUsername,
|
||||||
|
defaultPassword,
|
||||||
|
defaultFromEmail,
|
||||||
|
emailSmtpHost,
|
||||||
|
emailSmtpPort,
|
||||||
|
emailSmtpUser,
|
||||||
|
emailSmtpPassword,
|
||||||
|
emailSmtpUseTls,
|
||||||
|
emailSmtpUseSsl,
|
||||||
|
emailBackend,
|
||||||
|
mailgunApiKey,
|
||||||
|
sendgridApiKey,
|
||||||
|
enableOpenUserRegistration,
|
||||||
|
}
|
||||||
|
} = service;
|
||||||
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
|
const port = getServiceMainPort('glitchTip');
|
||||||
|
|
||||||
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
|
const image = getServiceImage(type);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
glitchTip: {
|
||||||
|
image: `${image}:${version}`,
|
||||||
|
environmentVariables: {
|
||||||
|
PORT: port,
|
||||||
|
GLITCHTIP_DOMAIN: fqdn,
|
||||||
|
SECRET_KEY: secretKeyBase,
|
||||||
|
DATABASE_URL: `postgresql://${postgresqlUser}:${postgresqlPassword}@${id}-postgresql:5432/${postgresqlDatabase}`,
|
||||||
|
REDIS_URL: `redis://${id}-redis:6379/0`,
|
||||||
|
DEFAULT_FROM_EMAIL: defaultFromEmail,
|
||||||
|
EMAIL_HOST: emailSmtpHost,
|
||||||
|
EMAIL_PORT: emailSmtpPort,
|
||||||
|
EMAIL_HOST_USER: emailSmtpUser,
|
||||||
|
EMAIL_HOST_PASSWORD: emailSmtpPassword,
|
||||||
|
EMAIL_USE_TLS: emailSmtpUseTls,
|
||||||
|
EMAIL_USE_SSL: emailSmtpUseSsl,
|
||||||
|
EMAIL_BACKEND: emailBackend,
|
||||||
|
MAILGUN_API_KEY: mailgunApiKey,
|
||||||
|
SENDGRID_API_KEY: sendgridApiKey,
|
||||||
|
ENABLE_OPEN_USER_REGISTRATION: enableOpenUserRegistration,
|
||||||
|
DJANGO_SUPERUSER_EMAIL: defaultEmail,
|
||||||
|
DJANGO_SUPERUSER_USERNAME: defaultUsername,
|
||||||
|
DJANGO_SUPERUSER_PASSWORD: defaultPassword,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
postgresql: {
|
||||||
|
image: 'postgres:14-alpine',
|
||||||
|
volume: `${id}-postgresql-data:/var/lib/postgresql/data`,
|
||||||
|
environmentVariables: {
|
||||||
|
POSTGRES_USER: postgresqlUser,
|
||||||
|
POSTGRES_PASSWORD: postgresqlPassword,
|
||||||
|
POSTGRES_DB: postgresqlDatabase
|
||||||
|
}
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
image: 'redis:7-alpine',
|
||||||
|
volume: `${id}-redis-data:/data`,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (serviceSecret.length > 0) {
|
||||||
|
serviceSecret.forEach((secret) => {
|
||||||
|
config.glitchTip.environmentVariables[secret.name] = secret.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config.glitchTip)
|
||||||
|
const composeFile: ComposeFile = {
|
||||||
|
version: '3.8',
|
||||||
|
services: {
|
||||||
|
[id]: {
|
||||||
|
container_name: id,
|
||||||
|
image: config.glitchTip.image,
|
||||||
|
environment: config.glitchTip.environmentVariables,
|
||||||
|
volumes,
|
||||||
|
labels: makeLabelForServices('glitchTip'),
|
||||||
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
|
depends_on: [`${id}-postgresql`, `${id}-redis`],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
[`${id}-worker`]: {
|
||||||
|
container_name: `${id}-worker`,
|
||||||
|
image: config.glitchTip.image,
|
||||||
|
command: './bin/run-celery-with-beat.sh',
|
||||||
|
environment: config.glitchTip.environmentVariables,
|
||||||
|
depends_on: [`${id}-postgresql`, `${id}-redis`],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
[`${id}-setup`]: {
|
||||||
|
container_name: `${id}-setup`,
|
||||||
|
image: config.glitchTip.image,
|
||||||
|
command: 'sh -c "(./manage.py migrate || true) && (./manage.py createsuperuser --noinput || true)"',
|
||||||
|
environment: config.glitchTip.environmentVariables,
|
||||||
|
networks: [network],
|
||||||
|
restart: "no",
|
||||||
|
depends_on: [`${id}-postgresql`, `${id}-redis`]
|
||||||
|
},
|
||||||
|
[`${id}-postgresql`]: {
|
||||||
|
image: config.postgresql.image,
|
||||||
|
container_name: `${id}-postgresql`,
|
||||||
|
environment: config.postgresql.environmentVariables,
|
||||||
|
volumes: [config.postgresql.volume],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
[`${id}-redis`]: {
|
||||||
|
image: config.redis.image,
|
||||||
|
container_name: `${id}-redis`,
|
||||||
|
volumes: [config.redis.volume],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
networks: {
|
||||||
|
[network]: {
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
volumes: {
|
||||||
|
...volumeMounts,
|
||||||
|
[config.postgresql.volume.split(':')[0]]: {
|
||||||
|
name: config.postgresql.volume.split(':')[0]
|
||||||
|
},
|
||||||
|
[config.redis.volume.split(':')[0]]: {
|
||||||
|
name: config.redis.volume.split(':')[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||||
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
|
||||||
|
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
|
||||||
|
|
||||||
|
return {}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startSearXNGService(request: FastifyRequest<ServiceStartStop>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
|
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort, persistentStorage, fqdn, searxng: { secretKey, redisPassword } } =
|
||||||
|
service;
|
||||||
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
|
const port = getServiceMainPort('searxng');
|
||||||
|
|
||||||
|
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||||
|
const image = getServiceImage(type);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
searxng: {
|
||||||
|
image: `${image}:${version}`,
|
||||||
|
volume: `${id}-searxng:/etc/searxng`,
|
||||||
|
environmentVariables: {
|
||||||
|
SEARXNG_BASE_URL: `${fqdn}`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
image: 'redis:7-alpine',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const settingsYml = `
|
||||||
|
# see https://docs.searxng.org/admin/engines/settings.html#use-default-settings
|
||||||
|
use_default_settings: true
|
||||||
|
server:
|
||||||
|
secret_key: ${secretKey}
|
||||||
|
limiter: true
|
||||||
|
image_proxy: true
|
||||||
|
ui:
|
||||||
|
static_use_hash: true
|
||||||
|
redis:
|
||||||
|
url: redis://:${redisPassword}@${id}-redis:6379/0`
|
||||||
|
|
||||||
|
const Dockerfile = `
|
||||||
|
FROM ${config.searxng.image}
|
||||||
|
COPY ./settings.yml /etc/searxng/settings.yml`;
|
||||||
|
|
||||||
|
if (serviceSecret.length > 0) {
|
||||||
|
serviceSecret.forEach((secret) => {
|
||||||
|
config.searxng.environmentVariables[secret.name] = secret.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
|
const composeFile: ComposeFile = {
|
||||||
|
version: '3.8',
|
||||||
|
services: {
|
||||||
|
[id]: {
|
||||||
|
build: workdir,
|
||||||
|
container_name: id,
|
||||||
|
volumes,
|
||||||
|
environment: config.searxng.environmentVariables,
|
||||||
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
|
labels: makeLabelForServices('searxng'),
|
||||||
|
cap_drop: ['ALL'],
|
||||||
|
cap_add: ['CHOWN', 'SETGID', 'SETUID', 'DAC_OVERRIDE'],
|
||||||
|
depends_on: [`${id}-redis`],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
[`${id}-redis`]: {
|
||||||
|
container_name: `${id}-redis`,
|
||||||
|
image: config.redis.image,
|
||||||
|
command: `redis-server --requirepass ${redisPassword} --save "" --appendonly "no"`,
|
||||||
|
labels: makeLabelForServices('searxng'),
|
||||||
|
cap_drop: ['ALL'],
|
||||||
|
cap_add: ['SETGID', 'SETUID', 'DAC_OVERRIDE'],
|
||||||
|
...defaultComposeConfiguration(network),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
networks: {
|
||||||
|
[network]: {
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
volumes: volumeMounts
|
||||||
|
};
|
||||||
|
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||||
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile);
|
||||||
|
await fs.writeFile(`${workdir}/settings.yml`, settingsYml);
|
||||||
|
|
||||||
|
await startServiceContainers(destinationDocker.id, composeFileDestination)
|
||||||
|
return {}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
@@ -2613,7 +2822,7 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
const publicPort = await getFreePublicPort(id, dockerId);
|
const publicPort = await getFreePublicPort(id, dockerId);
|
||||||
|
|
||||||
let ftpUser = cuid();
|
let ftpUser = cuid();
|
||||||
let ftpPassword = generatePassword();
|
let ftpPassword = generatePassword({});
|
||||||
|
|
||||||
const hostkeyDir = isDev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
|
const hostkeyDir = isDev ? '/tmp/hostkeys' : '/app/ssl/hostkeys';
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
ports: {
|
ports: {
|
||||||
main: 80
|
main: 80
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
// {
|
// {
|
||||||
// name: 'moodle',
|
// name: 'moodle',
|
||||||
// fancyName: 'Moodle',
|
// fancyName: 'Moodle',
|
||||||
@@ -170,6 +170,28 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
// main: 8080
|
// 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: ['redis:6.2-alpine'],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const asyncSleep = (delay: number) =>
|
export const asyncSleep = (delay: number) =>
|
||||||
|
|||||||
51
apps/ui/src/lib/components/svg/services/GlitchTip.svelte
Normal file
51
apps/ui/src/lib/components/svg/services/GlitchTip.svelte
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
style="isolation:isolate"
|
||||||
|
viewBox="0 0 400 400"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="_clipPath_5kOQy2sGcuF9aeG3NHWmCAGgMEPQrnNW">
|
||||||
|
<rect width="400" height="400" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#_clipPath_5kOQy2sGcuF9aeG3NHWmCAGgMEPQrnNW)">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d=" M 276.155 367.684 L 337.655 367.684 L 337.655 180.781 L 205.525 180.781 L 205.525 241.801 L 267.987 241.801 L 267.987 258.617 C 267.987 291.29 238.678 308.586 202.162 308.586 C 156.998 308.586 127.689 282.641 127.689 226.906 L 127.689 173.094 C 127.689 117.359 156.998 91.414 202.162 91.414 C 241.08 91.414 261.74 112.554 271.83 138.5 L 331.409 104.386 C 306.424 52.976 261.74 26.55 202.162 26.55 C 111.353 26.55 50.333 88.531 50.333 201.441 C 50.333 313.872 110.873 373.45 187.748 373.45 C 238.197 373.45 268.947 347.985 273.752 314.352 L 276.155 314.352 L 276.155 367.684 Z "
|
||||||
|
fill="rgb(132,24,128)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g opacity="0.5">
|
||||||
|
<path
|
||||||
|
d=" M 139.701 175.78 L 139.701 173.094 C 139.701 117.359 169.01 91.414 214.174 91.414 C 253.092 91.414 273.752 112.554 283.842 138.5 L 343.421 104.386 C 318.436 52.976 273.752 26.55 214.174 26.55 C 128.962 26.55 69.981 81.125 63.033 181.145 L 139.701 175.78 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g opacity="0.5">
|
||||||
|
<path
|
||||||
|
d=" M 349.667 305.194 L 349.667 247.137 L 279.998 252.019 L 279.998 258.617 C 279.998 291.29 250.69 308.586 214.174 308.586 C 179.697 308.586 154.459 293.467 144.446 261.518 L 70.341 266.711 C 76.285 288.796 85.348 307.563 96.86 322.909 L 349.667 305.194 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
d=" M 337.655 247.03 L 337.655 180.781 L 205.525 180.781 L 205.525 241.801 L 267.987 241.801 L 267.987 251.912 L 337.655 247.03 Z M 132.401 261.413 C 129.319 251.534 127.689 240.048 127.689 226.906 L 127.689 175.099 L 51.069 180.468 C 50.581 187.25 50.333 194.242 50.333 201.441 C 50.333 225.632 53.136 247.376 58.301 266.606 L 132.401 261.413 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(233,64,86)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d=" M 337.655 305.862 L 337.655 367.684 L 276.155 367.684 L 276.155 314.352 L 273.752 314.352 C 268.947 347.985 238.197 373.45 187.748 373.45 C 146.712 373.45 110.33 356.473 85.327 323.543 L 337.655 305.862 Z "
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="rgb(255,63,42)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
57
apps/ui/src/lib/components/svg/services/Searxng.svelte
Normal file
57
apps/ui/src/lib/components/svg/services/Searxng.svelte
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isAbsolute = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
id="svg8"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 92 92"
|
||||||
|
class={isAbsolute ? 'w-12 absolute top-0 left-0 -m-3 -mt-5' : 'w-8 mx-auto'}
|
||||||
|
>
|
||||||
|
<defs id="defs2" />
|
||||||
|
<metadata id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(-40.921303,-17.416526)" id="layer1">
|
||||||
|
<circle
|
||||||
|
r="0"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cy="92"
|
||||||
|
cx="75"
|
||||||
|
id="path3713"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
r="30"
|
||||||
|
cy="53.902557"
|
||||||
|
cx="75.921303"
|
||||||
|
id="path834"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#3050ff;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m 67.514849,37.91524 a 18,18 0 0 1 21.051475,3.312407 18,18 0 0 1 3.137312,21.078282"
|
||||||
|
id="path852"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#3050ff;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
transform="rotate(-46.234709)"
|
||||||
|
ry="1.8669105e-13"
|
||||||
|
y="122.08995"
|
||||||
|
x="3.7063529"
|
||||||
|
height="39.963303"
|
||||||
|
width="18.846331"
|
||||||
|
id="rect912"
|
||||||
|
style="opacity:1;fill:#3050ff;fill-opacity:1;stroke:none;stroke-width:8;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
@@ -36,4 +36,8 @@
|
|||||||
<Icons.Appwrite {isAbsolute} />
|
<Icons.Appwrite {isAbsolute} />
|
||||||
{:else if type === 'moodle'}
|
{:else if type === 'moodle'}
|
||||||
<Icons.Moodle {isAbsolute} />
|
<Icons.Moodle {isAbsolute} />
|
||||||
|
{:else if type === 'glitchTip'}
|
||||||
|
<Icons.GlitchTip {isAbsolute} />
|
||||||
|
{:else if type === 'searxng'}
|
||||||
|
<Icons.Searxng {isAbsolute} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ export { default as Hasura } from './Hasura.svelte';
|
|||||||
export { default as Fider } from './Fider.svelte';
|
export { default as Fider } from './Fider.svelte';
|
||||||
export { default as Appwrite } from './Appwrite.svelte';
|
export { default as Appwrite } from './Appwrite.svelte';
|
||||||
export { default as Moodle } from './Moodle.svelte';
|
export { default as Moodle } from './Moodle.svelte';
|
||||||
|
export { default as GlitchTip } from './GlitchTip.svelte';
|
||||||
|
export { default as Searxng } from './Searxng.svelte';
|
||||||
@@ -43,7 +43,8 @@
|
|||||||
const batchSecretsPairs = eachValuePair
|
const batchSecretsPairs = eachValuePair
|
||||||
.filter((secret) => !secret.startsWith('#') && secret)
|
.filter((secret) => !secret.startsWith('#') && secret)
|
||||||
.map((secret) => {
|
.map((secret) => {
|
||||||
const [name, value] = secret.split('=');
|
const [name, ...rest] = secret.split('=');
|
||||||
|
const value = rest.join('=');
|
||||||
const cleanValue = value?.replaceAll('"', '') || '';
|
const cleanValue = value?.replaceAll('"', '') || '';
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col justify-center">
|
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
|
||||||
{#if !applications || ownApplications.length === 0}
|
{#if !applications || ownApplications.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
|
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-col justify-center">
|
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
|
||||||
{#if !databases || ownDatabases.length === 0}
|
{#if !databases || ownDatabases.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>
|
<div class="text-center text-xl font-bold">{$t('database.no_databases_found')}</div>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col justify-center">
|
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
|
||||||
{#if !destinations || ownDestinations.length === 0}
|
{#if !destinations || ownDestinations.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">{$t('destination.no_destination_found')}</div>
|
<div class="text-center text-xl font-bold">{$t('destination.no_destination_found')}</div>
|
||||||
|
|||||||
@@ -212,6 +212,7 @@
|
|||||||
<div class="flex items-center justify-center pt-3">
|
<div class="flex items-center justify-center pt-3">
|
||||||
<button
|
<button
|
||||||
on:click|preventDefault={() => switchTeam(team.id)}
|
on:click|preventDefault={() => switchTeam(team.id)}
|
||||||
|
class="btn btn-sm"
|
||||||
class:bg-fuchsia-600={$appSession.teamId !== team.id}
|
class:bg-fuchsia-600={$appSession.teamId !== team.id}
|
||||||
class:hover:bg-fuchsia-500={$appSession.teamId !== team.id}
|
class:hover:bg-fuchsia-500={$appSession.teamId !== team.id}
|
||||||
class:bg-transparent={$appSession.teamId === team.id}
|
class:bg-transparent={$appSession.teamId === team.id}
|
||||||
|
|||||||
@@ -93,11 +93,13 @@
|
|||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
<div class="mr-4 text-2xl tracking-tight">{$t('index.dashboard')}</div>
|
<div class="mr-4 text-2xl tracking-tight">{$t('index.dashboard')}</div>
|
||||||
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
|
{#if $appSession.teamId === '0'}
|
||||||
>Cleanup Storage</button
|
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
|
||||||
>
|
>Cleanup Storage</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-10 pb-12 tracking-tight sm:pb-16">
|
<div class="mt-10 pb-12 sm:pb-16">
|
||||||
<div class="mx-auto px-10">
|
<div class="mx-auto px-10">
|
||||||
<div class="flex flex-col justify-center xl:flex-row">
|
<div class="flex flex-col justify-center xl:flex-row">
|
||||||
{#if applications.length > 0}
|
{#if applications.length > 0}
|
||||||
@@ -341,6 +343,8 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="text-center text-xl font-bold">Nothing is configured yet.</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $appSession.teamId === '0'}
|
{#if $appSession.teamId === '0'}
|
||||||
<Usage />
|
<Usage />
|
||||||
|
|||||||
@@ -57,10 +57,18 @@
|
|||||||
</a>
|
</a>
|
||||||
{:else if service.type === 'appwrite'}
|
{:else if service.type === 'appwrite'}
|
||||||
<a href="https://appwrite.io" target="_blank">
|
<a href="https://appwrite.io" target="_blank">
|
||||||
<Icons.Appwrite/>
|
<Icons.Appwrite />
|
||||||
</a>
|
</a>
|
||||||
{:else if service.type === 'moodle'}
|
{:else if service.type === 'moodle'}
|
||||||
<a href="https://moodle.org" target="_blank">
|
<a href="https://moodle.org" target="_blank">
|
||||||
<Icons.Moodle />
|
<Icons.Moodle />
|
||||||
</a>
|
</a>
|
||||||
|
{:else if service.type === 'glitchTip'}
|
||||||
|
<a href="https://glitchtip.com" target="_blank">
|
||||||
|
<Icons.GlitchTip />
|
||||||
|
</a>
|
||||||
|
{:else if service.type === 'searxng'}
|
||||||
|
<a href="https://searxng.org" target="_blank">
|
||||||
|
<Icons.Searxng />
|
||||||
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
208
apps/ui/src/routes/services/[id]/_Services/_GlitchTip.svelte
Normal file
208
apps/ui/src/routes/services/[id]/_Services/_GlitchTip.svelte
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
export let service: any;
|
||||||
|
function toggleEmailSmtpUseTls() {
|
||||||
|
service.glitchTip.emailSmtpUseTls = !service.glitchTip.emailSmtpUseTls;
|
||||||
|
}
|
||||||
|
function toggleEmailSmtpUseSsl() {
|
||||||
|
service.glitchTip.emailSmtpUseSsl = !service.glitchTip.emailSmtpUseSsl;
|
||||||
|
}
|
||||||
|
function toggleEnableOpenUserRegistration() {
|
||||||
|
service.glitchTip.enableOpenUserRegistration = !service.glitchTip.enableOpenUserRegistration;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">GlitchTip</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-2 font-bold">
|
||||||
|
<div class="subtitle">Settings</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<Setting
|
||||||
|
bind:setting={service.glitchTip.enableOpenUserRegistration}
|
||||||
|
on:click={toggleEnableOpenUserRegistration}
|
||||||
|
title={'Enable Open User Registration'}
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-2 font-bold">
|
||||||
|
<div class="subtitle">Email settings</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultEmailFrom" class="text-base font-bold text-stone-100">Default Email From</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
required
|
||||||
|
name="defaultEmailFrom"
|
||||||
|
id="defaultEmailFrom"
|
||||||
|
value={service.glitchTip.defaultEmailFrom}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpHost" class="text-base font-bold text-stone-100">SMTP Host</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpHost"
|
||||||
|
id="emailSmtpHost"
|
||||||
|
value={service.glitchTip.emailSmtpHost}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpPort" class="text-base font-bold text-stone-100">SMTP Port</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpPort"
|
||||||
|
id="emailSmtpPort"
|
||||||
|
value={service.glitchTip.emailSmtpPort}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpUser" class="text-base font-bold text-stone-100">SMTP User</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpUser"
|
||||||
|
id="emailSmtpUser"
|
||||||
|
value={service.glitchTip.emailSmtpUser}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailSmtpPassword" class="text-base font-bold text-stone-100">SMTP Password</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="emailSmtpPassword"
|
||||||
|
id="emailSmtpPassword"
|
||||||
|
value={service.glitchTip.emailSmtpPassword}
|
||||||
|
isPasswordField
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<Setting
|
||||||
|
bind:setting={service.glitchTip.emailSmtpUseTls}
|
||||||
|
on:click={toggleEmailSmtpUseTls}
|
||||||
|
title={'SMTP Use TLS'}
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<Setting
|
||||||
|
bind:setting={service.glitchTip.emailSmtpUseSsl}
|
||||||
|
on:click={toggleEmailSmtpUseSsl}
|
||||||
|
title={'SMTP Use SSL'}
|
||||||
|
description={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="emailBackend" class="text-base font-bold text-stone-100">Email Backend</label>
|
||||||
|
<CopyPasswordField name="emailBackend" id="emailBackend" value={service.glitchTip.emailBackend} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="mailgunApiKey" class="text-base font-bold text-stone-100">Mailgun API Key</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="mailgunApiKey"
|
||||||
|
id="mailgunApiKey"
|
||||||
|
value={service.glitchTip.mailgunApiKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="sendgridApiKey" class="text-base font-bold text-stone-100">SendGrid API Key</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="sendgridApiKey"
|
||||||
|
id="sendgridApiKey"
|
||||||
|
value={service.glitchTip.sendgridApiKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-2 font-bold">
|
||||||
|
<div class="subtitle">Default User & Superuser</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultEmail" class="text-base font-bold text-stone-100">{$t('forms.email')}</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="defaultEmail"
|
||||||
|
id="defaultEmail"
|
||||||
|
value={service.glitchTip.defaultEmail}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultUsername" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.username')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="defaultUsername"
|
||||||
|
id="defaultUsername"
|
||||||
|
value={service.glitchTip.defaultUsername}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="defaultPassword" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.password')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="defaultPassword"
|
||||||
|
id="defaultPassword"
|
||||||
|
value={service.glitchTip.defaultPassword}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
isPasswordField
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">PostgreSQL</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlUser" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.username')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlUser"
|
||||||
|
id="postgresqlUser"
|
||||||
|
value={service.glitchTip.postgresqlUser}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlPassword" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('forms.password')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
id="postgresqlPassword"
|
||||||
|
isPasswordField
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
name="postgresqlPassword"
|
||||||
|
value={service.glitchTip.postgresqlPassword}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="postgresqlDatabase" class="text-base font-bold text-stone-100"
|
||||||
|
>{$t('index.database')}</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="postgresqlDatabase"
|
||||||
|
id="postgresqlDatabase"
|
||||||
|
value={service.glitchTip.postgresqlDatabase}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
36
apps/ui/src/routes/services/[id]/_Services/_Searxng.svelte
Normal file
36
apps/ui/src/routes/services/[id]/_Services/_Searxng.svelte
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
export let service: any;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">SearXNG</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="secretKey">Secret Key</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="secretKey"
|
||||||
|
id="secretKey"
|
||||||
|
isPasswordField
|
||||||
|
value={service.searxng.secretKey}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
|
<div class="title">Redis</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
|
<label for="redisPassword">{$t('forms.password')}</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
name="redisPassword"
|
||||||
|
id="redisPassword"
|
||||||
|
isPasswordField
|
||||||
|
value={service.searxng.redisPassword}
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import Fider from './_Fider.svelte';
|
import Fider from './_Fider.svelte';
|
||||||
import Ghost from './_Ghost.svelte';
|
import Ghost from './_Ghost.svelte';
|
||||||
|
import GlitchTip from './_GlitchTip.svelte';
|
||||||
import Hasura from './_Hasura.svelte';
|
import Hasura from './_Hasura.svelte';
|
||||||
import MeiliSearch from './_MeiliSearch.svelte';
|
import MeiliSearch from './_MeiliSearch.svelte';
|
||||||
import MinIo from './_MinIO.svelte';
|
import MinIo from './_MinIO.svelte';
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
import Wordpress from './_Wordpress.svelte';
|
import Wordpress from './_Wordpress.svelte';
|
||||||
import Appwrite from './_Appwrite.svelte';
|
import Appwrite from './_Appwrite.svelte';
|
||||||
import Moodle from './_Moodle.svelte';
|
import Moodle from './_Moodle.svelte';
|
||||||
|
import Searxng from './_Searxng.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
$: isDisabled =
|
$: isDisabled =
|
||||||
@@ -399,6 +401,10 @@
|
|||||||
<Appwrite bind:service {readOnly} />
|
<Appwrite bind:service {readOnly} />
|
||||||
{:else if service.type === 'moodle'}
|
{:else if service.type === 'moodle'}
|
||||||
<Moodle bind:service {readOnly} />
|
<Moodle bind:service {readOnly} />
|
||||||
|
{:else if service.type === 'glitchTip'}
|
||||||
|
<GlitchTip bind:service />
|
||||||
|
{:else if service.type === 'searxng'}
|
||||||
|
<Searxng bind:service />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-col justify-center">
|
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
|
||||||
{#if !services || ownServices.length === 0}
|
{#if !services || ownServices.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">{$t('service.no_service')}</div>
|
<div class="text-center text-xl font-bold">{$t('service.no_service')}</div>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col justify-center">
|
<div class="flex-col justify-center mt-10 pb-12 sm:pb-16">
|
||||||
{#if !sources || ownSources.length === 0}
|
{#if !sources || ownSources.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">{$t('source.no_git_sources_found')}</div>
|
<div class="text-center text-xl font-bold">{$t('source.no_git_sources_found')}</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "3.6.0",
|
"version": "3.8.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": "github:coollabsio/coolify",
|
"repository": "github:coollabsio/coolify",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
524
pnpm-lock.yaml
generated
524
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user