mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 20:49:28 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
397ca7f20e | ||
|
|
e10b76a46b | ||
|
|
b46566280d | ||
|
|
3ab6a231eb | ||
|
|
2b28f8bd8f | ||
|
|
625e71ab08 | ||
|
|
b0af54587b | ||
|
|
be3080df08 | ||
|
|
04685c9f9d | ||
|
|
1a83f2635f | ||
|
|
630aa45c87 | ||
|
|
0c3a381d1f | ||
|
|
ffac7c5c87 | ||
|
|
410800e81c | ||
|
|
9481beb61f | ||
|
|
141f2481a7 | ||
|
|
ea18f25adc | ||
|
|
9018184747 | ||
|
|
4fc2dd55f5 | ||
|
|
5ef9a282eb | ||
|
|
56b9a376bd | ||
|
|
0a1d31a188 | ||
|
|
64c9fb9a1b | ||
|
|
47aad15cd5 | ||
|
|
260a47a366 | ||
|
|
fd4bbe17f0 | ||
|
|
25ff637703 | ||
|
|
f571453696 | ||
|
|
5cd7533972 | ||
|
|
3a252509d0 | ||
|
|
2bd3802a6f | ||
|
|
ce2757f514 | ||
|
|
8419cdf604 | ||
|
|
907c2414ae | ||
|
|
f82207564f | ||
|
|
991a09838c | ||
|
|
25df4bfd85 | ||
|
|
d2f89d001b | ||
|
|
1971f227fd |
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>
|
||||||
@@ -2,10 +2,8 @@ version: '3.8'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
redis:
|
redis:
|
||||||
image: 'bitnami/redis:6.2'
|
image: redis:6.2-alpine
|
||||||
container_name: coolify-redis
|
container_name: coolify-redis
|
||||||
environment:
|
|
||||||
- ALLOW_EMPTY_PASSWORD=yes
|
|
||||||
networks:
|
networks:
|
||||||
- coolify-infra
|
- coolify-infra
|
||||||
ports:
|
ports:
|
||||||
@@ -13,7 +11,24 @@ services:
|
|||||||
published: 6379
|
published: 6379
|
||||||
protocol: tcp
|
protocol: tcp
|
||||||
mode: host
|
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:
|
networks:
|
||||||
coolify-infra:
|
coolify-infra:
|
||||||
attachable: true
|
attachable: true
|
||||||
|
|||||||
@@ -21,11 +21,9 @@ services:
|
|||||||
- coolify-infra
|
- coolify-infra
|
||||||
depends_on: ['redis']
|
depends_on: ['redis']
|
||||||
redis:
|
redis:
|
||||||
image: bitnami/redis:6.2
|
image: redis:6.2-alpine
|
||||||
restart: always
|
restart: always
|
||||||
container_name: coolify-redis
|
container_name: coolify-redis
|
||||||
environment:
|
|
||||||
- ALLOW_EMPTY_PASSWORD=yes
|
|
||||||
networks:
|
networks:
|
||||||
- coolify-infra
|
- coolify-infra
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "2.4.7",
|
"version": "2.4.11",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"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:stop": "docker-compose -f docker-compose-dev.yaml down",
|
||||||
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
|
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
|
||||||
"studio": "npx prisma studio",
|
"studio": "npx prisma studio",
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@prisma/client": "3.11.1",
|
"@prisma/client": "3.11.1",
|
||||||
"@sentry/node": "6.19.6",
|
"@sentry/node": "6.19.6",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bullmq": "1.80.0",
|
"bullmq": "1.80.0",
|
||||||
"compare-versions": "4.1.3",
|
"compare-versions": "4.1.3",
|
||||||
"cookie": "0.4.2",
|
"cookie": "0.4.2",
|
||||||
|
|||||||
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@@ -5,7 +5,7 @@ specifiers:
|
|||||||
'@prisma/client': 3.11.1
|
'@prisma/client': 3.11.1
|
||||||
'@sentry/node': 6.19.6
|
'@sentry/node': 6.19.6
|
||||||
'@sveltejs/adapter-node': 1.0.0-next.73
|
'@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-cookie': 3.0.1
|
||||||
'@types/js-yaml': 4.0.5
|
'@types/js-yaml': 4.0.5
|
||||||
'@types/node': 17.0.23
|
'@types/node': 17.0.23
|
||||||
@@ -14,8 +14,8 @@ specifiers:
|
|||||||
'@typescript-eslint/parser': 4.31.1
|
'@typescript-eslint/parser': 4.31.1
|
||||||
'@zerodevx/svelte-toast': 0.7.1
|
'@zerodevx/svelte-toast': 0.7.1
|
||||||
autoprefixer: 10.4.4
|
autoprefixer: 10.4.4
|
||||||
bcryptjs: ^2.4.3
|
bcryptjs: 2.4.3
|
||||||
bullmq: 1.78.1
|
bullmq: 1.80.0
|
||||||
compare-versions: 4.1.3
|
compare-versions: 4.1.3
|
||||||
cookie: 0.4.2
|
cookie: 0.4.2
|
||||||
cross-env: 7.0.3
|
cross-env: 7.0.3
|
||||||
@@ -60,7 +60,7 @@ dependencies:
|
|||||||
'@prisma/client': 3.11.1_prisma@3.11.1
|
'@prisma/client': 3.11.1_prisma@3.11.1
|
||||||
'@sentry/node': 6.19.6
|
'@sentry/node': 6.19.6
|
||||||
bcryptjs: 2.4.3
|
bcryptjs: 2.4.3
|
||||||
bullmq: 1.78.1
|
bullmq: 1.80.0
|
||||||
compare-versions: 4.1.3
|
compare-versions: 4.1.3
|
||||||
cookie: 0.4.2
|
cookie: 0.4.2
|
||||||
cuid: 2.1.8
|
cuid: 2.1.8
|
||||||
@@ -82,7 +82,7 @@ dependencies:
|
|||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@sveltejs/adapter-node': 1.0.0-next.73
|
'@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-cookie': 3.0.1
|
||||||
'@types/js-yaml': 4.0.5
|
'@types/js-yaml': 4.0.5
|
||||||
'@types/node': 17.0.23
|
'@types/node': 17.0.23
|
||||||
@@ -374,10 +374,10 @@ packages:
|
|||||||
tiny-glob: 0.2.9
|
tiny-glob: 0.2.9
|
||||||
dev: true
|
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:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-WdxDc8OiF1WEd/bEza7CBdzA+3qIcCi1GKBj/gieKX9I3N8iDJt/Cg2POrLo9wQoJ47nZcAd1eOhfr7XEX1aIQ==
|
integrity: sha512-pTyMyaoyHS+V5cQZIQMfQXmLkhw1VaRwT9avOSgwDc0QBpnNw2LdzwoPYsUr96ca5B6cfT3SMUNolxErTNHmPQ==
|
||||||
}
|
}
|
||||||
engines: { node: '>=14.13' }
|
engines: { node: '>=14.13' }
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -1669,10 +1669,10 @@ packages:
|
|||||||
ieee754: 1.2.1
|
ieee754: 1.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/bullmq/1.78.1:
|
/bullmq/1.80.0:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-er45mM8nGhgA83EVCJ4PNxPyDSzakvoxeFGU4vdSgYeB+SbeFQAlJYmAC50Ms7YFPstm1LeinbVZ+oX/BmBzOg==
|
integrity: sha512-oz7GZIg7gAGIIlLQ3KdpYSA5WSz5205pQHyGwOtQof9MmkOf+Kmo6sxqr+BiQrjhFOrB6JLSCqS3EGEbMA34MA==
|
||||||
}
|
}
|
||||||
dependencies:
|
dependencies:
|
||||||
cron-parser: 4.2.1
|
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 {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
binaryTargets = ["linux-musl"]
|
binaryTargets = ["native", "linux-musl"]
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
@@ -91,6 +91,7 @@ model Application {
|
|||||||
pythonWSGI String?
|
pythonWSGI String?
|
||||||
pythonModule String?
|
pythonModule String?
|
||||||
pythonVariable String?
|
pythonVariable String?
|
||||||
|
dockerFileLocation String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
settings ApplicationSettings?
|
settings ApplicationSettings?
|
||||||
@@ -118,14 +119,25 @@ model ApplicationSettings {
|
|||||||
model ApplicationPersistentStorage {
|
model ApplicationPersistentStorage {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
applicationId String
|
applicationId String
|
||||||
path String
|
path String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@unique([applicationId, path])
|
@@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 {
|
model Secret {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
@@ -267,17 +279,17 @@ model DatabaseSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Service {
|
model Service {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
fqdn String?
|
fqdn String?
|
||||||
dualCerts Boolean @default(false)
|
dualCerts Boolean @default(false)
|
||||||
type String?
|
type String?
|
||||||
version String?
|
version String?
|
||||||
teams Team[]
|
teams Team[]
|
||||||
destinationDockerId String?
|
destinationDockerId String?
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
plausibleAnalytics PlausibleAnalytics?
|
plausibleAnalytics PlausibleAnalytics?
|
||||||
minio Minio?
|
minio Minio?
|
||||||
vscodeserver Vscodeserver?
|
vscodeserver Vscodeserver?
|
||||||
@@ -285,6 +297,7 @@ model Service {
|
|||||||
ghost Ghost?
|
ghost Ghost?
|
||||||
serviceSecret ServiceSecret[]
|
serviceSecret ServiceSecret[]
|
||||||
meiliSearch MeiliSearch?
|
meiliSearch MeiliSearch?
|
||||||
|
persistentStorage ServicePersistentStorage[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model PlausibleAnalytics {
|
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>;
|
cookies: Record<string, string>;
|
||||||
}
|
}
|
||||||
interface Platform {}
|
interface Platform {}
|
||||||
interface Session extends SessionData {}
|
interface Session extends SessionData {
|
||||||
|
whiteLabelDetails: {
|
||||||
|
icon: string | null;
|
||||||
|
};
|
||||||
|
}
|
||||||
interface Stuff {
|
interface Stuff {
|
||||||
service: any;
|
service: any;
|
||||||
application: any;
|
application: any;
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import cookie from 'cookie';
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
|
|
||||||
const whiteLabeled = process.env['COOLIFY_WHITE_LABELED'] === 'true';
|
const whiteLabeled = process.env['COOLIFY_WHITE_LABELED'] === 'true';
|
||||||
|
const whiteLabelDetails = {
|
||||||
|
icon: (whiteLabeled && process.env['COOLIFY_WHITE_LABELED_ICON']) || null
|
||||||
|
};
|
||||||
|
|
||||||
export const handle = handleSession(
|
export const handle = handleSession(
|
||||||
{
|
{
|
||||||
@@ -74,6 +77,7 @@ export const getSession: GetSession = function ({ locals }) {
|
|||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
whiteLabeled,
|
whiteLabeled,
|
||||||
|
whiteLabelDetails,
|
||||||
...locals.session.data
|
...locals.session.data
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ export const setDefaultConfiguration = async (data) => {
|
|||||||
startCommand,
|
startCommand,
|
||||||
buildCommand,
|
buildCommand,
|
||||||
publishDirectory,
|
publishDirectory,
|
||||||
baseDirectory
|
baseDirectory,
|
||||||
|
dockerFileLocation
|
||||||
} = data;
|
} = data;
|
||||||
const template = scanningTemplates[buildPack];
|
const template = scanningTemplates[buildPack];
|
||||||
if (!port) {
|
if (!port) {
|
||||||
@@ -110,6 +111,12 @@ export const setDefaultConfiguration = async (data) => {
|
|||||||
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
||||||
if (!baseDirectory.endsWith('/')) 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 {
|
return {
|
||||||
buildPack,
|
buildPack,
|
||||||
@@ -118,7 +125,8 @@ export const setDefaultConfiguration = async (data) => {
|
|||||||
startCommand,
|
startCommand,
|
||||||
buildCommand,
|
buildCommand,
|
||||||
publishDirectory,
|
publishDirectory,
|
||||||
baseDirectory
|
baseDirectory,
|
||||||
|
dockerFileLocation
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,16 @@ export default async function ({
|
|||||||
buildId,
|
buildId,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
secrets,
|
secrets,
|
||||||
pullmergeRequestId
|
pullmergeRequestId,
|
||||||
|
dockerFileLocation
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
let file = `${workdir}/Dockerfile`;
|
const file = `${workdir}${dockerFileLocation}`;
|
||||||
|
let dockerFileOut = `${workdir}`;
|
||||||
if (baseDirectory) {
|
if (baseDirectory) {
|
||||||
file = `${workdir}/${baseDirectory}/Dockerfile`;
|
dockerFileOut = `${workdir}${baseDirectory}`;
|
||||||
workdir = `${workdir}/${baseDirectory}`;
|
workdir = `${workdir}${baseDirectory}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dockerfile: Array<string> = (await fs.readFile(`${file}`, 'utf8'))
|
const Dockerfile: Array<string> = (await fs.readFile(`${file}`, 'utf8'))
|
||||||
.toString()
|
.toString()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -41,8 +42,8 @@ export default async function ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${file}`, Dockerfile.join('\n'));
|
await fs.writeFile(`${dockerFileOut}${dockerFileLocation}`, Dockerfile.join('\n'));
|
||||||
await buildImage({ applicationId, tag, workdir, docker, buildId, debug });
|
await buildImage({ applicationId, tag, workdir, docker, buildId, debug, dockerFileLocation });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,14 +61,12 @@ export const saveBuildLog = async ({
|
|||||||
buildId: string;
|
buildId: string;
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}): Promise<Job> => {
|
}): Promise<Job> => {
|
||||||
if (line) {
|
if (line && typeof line === 'string' && line.includes('ghs_')) {
|
||||||
if (line.includes('ghs_')) {
|
const regex = /ghs_.*@/g;
|
||||||
const regex = /ghs_.*@/g;
|
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
||||||
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
|
||||||
}
|
|
||||||
const addTimestamp = `${generateTimestamp()} ${line}`;
|
|
||||||
return await buildLogQueue.add(buildId, { buildId, line: addTimestamp, applicationId });
|
|
||||||
}
|
}
|
||||||
|
const addTimestamp = `${generateTimestamp()} ${line}`;
|
||||||
|
return await buildLogQueue.add(buildId, { buildId, line: addTimestamp, applicationId });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTeam = (event: RequestEvent): string | null => {
|
export const getTeam = (event: RequestEvent): string | null => {
|
||||||
|
|||||||
@@ -263,7 +263,8 @@ export async function configureApplication({
|
|||||||
publishDirectory,
|
publishDirectory,
|
||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable
|
pythonVariable,
|
||||||
|
dockerFileLocation
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
buildPack: string;
|
buildPack: string;
|
||||||
@@ -278,6 +279,7 @@ export async function configureApplication({
|
|||||||
pythonWSGI: string;
|
pythonWSGI: string;
|
||||||
pythonModule: string;
|
pythonModule: string;
|
||||||
pythonVariable: string;
|
pythonVariable: string;
|
||||||
|
dockerFileLocation: string;
|
||||||
}): Promise<Application> {
|
}): Promise<Application> {
|
||||||
return await prisma.application.update({
|
return await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -293,7 +295,8 @@ export async function configureApplication({
|
|||||||
publishDirectory,
|
publishDirectory,
|
||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable
|
pythonVariable,
|
||||||
|
dockerFileLocation
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { default as ProdPrisma } from '@prisma/client';
|
|||||||
import type { Database, DatabaseSettings } from '@prisma/client';
|
import type { Database, DatabaseSettings } from '@prisma/client';
|
||||||
import generator from 'generate-password';
|
import generator from 'generate-password';
|
||||||
import forge from 'node-forge';
|
import forge from 'node-forge';
|
||||||
|
import getPort, { portNumbers } from 'get-port';
|
||||||
|
|
||||||
export function generatePassword(length = 24): string {
|
export function generatePassword(length = 24): string {
|
||||||
return generator.generate({
|
return generator.generate({
|
||||||
@@ -251,3 +252,29 @@ export function generateDatabaseConfiguration(database: Database & { settings: D
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getFreePort() {
|
||||||
|
const data = await prisma.setting.findFirst();
|
||||||
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
|
const dbUsed = await (
|
||||||
|
await prisma.database.findMany({
|
||||||
|
where: { publicPort: { not: null } },
|
||||||
|
select: { publicPort: true }
|
||||||
|
})
|
||||||
|
).map((a) => a.publicPort);
|
||||||
|
const wpFtpUsed = await (
|
||||||
|
await prisma.wordpress.findMany({
|
||||||
|
where: { ftpPublicPort: { not: null } },
|
||||||
|
select: { ftpPublicPort: true }
|
||||||
|
})
|
||||||
|
).map((a) => a.ftpPublicPort);
|
||||||
|
const wpUsed = await (
|
||||||
|
await prisma.wordpress.findMany({
|
||||||
|
where: { mysqlPublicPort: { not: null } },
|
||||||
|
select: { mysqlPublicPort: true }
|
||||||
|
})
|
||||||
|
).map((a) => a.mysqlPublicPort);
|
||||||
|
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed];
|
||||||
|
return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
|
||||||
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export async function addGitLabSource({
|
|||||||
appSecret,
|
appSecret,
|
||||||
groupName
|
groupName
|
||||||
}) {
|
}) {
|
||||||
const encrptedAppSecret = encrypt(appSecret);
|
const encryptedAppSecret = encrypt(appSecret);
|
||||||
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name } });
|
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name } });
|
||||||
return await prisma.gitlabApp.create({
|
return await prisma.gitlabApp.create({
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -27,25 +27,35 @@ export async function newService({
|
|||||||
|
|
||||||
export async function getService({ id, teamId }: { id: string; teamId: string }): Promise<Service> {
|
export async function getService({ id, teamId }: { id: string; teamId: string }): Promise<Service> {
|
||||||
let body;
|
let body;
|
||||||
const include = {
|
|
||||||
destinationDocker: true,
|
|
||||||
plausibleAnalytics: true,
|
|
||||||
minio: true,
|
|
||||||
vscodeserver: true,
|
|
||||||
wordpress: true,
|
|
||||||
ghost: true,
|
|
||||||
serviceSecret: true,
|
|
||||||
meiliSearch: true
|
|
||||||
};
|
|
||||||
if (teamId === '0') {
|
if (teamId === '0') {
|
||||||
body = await prisma.service.findFirst({
|
body = await prisma.service.findFirst({
|
||||||
where: { id },
|
where: { id },
|
||||||
include
|
include: {
|
||||||
|
destinationDocker: true,
|
||||||
|
plausibleAnalytics: true,
|
||||||
|
minio: true,
|
||||||
|
vscodeserver: true,
|
||||||
|
wordpress: true,
|
||||||
|
ghost: true,
|
||||||
|
serviceSecret: true,
|
||||||
|
meiliSearch: true,
|
||||||
|
persistentStorage: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
body = await prisma.service.findFirst({
|
body = await prisma.service.findFirst({
|
||||||
where: { id, teams: { some: { id: teamId } } },
|
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> {
|
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.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
||||||
|
|||||||
@@ -46,8 +46,12 @@ export async function login({
|
|||||||
if (users === 0) {
|
if (users === 0) {
|
||||||
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
|
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
|
||||||
// Create default network & start Coolify Proxy
|
// Create default network & start Coolify Proxy
|
||||||
await asyncExecShell(`docker network create --attachable coolify`);
|
try {
|
||||||
await startCoolifyProxy('/var/run/docker.sock');
|
await asyncExecShell(`docker network create --attachable coolify`);
|
||||||
|
} catch (error) {}
|
||||||
|
try {
|
||||||
|
await startCoolifyProxy('/var/run/docker.sock');
|
||||||
|
} catch (error) {}
|
||||||
uid = '0';
|
uid = '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ export async function buildImage({
|
|||||||
docker,
|
docker,
|
||||||
buildId,
|
buildId,
|
||||||
isCache = false,
|
isCache = false,
|
||||||
debug = false
|
debug = false,
|
||||||
|
dockerFileLocation = '/Dockerfile'
|
||||||
}) {
|
}) {
|
||||||
if (isCache) {
|
if (isCache) {
|
||||||
await saveBuildLog({ line: `Building cache image started.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building cache image started.`, buildId, applicationId });
|
||||||
@@ -103,7 +104,7 @@ export async function buildImage({
|
|||||||
const stream = await docker.engine.buildImage(
|
const stream = await docker.engine.buildImage(
|
||||||
{ src: ['.'], context: workdir },
|
{ src: ['.'], context: workdir },
|
||||||
{
|
{
|
||||||
dockerfile: isCache ? 'Dockerfile-cache' : 'Dockerfile',
|
dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation,
|
||||||
t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -127,10 +127,10 @@ export async function startTcpProxy(
|
|||||||
|
|
||||||
const containerName = `haproxy-for-${publicPort}`;
|
const containerName = `haproxy-for-${publicPort}`;
|
||||||
const found = await checkContainer(engine, containerName);
|
const found = await checkContainer(engine, containerName);
|
||||||
const foundDB = await checkContainer(engine, id);
|
const foundDependentContainer = await checkContainer(engine, id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (foundDB && !found) {
|
if (foundDependentContainer && !found) {
|
||||||
const { stdout: Config } = await asyncExecShell(
|
const { stdout: Config } = await asyncExecShell(
|
||||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||||
);
|
);
|
||||||
@@ -141,6 +141,11 @@ export async function startTcpProxy(
|
|||||||
} -d coollabsio/${defaultProxyImageTcp}`
|
} -d coollabsio/${defaultProxyImageTcp}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!foundDependentContainer && found) {
|
||||||
|
return await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
@@ -157,10 +162,10 @@ export async function startHttpProxy(
|
|||||||
|
|
||||||
const containerName = `haproxy-for-${publicPort}`;
|
const containerName = `haproxy-for-${publicPort}`;
|
||||||
const found = await checkContainer(engine, containerName);
|
const found = await checkContainer(engine, containerName);
|
||||||
const foundDB = await checkContainer(engine, id);
|
const foundDependentContainer = await checkContainer(engine, id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (foundDB && !found) {
|
if (foundDependentContainer && !found) {
|
||||||
const { stdout: Config } = await asyncExecShell(
|
const { stdout: Config } = await asyncExecShell(
|
||||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||||
);
|
);
|
||||||
@@ -169,6 +174,11 @@ export async function startHttpProxy(
|
|||||||
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}`
|
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!foundDependentContainer && found) {
|
||||||
|
return await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import { promises as dns } from 'dns';
|
|||||||
|
|
||||||
export async function letsEncrypt(domain: string, id?: string, isCoolify = false): Promise<void> {
|
export async function letsEncrypt(domain: string, id?: string, isCoolify = false): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
const certbotImage =
|
||||||
|
process.arch === 'x64' ? 'certbot/certbot' : 'certbot/certbot:arm64v8-latest';
|
||||||
|
|
||||||
const data = await db.prisma.setting.findFirst();
|
const data = await db.prisma.setting.findFirst();
|
||||||
const { minPort, maxPort } = data;
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ export async function letsEncrypt(domain: string, id?: string, isCoolify = false
|
|||||||
if (found) return;
|
if (found) return;
|
||||||
|
|
||||||
await asyncExecShell(
|
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' : ''
|
dev ? '--test-cert' : ''
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
@@ -83,7 +86,7 @@ export async function letsEncrypt(domain: string, id?: string, isCoolify = false
|
|||||||
}
|
}
|
||||||
if (found) return;
|
if (found) return;
|
||||||
await asyncExecShell(
|
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' : ''
|
dev ? '--test-cert' : ''
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
@@ -215,7 +218,7 @@ export async function generateSSLCerts(): Promise<void> {
|
|||||||
certificates.includes(ssl.domain) ||
|
certificates.includes(ssl.domain) ||
|
||||||
certificates.includes(ssl.domain.replace('www.', ''))
|
certificates.includes(ssl.domain.replace('www.', ''))
|
||||||
) {
|
) {
|
||||||
console.log(`Certificate for ${ssl.domain} already exists`);
|
// console.log(`Certificate for ${ssl.domain} already exists`);
|
||||||
} else {
|
} else {
|
||||||
// Checking DNS entry before generating certificate
|
// Checking DNS entry before generating certificate
|
||||||
if (ipv4 || ipv6) {
|
if (ipv4 || ipv6) {
|
||||||
@@ -232,7 +235,7 @@ export async function generateSSLCerts(): Promise<void> {
|
|||||||
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
||||||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
||||||
) {
|
) {
|
||||||
console.log('Generating SSL for', ssl.domain, '.');
|
console.log('Generating SSL for', ssl.domain);
|
||||||
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,7 +264,7 @@ export async function generateSSLCerts(): Promise<void> {
|
|||||||
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
||||||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
||||||
) {
|
) {
|
||||||
console.log('Generating SSL for', ssl.domain, '.');
|
console.log('Generating SSL for', ssl.domain);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
|||||||
buildCommand,
|
buildCommand,
|
||||||
startCommand,
|
startCommand,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
publishDirectory
|
publishDirectory,
|
||||||
|
dockerFileLocation
|
||||||
} = job.data;
|
} = job.data;
|
||||||
const { debug } = settings;
|
const { debug } = settings;
|
||||||
|
|
||||||
@@ -107,6 +108,7 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
|||||||
buildCommand = configuration.buildCommand;
|
buildCommand = configuration.buildCommand;
|
||||||
publishDirectory = configuration.publishDirectory;
|
publishDirectory = configuration.publishDirectory;
|
||||||
baseDirectory = configuration.baseDirectory;
|
baseDirectory = configuration.baseDirectory;
|
||||||
|
dockerFileLocation = configuration.dockerFileLocation;
|
||||||
|
|
||||||
const commit = await importers[gitSource.type]({
|
const commit = await importers[gitSource.type]({
|
||||||
applicationId,
|
applicationId,
|
||||||
@@ -209,7 +211,8 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
|||||||
phpModules,
|
phpModules,
|
||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable
|
pythonVariable,
|
||||||
|
dockerFileLocation
|
||||||
});
|
});
|
||||||
else {
|
else {
|
||||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
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,
|
labels,
|
||||||
depends_on: [],
|
depends_on: [],
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
|
// logging: {
|
||||||
|
// driver: 'fluentd',
|
||||||
|
// },
|
||||||
deploy: {
|
deploy: {
|
||||||
restart_policy: {
|
restart_policy: {
|
||||||
condition: 'on-failure',
|
condition: 'on-failure',
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { asyncExecShell, getEngine, version } from '$lib/common';
|
|||||||
import { prisma } from '$lib/database';
|
import { prisma } from '$lib/database';
|
||||||
export default async function (): Promise<void> {
|
export default async function (): Promise<void> {
|
||||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||||
for (const destinationDocker of destinationDockers) {
|
const engines = [...new Set(destinationDockers.map(({ engine }) => engine))];
|
||||||
const host = getEngine(destinationDocker.engine);
|
for (const engine of engines) {
|
||||||
|
const host = getEngine(engine);
|
||||||
// Cleanup old coolify images
|
// Cleanup old coolify images
|
||||||
try {
|
try {
|
||||||
let { stdout: images } = await asyncExecShell(
|
let { stdout: images } = await asyncExecShell(
|
||||||
@@ -28,7 +29,7 @@ export default async function (): Promise<void> {
|
|||||||
}
|
}
|
||||||
// Cleanup old images older than a day
|
// Cleanup old images older than a day
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=24h" -a -f`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//console.log(error);
|
//console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import builder from './builder';
|
|||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import cleanup from './cleanup';
|
import cleanup from './cleanup';
|
||||||
import proxy from './proxy';
|
import proxy from './proxy';
|
||||||
|
import proxyTcpHttp from './proxyTcpHttp';
|
||||||
import ssl from './ssl';
|
import ssl from './ssl';
|
||||||
import sslrenewal from './sslrenewal';
|
import sslrenewal from './sslrenewal';
|
||||||
|
|
||||||
@@ -29,17 +30,20 @@ const connectionOptions = {
|
|||||||
|
|
||||||
const cron = async (): Promise<void> => {
|
const cron = async (): Promise<void> => {
|
||||||
new QueueScheduler('proxy', connectionOptions);
|
new QueueScheduler('proxy', connectionOptions);
|
||||||
|
new QueueScheduler('proxyTcpHttp', connectionOptions);
|
||||||
new QueueScheduler('cleanup', connectionOptions);
|
new QueueScheduler('cleanup', connectionOptions);
|
||||||
new QueueScheduler('ssl', connectionOptions);
|
new QueueScheduler('ssl', connectionOptions);
|
||||||
new QueueScheduler('sslRenew', connectionOptions);
|
new QueueScheduler('sslRenew', connectionOptions);
|
||||||
|
|
||||||
const queue = {
|
const queue = {
|
||||||
proxy: new Queue('proxy', { ...connectionOptions }),
|
proxy: new Queue('proxy', { ...connectionOptions }),
|
||||||
|
proxyTcpHttp: new Queue('proxyTcpHttp', { ...connectionOptions }),
|
||||||
cleanup: new Queue('cleanup', { ...connectionOptions }),
|
cleanup: new Queue('cleanup', { ...connectionOptions }),
|
||||||
ssl: new Queue('ssl', { ...connectionOptions }),
|
ssl: new Queue('ssl', { ...connectionOptions }),
|
||||||
sslRenew: new Queue('sslRenew', { ...connectionOptions })
|
sslRenew: new Queue('sslRenew', { ...connectionOptions })
|
||||||
};
|
};
|
||||||
await queue.proxy.drain();
|
await queue.proxy.drain();
|
||||||
|
await queue.proxyTcpHttp.drain();
|
||||||
await queue.cleanup.drain();
|
await queue.cleanup.drain();
|
||||||
await queue.ssl.drain();
|
await queue.ssl.drain();
|
||||||
await queue.sslRenew.drain();
|
await queue.sslRenew.drain();
|
||||||
@@ -54,6 +58,16 @@ const cron = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
new Worker(
|
||||||
|
'proxyTcpHttp',
|
||||||
|
async () => {
|
||||||
|
await proxyTcpHttp();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...connectionOptions
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
new Worker(
|
new Worker(
|
||||||
'ssl',
|
'ssl',
|
||||||
async () => {
|
async () => {
|
||||||
@@ -85,6 +99,7 @@ const cron = async (): Promise<void> => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
|
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
|
||||||
|
await queue.proxyTcpHttp.add('proxyTcpHttp', {}, { repeat: { every: 10000 } });
|
||||||
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
|
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
|
||||||
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
|
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
|
||||||
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
||||||
|
|||||||
55
src/lib/queues/proxyTcpHttp.ts
Normal file
55
src/lib/queues/proxyTcpHttp.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database';
|
||||||
|
import { startCoolifyProxy, startHttpProxy, startTcpProxy } from '$lib/haproxy';
|
||||||
|
|
||||||
|
export default async function (): Promise<void | {
|
||||||
|
status: number;
|
||||||
|
body: { message: string; error: string };
|
||||||
|
}> {
|
||||||
|
try {
|
||||||
|
// Coolify Proxy
|
||||||
|
const localDocker = await prisma.destinationDocker.findFirst({
|
||||||
|
where: { engine: '/var/run/docker.sock' }
|
||||||
|
});
|
||||||
|
if (localDocker && localDocker.isCoolifyProxyUsed) {
|
||||||
|
await startCoolifyProxy('/var/run/docker.sock');
|
||||||
|
}
|
||||||
|
// TCP Proxies
|
||||||
|
const databasesWithPublicPort = await prisma.database.findMany({
|
||||||
|
where: { publicPort: { not: null } },
|
||||||
|
include: { settings: true, destinationDocker: true }
|
||||||
|
});
|
||||||
|
for (const database of databasesWithPublicPort) {
|
||||||
|
const { destinationDockerId, destinationDocker, publicPort, id } = database;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
const { privatePort } = generateDatabaseConfiguration(database);
|
||||||
|
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const wordpressWithFtp = await prisma.wordpress.findMany({
|
||||||
|
where: { ftpPublicPort: { not: null } },
|
||||||
|
include: { service: { include: { destinationDocker: true } } }
|
||||||
|
});
|
||||||
|
for (const ftp of wordpressWithFtp) {
|
||||||
|
const { service, ftpPublicPort } = ftp;
|
||||||
|
const { destinationDockerId, destinationDocker, id } = service;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
await startTcpProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP Proxies
|
||||||
|
const minioInstances = await prisma.minio.findMany({
|
||||||
|
where: { publicPort: { not: null } },
|
||||||
|
include: { service: { include: { destinationDocker: true } } }
|
||||||
|
});
|
||||||
|
for (const minio of minioInstances) {
|
||||||
|
const { service, publicPort } = minio;
|
||||||
|
const { destinationDockerId, destinationDocker, id } = service;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
await startHttpProxy(destinationDocker, id, publicPort, 9000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorHandler(error.response?.body || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
export const publicPaths = [
|
export const publicPaths = [
|
||||||
'/login',
|
'/login',
|
||||||
'/register',
|
'/register',
|
||||||
'/reset',
|
|
||||||
'/reset/password',
|
|
||||||
'/webhooks/success',
|
'/webhooks/success',
|
||||||
'/webhooks/github',
|
'/webhooks/github',
|
||||||
'/webhooks/github/install',
|
'/webhooks/github/install',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export type BuilderJob = {
|
|||||||
pythonWSGI: string;
|
pythonWSGI: string;
|
||||||
pythonModule: string;
|
pythonModule: string;
|
||||||
pythonVariable: string;
|
pythonVariable: string;
|
||||||
|
dockerFileLocation: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
destinationDockerId: string;
|
destinationDockerId: string;
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ export const post: RequestHandler = async (event) => {
|
|||||||
publishDirectory,
|
publishDirectory,
|
||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable
|
pythonVariable,
|
||||||
|
dockerFileLocation
|
||||||
} = await event.request.json();
|
} = await event.request.json();
|
||||||
if (port) port = Number(port);
|
if (port) port = Number(port);
|
||||||
|
|
||||||
@@ -68,7 +69,8 @@ export const post: RequestHandler = async (event) => {
|
|||||||
startCommand,
|
startCommand,
|
||||||
buildCommand,
|
buildCommand,
|
||||||
publishDirectory,
|
publishDirectory,
|
||||||
baseDirectory
|
baseDirectory,
|
||||||
|
dockerFileLocation
|
||||||
});
|
});
|
||||||
await db.configureApplication({
|
await db.configureApplication({
|
||||||
id,
|
id,
|
||||||
@@ -84,6 +86,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable,
|
pythonVariable,
|
||||||
|
dockerFileLocation,
|
||||||
...defaultConfiguration
|
...defaultConfiguration
|
||||||
});
|
});
|
||||||
return { status: 201 };
|
return { status: 201 };
|
||||||
|
|||||||
@@ -68,11 +68,6 @@
|
|||||||
value: 'Gunicorn',
|
value: 'Gunicorn',
|
||||||
label: 'Gunicorn'
|
label: 'Gunicorn'
|
||||||
}
|
}
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// value: 'uWSGI',
|
|
||||||
// label: 'uWSGI'
|
|
||||||
// }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
||||||
@@ -420,6 +415,23 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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="grid grid-cols-2 items-center">
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
|
<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')
|
.split('\n')
|
||||||
.map((l) => l.slice(8))
|
.map((l) => l.slice(8))
|
||||||
.filter((a) => a)
|
.filter((a) => a)
|
||||||
|
.reverse()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,19 +24,23 @@
|
|||||||
export let application;
|
export let application;
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import LoadingLogs from './_Loading.svelte';
|
import LoadingLogs from './_Loading.svelte';
|
||||||
import { getDomain } from '$lib/components/common';
|
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
|
|
||||||
let loadLogsInterval = null;
|
let loadLogsInterval = null;
|
||||||
|
let allLogs = {
|
||||||
|
logs: []
|
||||||
|
};
|
||||||
let logs = [];
|
let logs = [];
|
||||||
let followingBuild;
|
let currentPage = 1;
|
||||||
|
let endOfLogs = false;
|
||||||
|
let startOfLogs = true;
|
||||||
let followingInterval;
|
let followingInterval;
|
||||||
let logsEl;
|
let logsEl;
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
loadLogs();
|
loadAllLogs();
|
||||||
loadLogsInterval = setInterval(() => {
|
loadLogsInterval = setInterval(() => {
|
||||||
loadLogs();
|
loadLogs();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@@ -45,25 +49,55 @@
|
|||||||
clearInterval(loadLogsInterval);
|
clearInterval(loadLogsInterval);
|
||||||
clearInterval(followingInterval);
|
clearInterval(followingInterval);
|
||||||
});
|
});
|
||||||
async function loadLogs() {
|
async function loadAllLogs() {
|
||||||
try {
|
try {
|
||||||
const newLogs = await get(`/applications/${id}/logs.json`);
|
const data: any = await get(`/applications/${id}/logs.json`);
|
||||||
logs = newLogs.logs;
|
allLogs = data.logs;
|
||||||
|
logs = data.logs.slice(0, 100);
|
||||||
|
if (logs.length < 100) {
|
||||||
|
endOfLogs = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function loadLogs() {
|
||||||
function followBuild() {
|
try {
|
||||||
followingBuild = !followingBuild;
|
const newLogs = await get(`/applications/${id}/logs.json`);
|
||||||
if (followingBuild) {
|
logs = newLogs.logs.slice(0, 100);
|
||||||
followingInterval = setInterval(() => {
|
return;
|
||||||
logsEl.scrollTop = logsEl.scrollHeight;
|
} catch ({ error }) {
|
||||||
window.scrollTo(0, document.body.scrollHeight);
|
return errorNotification(error);
|
||||||
}, 100);
|
}
|
||||||
|
}
|
||||||
|
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 {
|
} else {
|
||||||
window.clearInterval(followingInterval);
|
startOfLogs = true;
|
||||||
|
loadLogs();
|
||||||
|
loadLogsInterval = setInterval(() => {
|
||||||
|
loadLogs();
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -145,13 +179,18 @@
|
|||||||
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
|
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="relative w-full">
|
<div class="relative w-full">
|
||||||
<LoadingLogs />
|
<div class="text-right " />
|
||||||
<div class="flex justify-end sticky top-0 p-2">
|
{#if loadLogsInterval}
|
||||||
|
<LoadingLogs />
|
||||||
|
{/if}
|
||||||
|
<div class="flex justify-end sticky top-0 p-2 mx-1">
|
||||||
<button
|
<button
|
||||||
on:click={followBuild}
|
on:click={loadOlderLogs}
|
||||||
class="bg-transparent"
|
class:text-coolgray-100={endOfLogs}
|
||||||
data-tooltip="Follow logs"
|
class:hover:bg-coolgray-400={!endOfLogs}
|
||||||
class:text-green-500={followingBuild}
|
class="bg-transparent tooltip-bottom"
|
||||||
|
data-tooltip="Older logs"
|
||||||
|
disabled={endOfLogs}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -164,10 +203,33 @@
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<circle cx="12" cy="12" r="9" />
|
<path
|
||||||
<line x1="8" y1="12" x2="12" y2="16" />
|
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"
|
||||||
<line x1="12" y1="8" x2="12" y2="16" />
|
/>
|
||||||
<line x1="16" y1="12" x2="12" y2="16" />
|
</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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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"
|
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}
|
bind:this={logsEl}
|
||||||
>
|
>
|
||||||
<div class="px-2">
|
<div class="px-2 pr-14">
|
||||||
{#each logs as log}
|
{#each logs as log}
|
||||||
{log + '\n'}
|
{log + '\n'}
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -29,10 +29,12 @@
|
|||||||
disabled={!isRunning}
|
disabled={!isRunning}
|
||||||
readonly={!isRunning}
|
readonly={!isRunning}
|
||||||
placeholder="Generated automatically after start"
|
placeholder="Generated automatically after start"
|
||||||
|
isPasswordField
|
||||||
id="rootUserPassword"
|
id="rootUserPassword"
|
||||||
name="rootUserPassword"
|
name="rootUserPassword"
|
||||||
bind:value={database.rootUserPassword}
|
bind:value={database.rootUserPassword}
|
||||||
/>
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
|
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getUserDetails } from '$lib/common';
|
import { getUserDetails } from '$lib/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler, stopDatabase } from '$lib/database';
|
import { ErrorHandler, stopDatabase } from '$lib/database';
|
||||||
import { deleteProxy } from '$lib/haproxy';
|
import { stopTcpHttpProxy } from '$lib/haproxy';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const del: RequestHandler = async (event) => {
|
export const del: RequestHandler = async (event) => {
|
||||||
@@ -12,7 +12,7 @@ export const del: RequestHandler = async (event) => {
|
|||||||
const database = await db.getDatabase({ id, teamId });
|
const database = await db.getDatabase({ id, teamId });
|
||||||
if (database.destinationDockerId) {
|
if (database.destinationDockerId) {
|
||||||
const everStarted = await stopDatabase(database);
|
const everStarted = await stopDatabase(database);
|
||||||
if (everStarted) await deleteProxy({ id });
|
if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort);
|
||||||
}
|
}
|
||||||
await db.removeDatabase({ id });
|
await db.removeDatabase({ id });
|
||||||
return { status: 200 };
|
return { status: 200 };
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
import { getUserDetails } from '$lib/common';
|
import { getUserDetails } from '$lib/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { generateDatabaseConfiguration, ErrorHandler } from '$lib/database';
|
import { generateDatabaseConfiguration, ErrorHandler, getFreePort } from '$lib/database';
|
||||||
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import getPort, { portNumbers } from 'get-port';
|
|
||||||
|
|
||||||
export const post: RequestHandler = async (event) => {
|
export const post: RequestHandler = async (event) => {
|
||||||
const { status, body, teamId } = await getUserDetails(event);
|
const { status, body, teamId } = await getUserDetails(event);
|
||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
const data = await db.prisma.setting.findFirst();
|
|
||||||
const { minPort, maxPort } = data;
|
|
||||||
|
|
||||||
const { isPublic, appendOnly = true } = await event.request.json();
|
const { isPublic, appendOnly = true } = await event.request.json();
|
||||||
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
|
const publicPort = await getFreePort();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.setDatabase({ id, isPublic, appendOnly });
|
await db.setDatabase({ id, isPublic, appendOnly });
|
||||||
|
|||||||
@@ -13,20 +13,25 @@ export const get: RequestHandler = async (event) => {
|
|||||||
select: { id: true, email: true, teams: true }
|
select: { id: true, email: true, teams: true }
|
||||||
});
|
});
|
||||||
let accounts = [];
|
let accounts = [];
|
||||||
|
let allTeams = [];
|
||||||
if (teamId === '0') {
|
if (teamId === '0') {
|
||||||
accounts = await db.prisma.user.findMany({ select: { id: true, email: true, teams: true } });
|
accounts = await db.prisma.user.findMany({ select: { id: true, email: true, teams: true } });
|
||||||
|
allTeams = await db.prisma.team.findMany({
|
||||||
|
where: { users: { none: { id: userId } } },
|
||||||
|
include: { permissions: true }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
const ownTeams = await db.prisma.team.findMany({
|
||||||
const teams = await db.prisma.permission.findMany({
|
where: { users: { some: { id: userId } } },
|
||||||
where: { userId: teamId === '0' ? undefined : userId },
|
include: { permissions: true }
|
||||||
include: { team: { include: { _count: { select: { users: true } } } } }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const invitations = await db.prisma.teamInvitation.findMany({ where: { uid: userId } });
|
const invitations = await db.prisma.teamInvitation.findMany({ where: { uid: userId } });
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
teams,
|
ownTeams,
|
||||||
|
allTeams,
|
||||||
invitations,
|
invitations,
|
||||||
account,
|
account,
|
||||||
accounts
|
accounts
|
||||||
|
|||||||
@@ -36,18 +36,8 @@
|
|||||||
if (accounts.length === 0) {
|
if (accounts.length === 0) {
|
||||||
accounts.push(account);
|
accounts.push(account);
|
||||||
}
|
}
|
||||||
export let teams;
|
export let ownTeams;
|
||||||
|
export let allTeams;
|
||||||
const ownTeams = teams.filter((team) => {
|
|
||||||
if (team.team.id === $session.teamId) {
|
|
||||||
return team;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const otherTeams = teams.filter((team) => {
|
|
||||||
if (team.team.id !== $session.teamId) {
|
|
||||||
return team;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function resetPassword(id) {
|
async function resetPassword(id) {
|
||||||
const sure = window.confirm('Are you sure you want to reset the password?');
|
const sure = window.confirm('Are you sure you want to reset the password?');
|
||||||
@@ -167,49 +157,51 @@
|
|||||||
<div class="title font-bold">Teams</div>
|
<div class="title font-bold">Teams</div>
|
||||||
<div class="flex items-center justify-center pt-10">
|
<div class="flex items-center justify-center pt-10">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-col flex-wrap justify-center px-2 pb-10 md:flex-row">
|
<div class="flex flex-row flex-wrap justify-center px-2 pb-10 md:flex-row">
|
||||||
{#each ownTeams as team}
|
{#each ownTeams as team}
|
||||||
<a href="/iam/team/{team.teamId}" class="w-96 p-2 no-underline">
|
<a href="/iam/team/{team.id}" class="w-96 p-2 no-underline">
|
||||||
<div
|
<div
|
||||||
class="box-selection relative"
|
class="box-selection relative"
|
||||||
class:hover:bg-cyan-600={team.team?.id !== '0'}
|
class:hover:bg-cyan-600={team.id !== '0'}
|
||||||
class:hover:bg-red-500={team.team?.id === '0'}
|
class:hover:bg-red-500={team.id === '0'}
|
||||||
>
|
>
|
||||||
<div class="truncate text-center text-xl font-bold">
|
<div class="truncate text-center text-xl font-bold">
|
||||||
{team.team.name}
|
{team.name}
|
||||||
</div>
|
</div>
|
||||||
<div class="truncate text-center font-bold">
|
<div class="truncate text-center font-bold">
|
||||||
{team.team?.id === '0' ? 'root team' : ''}
|
{team.id === '0' ? 'root team' : ''}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-1 text-center">{team.team._count.users} member(s)</div>
|
<div class:mt-6={team.id !== '0'} class="mt-1 text-center">
|
||||||
|
{team.permissions?.length} member(s)
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if $session.teamId === '0' && otherTeams.length > 0}
|
{#if $session.teamId === '0' && allTeams.length > 0}
|
||||||
<div class="pb-5 pt-10 text-xl font-bold">Other Teams</div>
|
<div class="pb-5 pt-10 text-xl font-bold">Other Teams</div>
|
||||||
{/if}
|
<div class="flex flex-row flex-wrap justify-center px-2 md:flex-row">
|
||||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
{#each allTeams as team}
|
||||||
{#each otherTeams as team}
|
<a href="/iam/team/{team.id}" class="w-96 p-2 no-underline">
|
||||||
<a href="/iam/team/{team.teamId}" class="w-96 p-2 no-underline">
|
<div
|
||||||
<div
|
class="box-selection relative"
|
||||||
class="box-selection relative"
|
class:hover:bg-cyan-600={team.id !== '0'}
|
||||||
class:hover:bg-cyan-600={team.team?.id !== '0'}
|
class:hover:bg-red-500={team.id === '0'}
|
||||||
class:hover:bg-red-500={team.team?.id === '0'}
|
>
|
||||||
>
|
<div class="truncate text-center text-xl font-bold">
|
||||||
<div class="truncate text-center text-xl font-bold">
|
{team.name}
|
||||||
{team.team.name}
|
</div>
|
||||||
</div>
|
<div class="truncate text-center font-bold">
|
||||||
<div class="truncate text-center font-bold">
|
{team.id === '0' ? 'root team' : ''}
|
||||||
{team.team?.id === '0' ? 'root team' : ''}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-1 text-center">{team.team._count.users} member(s)</div>
|
<div class="mt-1 text-center">{team.permissions?.length} member(s)</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,8 +43,15 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="flex justify-center px-4">
|
<div class="flex justify-center px-4">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2">
|
<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>
|
{#if $session.whiteLabelDetails.icon}
|
||||||
<div class="text-xs text-center font-bold pb-10">v{$session.version}</div>
|
<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
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
name="email"
|
name="email"
|
||||||
@@ -76,10 +83,6 @@
|
|||||||
on:click|preventDefault={() => goto('/register')}
|
on:click|preventDefault={() => goto('/register')}
|
||||||
class="bg-transparent hover:bg-coolgray-300 text-white ">Register</button
|
class="bg-transparent hover:bg-coolgray-300 text-white ">Register</button
|
||||||
>
|
>
|
||||||
<button
|
|
||||||
class="bg-transparent hover:bg-coolgray-300"
|
|
||||||
on:click|preventDefault={() => goto('/reset')}>Reset password</button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
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: {}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -11,7 +11,9 @@
|
|||||||
let loading = false;
|
let loading = false;
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
|
if (loading) return;
|
||||||
try {
|
try {
|
||||||
|
loading = true;
|
||||||
await post('/new/destination/check.json', { network: payload.network });
|
await post('/new/destination/check.json', { network: payload.network });
|
||||||
const { id } = await post('/new/destination/docker.json', {
|
const { id } = await post('/new/destination/docker.json', {
|
||||||
...payload
|
...payload
|
||||||
|
|||||||
@@ -64,8 +64,15 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="flex justify-center px-4">
|
<div class="flex justify-center px-4">
|
||||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2">
|
<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>
|
{#if $session.whiteLabelDetails.icon}
|
||||||
<div class="text-xs text-center font-bold pb-10">v{$session.version}</div>
|
<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
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
name="email"
|
name="email"
|
||||||
@@ -105,6 +112,9 @@
|
|||||||
{#if userCount === 0}
|
{#if userCount === 0}
|
||||||
<div class="pt-5">
|
<div class="pt-5">
|
||||||
You are registering the first user. It will be the administrator of your Coolify instance.
|
You are registering the first user. It will be the administrator of your Coolify instance.
|
||||||
|
<br />
|
||||||
|
It will take a while, because Coolify will configure itself, the proxy and other docker related
|
||||||
|
stuff.
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
|
||||||
import * as db from '$lib/database';
|
|
||||||
|
|
||||||
export const get: RequestHandler = async () => {
|
|
||||||
const users = await db.prisma.user.findMany({});
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
users
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export const post: RequestHandler = async (event) => {
|
|
||||||
const { secretKey } = await event.request.json();
|
|
||||||
if (secretKey !== process.env.COOLIFY_SECRET_KEY) {
|
|
||||||
return {
|
|
||||||
status: 500,
|
|
||||||
body: {
|
|
||||||
error: 'Invalid secret key.'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
status: 200
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import { get, post } from '$lib/api';
|
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
|
||||||
import { errorNotification } from '$lib/form';
|
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
|
||||||
|
|
||||||
let secretKey;
|
|
||||||
let password = false;
|
|
||||||
let users = [];
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
|
||||||
try {
|
|
||||||
await post(`/reset.json`, { secretKey });
|
|
||||||
password = true;
|
|
||||||
const data = await get('/reset.json');
|
|
||||||
users = data.users;
|
|
||||||
return;
|
|
||||||
} catch ({ error }) {
|
|
||||||
return errorNotification(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function resetPassword(user) {
|
|
||||||
try {
|
|
||||||
await post(`/reset/password.json`, { secretKey, user });
|
|
||||||
toast.push('Password reset done.');
|
|
||||||
return;
|
|
||||||
} catch ({ error }) {
|
|
||||||
return errorNotification(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="icons fixed top-0 left-0 m-3 cursor-pointer" on:click={() => goto('/')}>
|
|
||||||
<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" />
|
|
||||||
<line x1="5" y1="12" x2="19" y2="12" />
|
|
||||||
<line x1="5" y1="12" x2="11" y2="18" />
|
|
||||||
<line x1="5" y1="12" x2="11" y2="6" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="pb-10 pt-24 text-center text-4xl font-bold">Reset Password</div>
|
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
{#if password}
|
|
||||||
<table class="mx-2 text-left">
|
|
||||||
<thead class="mb-2">
|
|
||||||
<tr>
|
|
||||||
<th class="px-2">Email</th>
|
|
||||||
<th>New password</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{#each users as user}
|
|
||||||
<tr>
|
|
||||||
<td class="px-2">{user.email}</td>
|
|
||||||
<td class="flex space-x-2">
|
|
||||||
<input
|
|
||||||
id="newPassword"
|
|
||||||
name="newPassword"
|
|
||||||
bind:value={user.newPassword}
|
|
||||||
placeholder="Super secure new password"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="mx-auto my-4 w-32 bg-coollabs hover:bg-coollabs-100"
|
|
||||||
on:click={() => resetPassword(user)}>Reset</button
|
|
||||||
></td
|
|
||||||
>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{:else}
|
|
||||||
<form class="flex flex-col" on:submit|preventDefault={handleSubmit}>
|
|
||||||
<div class="text-center text-2xl py-2 font-bold">Secret Key</div>
|
|
||||||
<CopyPasswordField
|
|
||||||
isPasswordField={true}
|
|
||||||
id="secretKey"
|
|
||||||
name="secretKey"
|
|
||||||
bind:value={secretKey}
|
|
||||||
placeholder="You can find it in ~/coolify/.env (COOLIFY_SECRET_KEY)"
|
|
||||||
/>
|
|
||||||
<button type="submit" class="bg-coollabs hover:bg-coollabs-100 mx-auto w-32 my-4"
|
|
||||||
>Submit</button
|
|
||||||
>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import type { RequestHandler } from '@sveltejs/kit';
|
|
||||||
import * as db from '$lib/database';
|
|
||||||
import { ErrorHandler, hashPassword } from '$lib/database';
|
|
||||||
|
|
||||||
export const post: RequestHandler = async (event) => {
|
|
||||||
const { secretKey, user } = await event.request.json();
|
|
||||||
if (secretKey !== process.env.COOLIFY_SECRET_KEY) {
|
|
||||||
return {
|
|
||||||
status: 500,
|
|
||||||
body: {
|
|
||||||
error: 'Invalid secret key.'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const hashedPassword = await hashPassword(user.newPassword);
|
|
||||||
await db.prisma.user.update({
|
|
||||||
where: { email: user.email },
|
|
||||||
data: { password: hashedPassword }
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
status: 200
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorHandler(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -62,10 +62,11 @@
|
|||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="extraConfig">Extra Config</label>
|
<label for="extraConfig">Extra Config</label>
|
||||||
<textarea
|
<textarea
|
||||||
|
bind:value={service.wordpress.extraConfig}
|
||||||
disabled={isRunning}
|
disabled={isRunning}
|
||||||
readonly={isRunning}
|
readonly={isRunning}
|
||||||
class:resize-none={isRunning}
|
class:resize-none={isRunning}
|
||||||
rows={isRunning ? 1 : 5}
|
rows="5"
|
||||||
name="extraConfig"
|
name="extraConfig"
|
||||||
id="extraConfig"
|
id="extraConfig"
|
||||||
placeholder={!isRunning
|
placeholder={!isRunning
|
||||||
@@ -74,8 +75,8 @@
|
|||||||
define('WP_ALLOW_MULTISITE', true);
|
define('WP_ALLOW_MULTISITE', true);
|
||||||
define('MULTISITE', true);
|
define('MULTISITE', true);
|
||||||
define('SUBDOMAIN_INSTALL', false);`
|
define('SUBDOMAIN_INSTALL', false);`
|
||||||
: 'N/A'}>{service.wordpress.extraConfig}</textarea
|
: 'N/A'}
|
||||||
>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center px-10">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<Setting
|
<Setting
|
||||||
|
|||||||
@@ -239,6 +239,35 @@
|
|||||||
</svg></button
|
</svg></button
|
||||||
></a
|
></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" />
|
<div class="border border-stone-700 h-8" />
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -135,11 +135,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (version === 'latest') {
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||||
await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -69,11 +69,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (version === 'latest') {
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||||
await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -74,11 +74,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (version === 'latest') {
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||||
await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import { promises as fs } from 'fs';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { startHttpProxy } from '$lib/haproxy';
|
import { startHttpProxy } from '$lib/haproxy';
|
||||||
import getPort, { portNumbers } from 'get-port';
|
import { ErrorHandler, getFreePort, getServiceImage } from '$lib/database';
|
||||||
import { getDomain } from '$lib/components/common';
|
|
||||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
|
||||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||||
import type { ComposeFile } from '$lib/types/composeFile';
|
import type { ComposeFile } from '$lib/types/composeFile';
|
||||||
|
|
||||||
@@ -28,13 +26,10 @@ export const post: RequestHandler = async (event) => {
|
|||||||
serviceSecret
|
serviceSecret
|
||||||
} = service;
|
} = service;
|
||||||
|
|
||||||
const data = await db.prisma.setting.findFirst();
|
|
||||||
const { minPort, maxPort } = data;
|
|
||||||
|
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const host = getEngine(destinationDocker.engine);
|
const host = getEngine(destinationDocker.engine);
|
||||||
|
|
||||||
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
|
const publicPort = await getFreePort();
|
||||||
|
|
||||||
const consolePort = 9001;
|
const consolePort = 9001;
|
||||||
const apiPort = 9000;
|
const apiPort = 9000;
|
||||||
@@ -93,6 +88,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||||
await db.updateMinioService({ id, publicPort });
|
await db.updateMinioService({ id, publicPort });
|
||||||
await startHttpProxy(destinationDocker, id, publicPort, apiPort);
|
await startHttpProxy(destinationDocker, id, publicPort, apiPort);
|
||||||
|
|||||||
@@ -70,11 +70,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (version === 'latest') {
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||||
await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -61,11 +61,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (version === 'latest') {
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||||
await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -192,9 +192,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
|||||||
};
|
};
|
||||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
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(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
|
`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));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (version === 'latest') {
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
|
||||||
await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -68,11 +68,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
try {
|
try {
|
||||||
if (version === 'latest') {
|
await asyncExecShell(`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`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
destinationDockerId,
|
destinationDockerId,
|
||||||
destinationDocker,
|
destinationDocker,
|
||||||
serviceSecret,
|
serviceSecret,
|
||||||
|
persistentStorage,
|
||||||
vscodeserver: { password }
|
vscodeserver: { password }
|
||||||
} = service;
|
} = service;
|
||||||
|
|
||||||
@@ -42,6 +43,28 @@ export const post: RequestHandler = async (event) => {
|
|||||||
config.environmentVariables[secret.name] = secret.value;
|
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 = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
@@ -50,7 +73,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
image: config.image,
|
image: config.image,
|
||||||
environment: config.environmentVariables,
|
environment: config.environmentVariables,
|
||||||
networks: [network],
|
networks: [network],
|
||||||
volumes: [config.volume],
|
volumes: [config.volume, ...volumes],
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
labels: makeLabelForServices('vscodeServer'),
|
labels: makeLabelForServices('vscodeServer'),
|
||||||
deploy: {
|
deploy: {
|
||||||
@@ -68,19 +91,21 @@ export const post: RequestHandler = async (event) => {
|
|||||||
external: true
|
external: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
volumes: {
|
volumes: volumeMounts
|
||||||
[config.volume.split(':')[0]]: {
|
|
||||||
name: config.volume.split(':')[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
|
|
||||||
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`);
|
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 {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { dev } from '$app/env';
|
|||||||
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
|
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
|
||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { generateDatabaseConfiguration, ErrorHandler, generatePassword } from '$lib/database';
|
import { ErrorHandler, generatePassword, getFreePort } from '$lib/database';
|
||||||
import { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
import { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy';
|
||||||
import type { ComposeFile } from '$lib/types/composeFile';
|
import type { ComposeFile } from '$lib/types/composeFile';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
@@ -16,11 +16,10 @@ export const post: RequestHandler = async (event) => {
|
|||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
const data = await db.prisma.setting.findFirst();
|
|
||||||
const { minPort, maxPort } = data;
|
|
||||||
|
|
||||||
const { ftpEnabled } = await event.request.json();
|
const { ftpEnabled } = await event.request.json();
|
||||||
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
|
const publicPort = await getFreePort();
|
||||||
|
|
||||||
let ftpUser = cuid();
|
let ftpUser = cuid();
|
||||||
let ftpPassword = generatePassword();
|
let ftpPassword = generatePassword();
|
||||||
|
|
||||||
@@ -114,7 +113,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
services: {
|
services: {
|
||||||
[`${id}-ftp`]: {
|
[`${id}-ftp`]: {
|
||||||
image: `atmoz/sftp:alpine`,
|
image: `atmoz/sftp:alpine`,
|
||||||
command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:1001'`,
|
command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`,
|
||||||
extra_hosts: ['host.docker.internal:host-gateway'],
|
extra_hosts: ['host.docker.internal:host-gateway'],
|
||||||
container_name: `${id}-ftp`,
|
container_name: `${id}-ftp`,
|
||||||
volumes,
|
volumes,
|
||||||
|
|||||||
@@ -121,11 +121,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
try {
|
try {
|
||||||
if (version === 'latest') {
|
await asyncExecShell(`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`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -12,21 +12,44 @@ export const post: RequestHandler = async (event) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const service = await db.getService({ id, teamId });
|
const service = await db.getService({ id, teamId });
|
||||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
const {
|
||||||
|
destinationDockerId,
|
||||||
|
destinationDocker,
|
||||||
|
fqdn,
|
||||||
|
wordpress: { ftpEnabled }
|
||||||
|
} = service;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const engine = destinationDocker.engine;
|
const engine = destinationDocker.engine;
|
||||||
try {
|
try {
|
||||||
let found = await checkContainer(engine, id);
|
const found = await checkContainer(engine, id);
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeDestinationDocker({ id, engine });
|
await removeDestinationDocker({ id, engine });
|
||||||
}
|
}
|
||||||
found = await checkContainer(engine, `${id}-mysql`);
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const found = await checkContainer(engine, `${id}-mysql`);
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeDestinationDocker({ id: `${id}-mysql`, engine });
|
await removeDestinationDocker({ id: `${id}-mysql`, engine });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
if (ftpEnabled) {
|
||||||
|
const found = await checkContainer(engine, `${id}-ftp`);
|
||||||
|
if (found) {
|
||||||
|
await removeDestinationDocker({ id: `${id}-ftp`, engine });
|
||||||
|
}
|
||||||
|
await db.prisma.wordpress.update({
|
||||||
|
where: { serviceId: id },
|
||||||
|
data: { ftpEnabled: false }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user