commit
397ca7f20e
6
data/fluentd/Dockerfile-dev
Normal file
6
data/fluentd/Dockerfile-dev
Normal file
@ -0,0 +1,6 @@
|
||||
FROM fluent/fluent-bit:1.9.0
|
||||
COPY fluentbit-dev.conf /tmp/fluentbit.conf
|
||||
ENTRYPOINT ["/fluent-bit/bin/fluent-bit", "-c", "/tmp/fluentbit.conf"]
|
||||
# USER root
|
||||
# RUN ["gem", "install", "fluent-plugin-mongo"]
|
||||
# USER fluent
|
24
data/fluentd/fluentbit-dev.conf
Normal file
24
data/fluentd/fluentbit-dev.conf
Normal file
@ -0,0 +1,24 @@
|
||||
[INPUT]
|
||||
Name forward
|
||||
Listen 0.0.0.0
|
||||
Port 24224
|
||||
Buffer_Chunk_Size 32KB
|
||||
Buffer_Max_Size 64KB
|
||||
|
||||
[OUTPUT]
|
||||
Name influxdb
|
||||
Match *
|
||||
Host coolify-influxdb
|
||||
Port 8086
|
||||
Bucket containerlogs
|
||||
Org organization
|
||||
HTTP_Token supertoken
|
||||
Sequence_Tag _seq
|
||||
Tag_Keys container_name
|
||||
[OUTPUT]
|
||||
Name http
|
||||
Match *
|
||||
Host host.docker.internal
|
||||
Port 3000
|
||||
URI /logs.json
|
||||
Format json
|
28
data/fluentd/fluentd-dev.conf
Normal file
28
data/fluentd/fluentd-dev.conf
Normal file
@ -0,0 +1,28 @@
|
||||
<source>
|
||||
@type forward
|
||||
port 24224
|
||||
bind 0.0.0.0
|
||||
</source>
|
||||
|
||||
<match **>
|
||||
@type http
|
||||
endpoint http://host.docker.internal:3000/logs.json
|
||||
<buffer>
|
||||
flush_at_shutdown true
|
||||
flush_mode immediate
|
||||
flush_thread_count 8
|
||||
flush_thread_interval 1
|
||||
flush_thread_burst_interval 1
|
||||
retry_forever true
|
||||
retry_type exponential_backoff
|
||||
</buffer>
|
||||
</match>
|
||||
|
||||
<filter docker.**>
|
||||
@type parser
|
||||
key_name log
|
||||
reserve_data true
|
||||
<parse>
|
||||
@type json
|
||||
</parse>
|
||||
</filter>
|
@ -11,7 +11,24 @@ services:
|
||||
published: 6379
|
||||
protocol: tcp
|
||||
mode: host
|
||||
|
||||
# fluentbit:
|
||||
# container_name: coolify-fluentbit
|
||||
# build:
|
||||
# context: ./data/fluentd
|
||||
# dockerfile: Dockerfile-dev
|
||||
# ports:
|
||||
# - target: 24224
|
||||
# published: 24224
|
||||
# protocol: tcp
|
||||
# mode: host
|
||||
# - target: 24224
|
||||
# published: 24224
|
||||
# protocol: udp
|
||||
# mode: host
|
||||
# networks:
|
||||
# - coolify-infra
|
||||
# extra_hosts:
|
||||
# - 'host.docker.internal:host-gateway'
|
||||
networks:
|
||||
coolify-infra:
|
||||
attachable: true
|
||||
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "2.4.10",
|
||||
"version": "2.4.11",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev",
|
||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0",
|
||||
"dev:stop": "docker-compose -f docker-compose-dev.yaml down",
|
||||
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
|
||||
"studio": "npx prisma studio",
|
||||
@ -63,7 +63,7 @@
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@prisma/client": "3.11.1",
|
||||
"@sentry/node": "6.19.6",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bullmq": "1.80.0",
|
||||
"compare-versions": "4.1.3",
|
||||
"cookie": "0.4.2",
|
||||
|
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@ -5,7 +5,7 @@ specifiers:
|
||||
'@prisma/client': 3.11.1
|
||||
'@sentry/node': 6.19.6
|
||||
'@sveltejs/adapter-node': 1.0.0-next.73
|
||||
'@sveltejs/kit': 1.0.0-next.303
|
||||
'@sveltejs/kit': 1.0.0-next.310
|
||||
'@types/js-cookie': 3.0.1
|
||||
'@types/js-yaml': 4.0.5
|
||||
'@types/node': 17.0.23
|
||||
@ -14,8 +14,8 @@ specifiers:
|
||||
'@typescript-eslint/parser': 4.31.1
|
||||
'@zerodevx/svelte-toast': 0.7.1
|
||||
autoprefixer: 10.4.4
|
||||
bcryptjs: ^2.4.3
|
||||
bullmq: 1.78.1
|
||||
bcryptjs: 2.4.3
|
||||
bullmq: 1.80.0
|
||||
compare-versions: 4.1.3
|
||||
cookie: 0.4.2
|
||||
cross-env: 7.0.3
|
||||
@ -60,7 +60,7 @@ dependencies:
|
||||
'@prisma/client': 3.11.1_prisma@3.11.1
|
||||
'@sentry/node': 6.19.6
|
||||
bcryptjs: 2.4.3
|
||||
bullmq: 1.78.1
|
||||
bullmq: 1.80.0
|
||||
compare-versions: 4.1.3
|
||||
cookie: 0.4.2
|
||||
cuid: 2.1.8
|
||||
@ -82,7 +82,7 @@ dependencies:
|
||||
|
||||
devDependencies:
|
||||
'@sveltejs/adapter-node': 1.0.0-next.73
|
||||
'@sveltejs/kit': 1.0.0-next.303_svelte@3.47.0
|
||||
'@sveltejs/kit': 1.0.0-next.310_svelte@3.47.0
|
||||
'@types/js-cookie': 3.0.1
|
||||
'@types/js-yaml': 4.0.5
|
||||
'@types/node': 17.0.23
|
||||
@ -374,10 +374,10 @@ packages:
|
||||
tiny-glob: 0.2.9
|
||||
dev: true
|
||||
|
||||
/@sveltejs/kit/1.0.0-next.303_svelte@3.47.0:
|
||||
/@sveltejs/kit/1.0.0-next.310_svelte@3.47.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-WdxDc8OiF1WEd/bEza7CBdzA+3qIcCi1GKBj/gieKX9I3N8iDJt/Cg2POrLo9wQoJ47nZcAd1eOhfr7XEX1aIQ==
|
||||
integrity: sha512-pTyMyaoyHS+V5cQZIQMfQXmLkhw1VaRwT9avOSgwDc0QBpnNw2LdzwoPYsUr96ca5B6cfT3SMUNolxErTNHmPQ==
|
||||
}
|
||||
engines: { node: '>=14.13' }
|
||||
hasBin: true
|
||||
@ -1669,10 +1669,10 @@ packages:
|
||||
ieee754: 1.2.1
|
||||
dev: false
|
||||
|
||||
/bullmq/1.78.1:
|
||||
/bullmq/1.80.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-er45mM8nGhgA83EVCJ4PNxPyDSzakvoxeFGU4vdSgYeB+SbeFQAlJYmAC50Ms7YFPstm1LeinbVZ+oX/BmBzOg==
|
||||
integrity: sha512-oz7GZIg7gAGIIlLQ3KdpYSA5WSz5205pQHyGwOtQof9MmkOf+Kmo6sxqr+BiQrjhFOrB6JLSCqS3EGEbMA34MA==
|
||||
}
|
||||
dependencies:
|
||||
cron-parser: 4.2.1
|
||||
|
@ -0,0 +1,12 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ServicePersistentStorage" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ServicePersistentStorage_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ServicePersistentStorage_serviceId_path_key" ON "ServicePersistentStorage"("serviceId", "path");
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Application" ADD COLUMN "dockerFileLocation" TEXT;
|
@ -1,6 +1,6 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["linux-musl"]
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "linux-musl"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
@ -91,6 +91,7 @@ model Application {
|
||||
pythonWSGI String?
|
||||
pythonModule String?
|
||||
pythonVariable String?
|
||||
dockerFileLocation String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
settings ApplicationSettings?
|
||||
@ -118,14 +119,25 @@ model ApplicationSettings {
|
||||
model ApplicationPersistentStorage {
|
||||
id String @id @default(cuid())
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
applicationId String
|
||||
path String
|
||||
applicationId String
|
||||
path String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([applicationId, path])
|
||||
}
|
||||
|
||||
model ServicePersistentStorage {
|
||||
id String @id @default(cuid())
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
serviceId String
|
||||
path String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([serviceId, path])
|
||||
}
|
||||
|
||||
model Secret {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
@ -267,17 +279,17 @@ model DatabaseSettings {
|
||||
}
|
||||
|
||||
model Service {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
fqdn String?
|
||||
dualCerts Boolean @default(false)
|
||||
dualCerts Boolean @default(false)
|
||||
type String?
|
||||
version String?
|
||||
teams Team[]
|
||||
destinationDockerId String?
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
plausibleAnalytics PlausibleAnalytics?
|
||||
minio Minio?
|
||||
vscodeserver Vscodeserver?
|
||||
@ -285,6 +297,7 @@ model Service {
|
||||
ghost Ghost?
|
||||
serviceSecret ServiceSecret[]
|
||||
meiliSearch MeiliSearch?
|
||||
persistentStorage ServicePersistentStorage[]
|
||||
}
|
||||
|
||||
model PlausibleAnalytics {
|
||||
|
6
src/app.d.ts
vendored
6
src/app.d.ts
vendored
@ -6,7 +6,11 @@ declare namespace App {
|
||||
cookies: Record<string, string>;
|
||||
}
|
||||
interface Platform {}
|
||||
interface Session extends SessionData {}
|
||||
interface Session extends SessionData {
|
||||
whiteLabelDetails: {
|
||||
icon: string | null;
|
||||
};
|
||||
}
|
||||
interface Stuff {
|
||||
service: any;
|
||||
application: any;
|
||||
|
@ -8,6 +8,9 @@ import cookie from 'cookie';
|
||||
import { dev } from '$app/env';
|
||||
|
||||
const whiteLabeled = process.env['COOLIFY_WHITE_LABELED'] === 'true';
|
||||
const whiteLabelDetails = {
|
||||
icon: (whiteLabeled && process.env['COOLIFY_WHITE_LABELED_ICON']) || null
|
||||
};
|
||||
|
||||
export const handle = handleSession(
|
||||
{
|
||||
@ -74,6 +77,7 @@ export const getSession: GetSession = function ({ locals }) {
|
||||
return {
|
||||
version,
|
||||
whiteLabeled,
|
||||
whiteLabelDetails,
|
||||
...locals.session.data
|
||||
};
|
||||
};
|
||||
|
@ -91,7 +91,8 @@ export const setDefaultConfiguration = async (data) => {
|
||||
startCommand,
|
||||
buildCommand,
|
||||
publishDirectory,
|
||||
baseDirectory
|
||||
baseDirectory,
|
||||
dockerFileLocation
|
||||
} = data;
|
||||
const template = scanningTemplates[buildPack];
|
||||
if (!port) {
|
||||
@ -110,6 +111,12 @@ export const setDefaultConfiguration = async (data) => {
|
||||
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
||||
if (!baseDirectory.endsWith('/')) baseDirectory = `${baseDirectory}/`;
|
||||
}
|
||||
if (dockerFileLocation) {
|
||||
if (!dockerFileLocation.startsWith('/')) dockerFileLocation = `/${dockerFileLocation}`;
|
||||
if (dockerFileLocation.endsWith('/')) dockerFileLocation = dockerFileLocation.slice(0, -1);
|
||||
} else {
|
||||
dockerFileLocation = '/Dockerfile';
|
||||
}
|
||||
|
||||
return {
|
||||
buildPack,
|
||||
@ -118,7 +125,8 @@ export const setDefaultConfiguration = async (data) => {
|
||||
startCommand,
|
||||
buildCommand,
|
||||
publishDirectory,
|
||||
baseDirectory
|
||||
baseDirectory,
|
||||
dockerFileLocation
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -10,15 +10,16 @@ export default async function ({
|
||||
buildId,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId
|
||||
pullmergeRequestId,
|
||||
dockerFileLocation
|
||||
}) {
|
||||
try {
|
||||
let file = `${workdir}/Dockerfile`;
|
||||
const file = `${workdir}${dockerFileLocation}`;
|
||||
let dockerFileOut = `${workdir}`;
|
||||
if (baseDirectory) {
|
||||
file = `${workdir}/${baseDirectory}/Dockerfile`;
|
||||
workdir = `${workdir}/${baseDirectory}`;
|
||||
dockerFileOut = `${workdir}${baseDirectory}`;
|
||||
workdir = `${workdir}${baseDirectory}`;
|
||||
}
|
||||
|
||||
const Dockerfile: Array<string> = (await fs.readFile(`${file}`, 'utf8'))
|
||||
.toString()
|
||||
.trim()
|
||||
@ -41,8 +42,8 @@ export default async function ({
|
||||
}
|
||||
});
|
||||
}
|
||||
await fs.writeFile(`${file}`, Dockerfile.join('\n'));
|
||||
await buildImage({ applicationId, tag, workdir, docker, buildId, debug });
|
||||
await fs.writeFile(`${dockerFileOut}${dockerFileLocation}`, Dockerfile.join('\n'));
|
||||
await buildImage({ applicationId, tag, workdir, docker, buildId, debug, dockerFileLocation });
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
@ -61,14 +61,12 @@ export const saveBuildLog = async ({
|
||||
buildId: string;
|
||||
applicationId: string;
|
||||
}): Promise<Job> => {
|
||||
if (line) {
|
||||
if (line.includes('ghs_')) {
|
||||
const regex = /ghs_.*@/g;
|
||||
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
||||
}
|
||||
const addTimestamp = `${generateTimestamp()} ${line}`;
|
||||
return await buildLogQueue.add(buildId, { buildId, line: addTimestamp, applicationId });
|
||||
if (line && typeof line === 'string' && line.includes('ghs_')) {
|
||||
const regex = /ghs_.*@/g;
|
||||
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
||||
}
|
||||
const addTimestamp = `${generateTimestamp()} ${line}`;
|
||||
return await buildLogQueue.add(buildId, { buildId, line: addTimestamp, applicationId });
|
||||
};
|
||||
|
||||
export const getTeam = (event: RequestEvent): string | null => {
|
||||
|
@ -263,7 +263,8 @@ export async function configureApplication({
|
||||
publishDirectory,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable
|
||||
pythonVariable,
|
||||
dockerFileLocation
|
||||
}: {
|
||||
id: string;
|
||||
buildPack: string;
|
||||
@ -278,6 +279,7 @@ export async function configureApplication({
|
||||
pythonWSGI: string;
|
||||
pythonModule: string;
|
||||
pythonVariable: string;
|
||||
dockerFileLocation: string;
|
||||
}): Promise<Application> {
|
||||
return await prisma.application.update({
|
||||
where: { id },
|
||||
@ -293,7 +295,8 @@ export async function configureApplication({
|
||||
publishDirectory,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable
|
||||
pythonVariable,
|
||||
dockerFileLocation
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -27,25 +27,35 @@ export async function newService({
|
||||
|
||||
export async function getService({ id, teamId }: { id: string; teamId: string }): Promise<Service> {
|
||||
let body;
|
||||
const include = {
|
||||
destinationDocker: true,
|
||||
plausibleAnalytics: true,
|
||||
minio: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true,
|
||||
ghost: true,
|
||||
serviceSecret: true,
|
||||
meiliSearch: true
|
||||
};
|
||||
if (teamId === '0') {
|
||||
body = await prisma.service.findFirst({
|
||||
where: { id },
|
||||
include
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
plausibleAnalytics: true,
|
||||
minio: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true,
|
||||
ghost: true,
|
||||
serviceSecret: true,
|
||||
meiliSearch: true,
|
||||
persistentStorage: true
|
||||
}
|
||||
});
|
||||
} else {
|
||||
body = await prisma.service.findFirst({
|
||||
where: { id, teams: { some: { id: teamId } } },
|
||||
include
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
plausibleAnalytics: true,
|
||||
minio: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true,
|
||||
ghost: true,
|
||||
serviceSecret: true,
|
||||
meiliSearch: true,
|
||||
persistentStorage: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -362,6 +372,7 @@ export async function updateGhostService({
|
||||
}
|
||||
|
||||
export async function removeService({ id }: { id: string }): Promise<void> {
|
||||
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
||||
|
@ -85,7 +85,8 @@ export async function buildImage({
|
||||
docker,
|
||||
buildId,
|
||||
isCache = false,
|
||||
debug = false
|
||||
debug = false,
|
||||
dockerFileLocation = '/Dockerfile'
|
||||
}) {
|
||||
if (isCache) {
|
||||
await saveBuildLog({ line: `Building cache image started.`, buildId, applicationId });
|
||||
@ -103,7 +104,7 @@ export async function buildImage({
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: workdir },
|
||||
{
|
||||
dockerfile: isCache ? 'Dockerfile-cache' : 'Dockerfile',
|
||||
dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation,
|
||||
t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||
}
|
||||
);
|
||||
|
@ -10,6 +10,9 @@ import { promises as dns } from 'dns';
|
||||
|
||||
export async function letsEncrypt(domain: string, id?: string, isCoolify = false): Promise<void> {
|
||||
try {
|
||||
const certbotImage =
|
||||
process.arch === 'x64' ? 'certbot/certbot' : 'certbot/certbot:arm64v8-latest';
|
||||
|
||||
const data = await db.prisma.setting.findFirst();
|
||||
const { minPort, maxPort } = data;
|
||||
|
||||
@ -63,7 +66,7 @@ export async function letsEncrypt(domain: string, id?: string, isCoolify = false
|
||||
if (found) return;
|
||||
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" ${certbotImage} --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||
dev ? '--test-cert' : ''
|
||||
}`
|
||||
);
|
||||
@ -83,7 +86,7 @@ export async function letsEncrypt(domain: string, id?: string, isCoolify = false
|
||||
}
|
||||
if (found) return;
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${domain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" ${certbotImage} --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${domain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||
dev ? '--test-cert' : ''
|
||||
}`
|
||||
);
|
||||
|
@ -56,7 +56,8 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory
|
||||
publishDirectory,
|
||||
dockerFileLocation
|
||||
} = job.data;
|
||||
const { debug } = settings;
|
||||
|
||||
@ -107,6 +108,7 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
||||
buildCommand = configuration.buildCommand;
|
||||
publishDirectory = configuration.publishDirectory;
|
||||
baseDirectory = configuration.baseDirectory;
|
||||
dockerFileLocation = configuration.dockerFileLocation;
|
||||
|
||||
const commit = await importers[gitSource.type]({
|
||||
applicationId,
|
||||
@ -209,7 +211,8 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
||||
phpModules,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable
|
||||
pythonVariable,
|
||||
dockerFileLocation
|
||||
});
|
||||
else {
|
||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||
@ -286,6 +289,9 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
||||
labels,
|
||||
depends_on: [],
|
||||
restart: 'always',
|
||||
// logging: {
|
||||
// driver: 'fluentd',
|
||||
// },
|
||||
deploy: {
|
||||
restart_policy: {
|
||||
condition: 'on-failure',
|
||||
|
@ -21,6 +21,7 @@ export type BuilderJob = {
|
||||
pythonWSGI: string;
|
||||
pythonModule: string;
|
||||
pythonVariable: string;
|
||||
dockerFileLocation: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
destinationDockerId: string;
|
||||
|
@ -56,7 +56,8 @@ export const post: RequestHandler = async (event) => {
|
||||
publishDirectory,
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable
|
||||
pythonVariable,
|
||||
dockerFileLocation
|
||||
} = await event.request.json();
|
||||
if (port) port = Number(port);
|
||||
|
||||
@ -68,7 +69,8 @@ export const post: RequestHandler = async (event) => {
|
||||
startCommand,
|
||||
buildCommand,
|
||||
publishDirectory,
|
||||
baseDirectory
|
||||
baseDirectory,
|
||||
dockerFileLocation
|
||||
});
|
||||
await db.configureApplication({
|
||||
id,
|
||||
@ -84,6 +86,7 @@ export const post: RequestHandler = async (event) => {
|
||||
pythonWSGI,
|
||||
pythonModule,
|
||||
pythonVariable,
|
||||
dockerFileLocation,
|
||||
...defaultConfiguration
|
||||
});
|
||||
return { status: 201 };
|
||||
|
@ -68,11 +68,6 @@
|
||||
value: 'Gunicorn',
|
||||
label: 'Gunicorn'
|
||||
}
|
||||
// },
|
||||
// {
|
||||
// value: 'uWSGI',
|
||||
// label: 'uWSGI'
|
||||
// }
|
||||
];
|
||||
|
||||
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
||||
@ -420,6 +415,23 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if application.buildPack === 'docker'}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="dockerFileLocation" class="text-base font-bold text-stone-100"
|
||||
>Dockerfile Location</label
|
||||
>
|
||||
<input
|
||||
readonly={!$session.isAdmin}
|
||||
name="dockerFileLocation"
|
||||
id="dockerFileLocation"
|
||||
bind:value={application.dockerFileLocation}
|
||||
placeholder="default: /Dockerfile"
|
||||
/>
|
||||
<Explainer
|
||||
text="Does not rely on Base Directory. <br>Should be absolute path, like <span class='text-green-500 font-bold'>/data/Dockerfile</span> or <span class='text-green-500 font-bold'>/Dockerfile.</span>"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<div class="flex-col">
|
||||
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
|
||||
|
@ -27,6 +27,7 @@ export const get: RequestHandler = async (event) => {
|
||||
.split('\n')
|
||||
.map((l) => l.slice(8))
|
||||
.filter((a) => a)
|
||||
.reverse()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -24,19 +24,23 @@
|
||||
export let application;
|
||||
import { page } from '$app/stores';
|
||||
import LoadingLogs from './_Loading.svelte';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { get } from '$lib/api';
|
||||
import { errorNotification } from '$lib/form';
|
||||
|
||||
let loadLogsInterval = null;
|
||||
let allLogs = {
|
||||
logs: []
|
||||
};
|
||||
let logs = [];
|
||||
let followingBuild;
|
||||
let currentPage = 1;
|
||||
let endOfLogs = false;
|
||||
let startOfLogs = true;
|
||||
let followingInterval;
|
||||
let logsEl;
|
||||
|
||||
const { id } = $page.params;
|
||||
onMount(async () => {
|
||||
loadLogs();
|
||||
loadAllLogs();
|
||||
loadLogsInterval = setInterval(() => {
|
||||
loadLogs();
|
||||
}, 1000);
|
||||
@ -45,25 +49,55 @@
|
||||
clearInterval(loadLogsInterval);
|
||||
clearInterval(followingInterval);
|
||||
});
|
||||
async function loadLogs() {
|
||||
async function loadAllLogs() {
|
||||
try {
|
||||
const newLogs = await get(`/applications/${id}/logs.json`);
|
||||
logs = newLogs.logs;
|
||||
const data: any = await get(`/applications/${id}/logs.json`);
|
||||
allLogs = data.logs;
|
||||
logs = data.logs.slice(0, 100);
|
||||
if (logs.length < 100) {
|
||||
endOfLogs = true;
|
||||
}
|
||||
return;
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
|
||||
function followBuild() {
|
||||
followingBuild = !followingBuild;
|
||||
if (followingBuild) {
|
||||
followingInterval = setInterval(() => {
|
||||
logsEl.scrollTop = logsEl.scrollHeight;
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}, 100);
|
||||
async function loadLogs() {
|
||||
try {
|
||||
const newLogs = await get(`/applications/${id}/logs.json`);
|
||||
logs = newLogs.logs.slice(0, 100);
|
||||
return;
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
async function loadOlderLogs() {
|
||||
clearInterval(loadLogsInterval);
|
||||
loadLogsInterval = null;
|
||||
logsEl.scrollTop = 0;
|
||||
if (logs.length < 100) {
|
||||
endOfLogs = true;
|
||||
return;
|
||||
}
|
||||
startOfLogs = false;
|
||||
endOfLogs = false;
|
||||
currentPage += 1;
|
||||
logs = allLogs.slice(currentPage * 100 - 100, currentPage * 100);
|
||||
}
|
||||
async function loadNewerLogs() {
|
||||
currentPage -= 1;
|
||||
logsEl.scrollTop = 0;
|
||||
if (currentPage !== 1) {
|
||||
clearInterval(loadLogsInterval);
|
||||
endOfLogs = false;
|
||||
loadLogsInterval = null;
|
||||
logs = allLogs.slice(currentPage * 100 - 100, currentPage * 100);
|
||||
} else {
|
||||
window.clearInterval(followingInterval);
|
||||
startOfLogs = true;
|
||||
loadLogs();
|
||||
loadLogsInterval = setInterval(() => {
|
||||
loadLogs();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -145,13 +179,18 @@
|
||||
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
|
||||
{:else}
|
||||
<div class="relative w-full">
|
||||
<LoadingLogs />
|
||||
<div class="flex justify-end sticky top-0 p-2">
|
||||
<div class="text-right " />
|
||||
{#if loadLogsInterval}
|
||||
<LoadingLogs />
|
||||
{/if}
|
||||
<div class="flex justify-end sticky top-0 p-2 mx-1">
|
||||
<button
|
||||
on:click={followBuild}
|
||||
class="bg-transparent"
|
||||
data-tooltip="Follow logs"
|
||||
class:text-green-500={followingBuild}
|
||||
on:click={loadOlderLogs}
|
||||
class:text-coolgray-100={endOfLogs}
|
||||
class:hover:bg-coolgray-400={!endOfLogs}
|
||||
class="bg-transparent tooltip-bottom"
|
||||
data-tooltip="Older logs"
|
||||
disabled={endOfLogs}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -164,10 +203,33 @@
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<line x1="8" y1="12" x2="12" y2="16" />
|
||||
<line x1="12" y1="8" x2="12" y2="16" />
|
||||
<line x1="16" y1="12" x2="12" y2="16" />
|
||||
<path
|
||||
d="M20 15h-8v3.586a1 1 0 0 1 -1.707 .707l-6.586 -6.586a1 1 0 0 1 0 -1.414l6.586 -6.586a1 1 0 0 1 1.707 .707v3.586h8a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
on:click={loadNewerLogs}
|
||||
class:text-coolgray-100={startOfLogs}
|
||||
class:hover:bg-coolgray-400={!startOfLogs}
|
||||
class="bg-transparent tooltip-bottom"
|
||||
data-tooltip="Newer logs"
|
||||
disabled={startOfLogs}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M4 9h8v-3.586a1 1 0 0 1 1.707 -.707l6.586 6.586a1 1 0 0 1 0 1.414l-6.586 6.586a1 1 0 0 1 -1.707 -.707v-3.586h-8a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@ -175,7 +237,7 @@
|
||||
class="font-mono w-full leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
||||
bind:this={logsEl}
|
||||
>
|
||||
<div class="px-2">
|
||||
<div class="px-2 pr-14">
|
||||
{#each logs as log}
|
||||
{log + '\n'}
|
||||
{/each}
|
||||
|
@ -43,8 +43,15 @@
|
||||
{:else}
|
||||
<div class="flex justify-center px-4">
|
||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2">
|
||||
<div class="text-6xl font-bold border-gradient w-48 mx-auto border-b-4">Coolify</div>
|
||||
<div class="text-xs text-center font-bold pb-10">v{$session.version}</div>
|
||||
{#if $session.whiteLabelDetails.icon}
|
||||
<img
|
||||
class="w-32 mx-auto pb-8"
|
||||
src={$session.whiteLabelDetails.icon}
|
||||
alt="Icon for white labeled version of Coolify"
|
||||
/>
|
||||
{:else}
|
||||
<div class="text-6xl font-bold border-gradient w-48 mx-auto border-b-4 mb-8">Coolify</div>
|
||||
{/if}
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
|
18
src/routes/logs.json.ts
Normal file
18
src/routes/logs.json.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import * as db from '$lib/database';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const data = await event.request.json();
|
||||
for (const d of data) {
|
||||
if (d.container_name) {
|
||||
const { log, container_name: containerId, source } = d;
|
||||
console.log(log);
|
||||
// await db.prisma.applicationLogs.create({ data: { log, containerId: containerId.substr(1), source } });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {}
|
||||
};
|
||||
};
|
@ -64,8 +64,15 @@
|
||||
{:else}
|
||||
<div class="flex justify-center px-4">
|
||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2">
|
||||
<div class="text-6xl font-bold border-gradient w-48 mx-auto border-b-4">Coolify</div>
|
||||
<div class="text-xs text-center font-bold pb-10">v{$session.version}</div>
|
||||
{#if $session.whiteLabelDetails.icon}
|
||||
<img
|
||||
class="w-32 mx-auto pb-8"
|
||||
src={$session.whiteLabelDetails.icon}
|
||||
alt="Icon for white labeled version of Coolify"
|
||||
/>
|
||||
{:else}
|
||||
<div class="text-6xl font-bold border-gradient w-48 mx-auto border-b-4 mb-8">Coolify</div>
|
||||
{/if}
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
|
@ -239,6 +239,35 @@
|
||||
</svg></button
|
||||
></a
|
||||
>
|
||||
<a
|
||||
href="/services/{id}/storage"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-pink-500 rounded"
|
||||
class:text-pink-500={$page.url.pathname === `/services/${id}/storage`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/storage`}
|
||||
>
|
||||
<button
|
||||
title="Persistent Storage"
|
||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
||||
data-tooltip="Persistent Storage"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
||||
</svg>
|
||||
</button></a
|
||||
>
|
||||
<div class="border border-stone-700 h-8" />
|
||||
{/if}
|
||||
<button
|
||||
|
@ -135,11 +135,7 @@ export const post: RequestHandler = async (event) => {
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
@ -69,11 +69,7 @@ export const post: RequestHandler = async (event) => {
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
@ -74,11 +74,7 @@ export const post: RequestHandler = async (event) => {
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
@ -88,6 +88,7 @@ export const post: RequestHandler = async (event) => {
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
await db.updateMinioService({ id, publicPort });
|
||||
await startHttpProxy(destinationDocker, id, publicPort, apiPort);
|
||||
|
@ -70,11 +70,7 @@ export const post: RequestHandler = async (event) => {
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
@ -61,11 +61,7 @@ export const post: RequestHandler = async (event) => {
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
@ -192,9 +192,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
|
||||
);
|
||||
|
73
src/routes/services/[id]/storage/_Storage.svelte
Normal file
73
src/routes/services/[id]/storage/_Storage.svelte
Normal file
@ -0,0 +1,73 @@
|
||||
<script lang="ts">
|
||||
export let isNew = false;
|
||||
export let storage = {
|
||||
id: null,
|
||||
path: null
|
||||
};
|
||||
import { del, post } from '$lib/api';
|
||||
import { page } from '$app/stores';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
const { id } = $page.params;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
async function saveStorage(newStorage = false) {
|
||||
try {
|
||||
if (!storage.path) return errorNotification('Path is required.');
|
||||
storage.path = storage.path.startsWith('/') ? storage.path : `/${storage.path}`;
|
||||
storage.path = storage.path.endsWith('/') ? storage.path.slice(0, -1) : storage.path;
|
||||
storage.path.replace(/\/\//g, '/');
|
||||
await post(`/services/${id}/storage.json`, {
|
||||
path: storage.path,
|
||||
storageId: storage.id,
|
||||
newStorage
|
||||
});
|
||||
dispatch('refresh');
|
||||
if (isNew) {
|
||||
storage.path = null;
|
||||
storage.id = null;
|
||||
}
|
||||
if (newStorage) toast.push('Storage saved.');
|
||||
else toast.push('Storage updated.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
async function removeStorage() {
|
||||
try {
|
||||
await del(`/services/${id}/storage.json`, { path: storage.path });
|
||||
dispatch('refresh');
|
||||
toast.push('Storage deleted.');
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<td>
|
||||
<input
|
||||
bind:value={storage.path}
|
||||
required
|
||||
placeholder="eg: /data"
|
||||
class=" border border-dashed border-coolgray-300"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{#if isNew}
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveStorage(true)}>Add</button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row justify-center space-x-2">
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="" on:click={() => saveStorage(false)}>Set</button>
|
||||
</div>
|
||||
<div class="flex justify-center items-end">
|
||||
<button class="bg-red-600 hover:bg-red-500" on:click={removeStorage}>Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
65
src/routes/services/[id]/storage/index.json.ts
Normal file
65
src/routes/services/[id]/storage/index.json.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
|
||||
export const get: RequestHandler = async (event) => {
|
||||
const { status, body, teamId } = await getUserDetails(event, false);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
try {
|
||||
const persistentStorages = await db.prisma.servicePersistentStorage.findMany({
|
||||
where: { serviceId: id }
|
||||
});
|
||||
return {
|
||||
body: {
|
||||
persistentStorages
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { path, newStorage, storageId } = await event.request.json();
|
||||
try {
|
||||
if (newStorage) {
|
||||
await db.prisma.servicePersistentStorage.create({
|
||||
data: { path, service: { connect: { id } } }
|
||||
});
|
||||
} else {
|
||||
await db.prisma.servicePersistentStorage.update({
|
||||
where: { id: storageId },
|
||||
data: { path }
|
||||
});
|
||||
}
|
||||
return {
|
||||
status: 201
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const del: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const { path } = await event.request.json();
|
||||
|
||||
try {
|
||||
await db.prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id, path } });
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
102
src/routes/services/[id]/storage/index.svelte
Normal file
102
src/routes/services/[id]/storage/index.svelte
Normal file
@ -0,0 +1,102 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export const load: Load = async ({ fetch, params, stuff }) => {
|
||||
let endpoint = `/services/${params.id}/storage.json`;
|
||||
const res = await fetch(endpoint);
|
||||
if (res.ok) {
|
||||
return {
|
||||
props: {
|
||||
service: stuff.service,
|
||||
...(await res.json())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
error: new Error(`Could not load ${endpoint}`)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let service;
|
||||
|
||||
export let persistentStorages;
|
||||
import { page } from '$app/stores';
|
||||
import Storage from './_Storage.svelte';
|
||||
import { get } from '$lib/api';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import ServiceLinks from '$lib/components/ServiceLinks.svelte';
|
||||
|
||||
const { id } = $page.params;
|
||||
async function refreshStorage() {
|
||||
const data = await get(`/services/${id}/storage.json`);
|
||||
persistentStorages = [...data.persistentStorages];
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex items-center space-x-2 p-5 px-6 font-bold"
|
||||
class:p-5={service.fqdn}
|
||||
class:p-6={!service.fqdn}
|
||||
>
|
||||
<div class="-mb-5 flex-col">
|
||||
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
|
||||
Persistent Storage
|
||||
</div>
|
||||
<span class="text-xs">{service.name}</span>
|
||||
</div>
|
||||
{#if service.fqdn}
|
||||
<a
|
||||
href={service.fqdn}
|
||||
target="_blank"
|
||||
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
|
||||
<line x1="10" y1="14" x2="20" y2="4" />
|
||||
<polyline points="15 4 20 4 20 9" />
|
||||
</svg></a
|
||||
>
|
||||
{/if}
|
||||
|
||||
<ServiceLinks {service} />
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||
<div class="flex justify-center py-4 text-center">
|
||||
<Explainer
|
||||
customClass="w-full"
|
||||
text={'You can specify any folder that you want to be persistent across restarts. <br>This is useful for storing data for VSCode server or WordPress.'}
|
||||
/>
|
||||
</div>
|
||||
<table class="mx-auto border-separate text-left">
|
||||
<thead>
|
||||
<tr class="h-12">
|
||||
<th scope="col">Path</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each persistentStorages as storage}
|
||||
{#key storage.id}
|
||||
<tr>
|
||||
<Storage on:refresh={refreshStorage} {storage} />
|
||||
</tr>
|
||||
{/key}
|
||||
{/each}
|
||||
<tr>
|
||||
<Storage on:refresh={refreshStorage} isNew />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
@ -68,11 +68,7 @@ export const post: RequestHandler = async (event) => {
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
@ -68,11 +68,7 @@ export const post: RequestHandler = async (event) => {
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
@ -21,6 +21,7 @@ export const post: RequestHandler = async (event) => {
|
||||
destinationDockerId,
|
||||
destinationDocker,
|
||||
serviceSecret,
|
||||
persistentStorage,
|
||||
vscodeserver: { password }
|
||||
} = service;
|
||||
|
||||
@ -42,6 +43,28 @@ export const post: RequestHandler = async (event) => {
|
||||
config.environmentVariables[secret.name] = secret.value;
|
||||
});
|
||||
}
|
||||
|
||||
const volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
return `${id}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||
}) || [];
|
||||
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
}
|
||||
};
|
||||
});
|
||||
const volumeMounts = Object.assign(
|
||||
{},
|
||||
{
|
||||
[config.volume.split(':')[0]]: {
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
},
|
||||
...composeVolumes
|
||||
);
|
||||
const composeFile: ComposeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
@ -50,7 +73,7 @@ export const post: RequestHandler = async (event) => {
|
||||
image: config.image,
|
||||
environment: config.environmentVariables,
|
||||
networks: [network],
|
||||
volumes: [config.volume],
|
||||
volumes: [config.volume, ...volumes],
|
||||
restart: 'always',
|
||||
labels: makeLabelForServices('vscodeServer'),
|
||||
deploy: {
|
||||
@ -68,19 +91,21 @@ export const post: RequestHandler = async (event) => {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[config.volume.split(':')[0]]: {
|
||||
name: config.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
volumes: volumeMounts
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
|
||||
const changePermissionOn = persistentStorage.map((p) => p.path);
|
||||
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker exec -u root ${id} chown -R 1000:1000 ${changePermissionOn.join(
|
||||
' '
|
||||
)}`
|
||||
);
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
|
@ -121,11 +121,7 @@ export const post: RequestHandler = async (event) => {
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
try {
|
||||
if (version === 'latest') {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
||||
);
|
||||
}
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
return {
|
||||
status: 200
|
||||
|
Loading…
x
Reference in New Issue
Block a user