mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
c1adffe260 | ||
|
|
e725887a55 | ||
|
|
5bf79b75b0 | ||
|
|
6926975e40 | ||
|
|
978a01c968 | ||
|
|
f421f5ee84 | ||
|
|
383831c7b8 | ||
|
|
41329facf7 | ||
|
|
7d3c644148 | ||
|
|
7fab9b5930 | ||
|
|
58763ef84c | ||
|
|
0e6abf172b | ||
|
|
9e681ece41 | ||
|
|
28f87a306d | ||
|
|
23e8833208 | ||
|
|
03962663c2 | ||
|
|
cc2ec55c4d | ||
|
|
ff2c38aa16 | ||
|
|
b5a9a2cea8 | ||
|
|
cd3f661f7e | ||
|
|
41bf6b5b86 | ||
|
|
a4e7c85184 | ||
|
|
19aca9ab35 | ||
|
|
08704c289a | ||
|
|
2224c22c6e | ||
|
|
b281889acd | ||
|
|
cfc50a27b0 |
@@ -1,6 +1,6 @@
|
|||||||
FROM haproxytech/haproxy-alpine:2.5
|
FROM haproxytech/haproxy-alpine:2.5
|
||||||
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
||||||
|
|
||||||
COPY haproxy/haproxy.cfg-http.template /usr/local/etc/haproxy/haproxy.cfg
|
COPY data/haproxy/haproxy.cfg-http.template /usr/local/etc/haproxy/haproxy.cfg
|
||||||
COPY haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
||||||
COPY haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
FROM haproxytech/haproxy-alpine:2.5
|
FROM haproxytech/haproxy-alpine:2.5
|
||||||
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
||||||
|
|
||||||
COPY haproxy/haproxy.cfg-tcp.template /usr/local/etc/haproxy/haproxy.cfg
|
COPY data/haproxy/haproxy.cfg-tcp.template /usr/local/etc/haproxy/haproxy.cfg
|
||||||
COPY haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
||||||
COPY haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
FROM haproxytech/haproxy-alpine:2.5
|
FROM haproxytech/haproxy-alpine:2.5
|
||||||
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
||||||
|
|
||||||
COPY haproxy/haproxy.cfg.template /usr/local/etc/haproxy/haproxy.cfg
|
COPY data/haproxy/haproxy.cfg.template /usr/local/etc/haproxy/haproxy.cfg
|
||||||
COPY haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
||||||
COPY haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
11
package.json
11
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "2.4.3",
|
"version": "2.4.10",
|
||||||
"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",
|
||||||
@@ -18,11 +18,12 @@
|
|||||||
"db:seed": "prisma db seed",
|
"db:seed": "prisma db seed",
|
||||||
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
||||||
"release:production:all": "cross-var docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest --push .",
|
"release:production:all": "cross-var docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest --push .",
|
||||||
|
"release:production:amd": "cross-var docker build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest --push .",
|
||||||
"release:staging:all": "cross-var docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify:$npm_package_version --push .",
|
"release:staging:all": "cross-var docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify:$npm_package_version --push .",
|
||||||
"release:staging:amd": "cross-var docker build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version --push .",
|
"release:staging:amd": "cross-var docker build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version --push .",
|
||||||
"release:haproxy": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-alpine:latest -t coollabsio/coolify-haproxy-alpine:1.1.0 -f haproxy.Dockerfile --push .",
|
"release:haproxy": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-alpine:latest -t coollabsio/coolify-haproxy-alpine:1.1.0 -f data/haproxy.Dockerfile --push .",
|
||||||
"release:haproxy:tcp": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-tcp-alpine:latest -t coollabsio/coolify-haproxy-tcp-alpine:1.1.0 -f haproxy-tcp.Dockerfile --push .",
|
"release:haproxy:tcp": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-tcp-alpine:latest -t coollabsio/coolify-haproxy-tcp-alpine:1.1.0 -f data/haproxy-tcp.Dockerfile --push .",
|
||||||
"release:haproxy:http": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-http-alpine:latest -t coollabsio/coolify-haproxy-http-alpine:1.1.0 -f haproxy-http.Dockerfile --push .",
|
"release:haproxy:http": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-http-alpine:latest -t coollabsio/coolify-haproxy-http-alpine:1.1.0 -f data/haproxy-http.Dockerfile --push .",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -63,7 +64,7 @@
|
|||||||
"@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.79.0",
|
"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",
|
||||||
|
|||||||
16
pnpm-lock.yaml
generated
16
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.310
|
'@sveltejs/kit': 1.0.0-next.303
|
||||||
'@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
|
||||||
@@ -15,7 +15,7 @@ specifiers:
|
|||||||
'@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.79.0
|
bullmq: 1.78.1
|
||||||
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.79.0
|
bullmq: 1.78.1
|
||||||
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.310_svelte@3.47.0
|
'@sveltejs/kit': 1.0.0-next.303_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.310_svelte@3.47.0:
|
/@sveltejs/kit/1.0.0-next.303_svelte@3.47.0:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-pTyMyaoyHS+V5cQZIQMfQXmLkhw1VaRwT9avOSgwDc0QBpnNw2LdzwoPYsUr96ca5B6cfT3SMUNolxErTNHmPQ==
|
integrity: sha512-WdxDc8OiF1WEd/bEza7CBdzA+3qIcCi1GKBj/gieKX9I3N8iDJt/Cg2POrLo9wQoJ47nZcAd1eOhfr7XEX1aIQ==
|
||||||
}
|
}
|
||||||
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.79.0:
|
/bullmq/1.78.1:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-rVtNCDpcWdc+U1MinRtvhJv+GBFNkz0Q3Unf20010qIC6Pj+O2kkIUeepBkCmMNz6G9abrhsee2PheGRJ32+sw==
|
integrity: sha512-er45mM8nGhgA83EVCJ4PNxPyDSzakvoxeFGU4vdSgYeB+SbeFQAlJYmAC50Ms7YFPstm1LeinbVZ+oX/BmBzOg==
|
||||||
}
|
}
|
||||||
dependencies:
|
dependencies:
|
||||||
cron-parser: 4.2.1
|
cron-parser: 4.2.1
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
// TODO: Make this functions generic
|
|
||||||
|
|
||||||
async function send({
|
async function send({
|
||||||
method,
|
method,
|
||||||
path,
|
path,
|
||||||
data = {},
|
data = {},
|
||||||
headers,
|
headers,
|
||||||
timeout = 30000
|
timeout = 120000
|
||||||
}): Promise<Record<string, unknown>> {
|
}): Promise<Record<string, unknown>> {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const id = setTimeout(() => controller.abort(), timeout);
|
const id = setTimeout(() => controller.abort(), timeout);
|
||||||
@@ -53,7 +51,7 @@ async function send({
|
|||||||
|
|
||||||
export function get(
|
export function get(
|
||||||
path: string,
|
path: string,
|
||||||
headers: Record<string, unknown>
|
headers?: Record<string, unknown>
|
||||||
): Promise<Record<string, unknown>> {
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'GET', path, headers });
|
return send({ method: 'GET', path, headers });
|
||||||
}
|
}
|
||||||
@@ -61,7 +59,7 @@ export function get(
|
|||||||
export function del(
|
export function del(
|
||||||
path: string,
|
path: string,
|
||||||
data: Record<string, unknown>,
|
data: Record<string, unknown>,
|
||||||
headers: Record<string, unknown>
|
headers?: Record<string, unknown>
|
||||||
): Promise<Record<string, unknown>> {
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'DELETE', path, data, headers });
|
return send({ method: 'DELETE', path, data, headers });
|
||||||
}
|
}
|
||||||
@@ -69,7 +67,7 @@ export function del(
|
|||||||
export function post(
|
export function post(
|
||||||
path: string,
|
path: string,
|
||||||
data: Record<string, unknown>,
|
data: Record<string, unknown>,
|
||||||
headers: Record<string, unknown>
|
headers?: Record<string, unknown>
|
||||||
): Promise<Record<string, unknown>> {
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'POST', path, data, headers });
|
return send({ method: 'POST', path, data, headers });
|
||||||
}
|
}
|
||||||
@@ -77,7 +75,7 @@ export function post(
|
|||||||
export function put(
|
export function put(
|
||||||
path: string,
|
path: string,
|
||||||
data: Record<string, unknown>,
|
data: Record<string, unknown>,
|
||||||
headers: Record<string, unknown>
|
headers?: Record<string, unknown>
|
||||||
): Promise<Record<string, unknown>> {
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'PUT', path, data, headers });
|
return send({ method: 'PUT', path, data, headers });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,11 +93,16 @@ export const getUserDetails = async (
|
|||||||
}> => {
|
}> => {
|
||||||
const teamId = getTeam(event);
|
const teamId = getTeam(event);
|
||||||
const userId = event?.locals?.session?.data?.userId || null;
|
const userId = event?.locals?.session?.data?.userId || null;
|
||||||
const { permission = 'read' } = await db.prisma.permission.findFirst({
|
let permission = 'read';
|
||||||
where: { teamId, userId },
|
if (teamId && userId) {
|
||||||
select: { permission: true },
|
const data = await db.prisma.permission.findFirst({
|
||||||
rejectOnNotFound: true
|
where: { teamId, userId },
|
||||||
});
|
select: { permission: true },
|
||||||
|
rejectOnNotFound: true
|
||||||
|
});
|
||||||
|
if (data.permission) permission = data.permission;
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
teamId,
|
teamId,
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -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({
|
||||||
@@ -219,7 +220,6 @@ export function generateDatabaseConfiguration(database: Database & { settings: D
|
|||||||
return {
|
return {
|
||||||
privatePort: 5432,
|
privatePort: 5432,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
POSTGRESQL_POSTGRES_PASSWORD: rootUserPassword,
|
|
||||||
POSTGRESQL_PASSWORD: dbUserPassword,
|
POSTGRESQL_PASSWORD: dbUserPassword,
|
||||||
POSTGRESQL_USERNAME: dbUser,
|
POSTGRESQL_USERNAME: dbUser,
|
||||||
POSTGRESQL_DATABASE: defaultDatabase
|
POSTGRESQL_DATABASE: defaultDatabase
|
||||||
@@ -252,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 });
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,18 +18,10 @@ export async function listSources(
|
|||||||
|
|
||||||
export async function newSource({
|
export async function newSource({
|
||||||
name,
|
name,
|
||||||
teamId,
|
teamId
|
||||||
type,
|
|
||||||
htmlUrl,
|
|
||||||
apiUrl,
|
|
||||||
organization
|
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
type: string;
|
|
||||||
htmlUrl: string;
|
|
||||||
apiUrl: string;
|
|
||||||
organization: string;
|
|
||||||
}): Promise<GitSource> {
|
}): Promise<GitSource> {
|
||||||
return await prisma.gitSource.create({
|
return await prisma.gitSource.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -74,8 +66,11 @@ export async function getSource({
|
|||||||
if (body?.gitlabApp?.appSecret) body.gitlabApp.appSecret = decrypt(body.gitlabApp.appSecret);
|
if (body?.gitlabApp?.appSecret) body.gitlabApp.appSecret = decrypt(body.gitlabApp.appSecret);
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
export async function addGitHubSource({ id, teamId, type, name, htmlUrl, apiUrl }) {
|
export async function addGitHubSource({ id, teamId, type, name, htmlUrl, apiUrl, organization }) {
|
||||||
await prisma.gitSource.update({ where: { id }, data: { type, name, htmlUrl, apiUrl } });
|
await prisma.gitSource.update({
|
||||||
|
where: { id },
|
||||||
|
data: { type, name, htmlUrl, apiUrl, organization }
|
||||||
|
});
|
||||||
return await prisma.githubApp.create({
|
return await prisma.githubApp.create({
|
||||||
data: {
|
data: {
|
||||||
teams: { connect: { id: teamId } },
|
teams: { connect: { id: teamId } },
|
||||||
@@ -95,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: {
|
||||||
@@ -123,10 +118,14 @@ export async function configureGitsource({
|
|||||||
}
|
}
|
||||||
export async function updateGitsource({
|
export async function updateGitsource({
|
||||||
id,
|
id,
|
||||||
name
|
name,
|
||||||
|
htmlUrl,
|
||||||
|
apiUrl
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
htmlUrl: string;
|
||||||
|
apiUrl: string;
|
||||||
}): Promise<GitSource> {
|
}): Promise<GitSource> {
|
||||||
return await prisma.gitSource.update({
|
return await prisma.gitSource.update({
|
||||||
where: { id },
|
where: { 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';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import got, { type Got } from 'got';
|
import got, { type Got } from 'got';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
|
import mustache from 'mustache';
|
||||||
|
import crypto from 'crypto';
|
||||||
import { checkContainer, checkHAProxy } from '.';
|
import { checkContainer, checkHAProxy } from '.';
|
||||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||||
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
||||||
@@ -18,10 +20,10 @@ global
|
|||||||
defaults
|
defaults
|
||||||
mode http
|
mode http
|
||||||
log global
|
log global
|
||||||
timeout http-request 60s
|
timeout http-request 120s
|
||||||
timeout connect 10s
|
timeout connect 10s
|
||||||
timeout client 60s
|
timeout client 120s
|
||||||
timeout server 60s
|
timeout server 120s
|
||||||
|
|
||||||
userlist haproxy-dataplaneapi
|
userlist haproxy-dataplaneapi
|
||||||
user admin insecure-password "\${HAPROXY_PASSWORD}"
|
user admin insecure-password "\${HAPROXY_PASSWORD}"
|
||||||
@@ -213,7 +215,8 @@ export async function configureHAProxy(): Promise<void> {
|
|||||||
plausibleAnalytics: true,
|
plausibleAnalytics: true,
|
||||||
vscodeserver: true,
|
vscodeserver: true,
|
||||||
wordpress: true,
|
wordpress: true,
|
||||||
ghost: true
|
ghost: true,
|
||||||
|
meiliSearch: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -262,36 +265,20 @@ export async function configureHAProxy(): Promise<void> {
|
|||||||
redirectValue,
|
redirectValue,
|
||||||
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain
|
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain
|
||||||
});
|
});
|
||||||
for (const service of services) {
|
}
|
||||||
const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service;
|
const output = mustache.render(template, data);
|
||||||
if (destinationDockerId) {
|
const newHash = crypto.createHash('md5').update(output).digest('hex');
|
||||||
const { engine } = destinationDocker;
|
const { proxyHash, id } = await db.listSettings();
|
||||||
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
|
if (proxyHash !== newHash) {
|
||||||
if (found) {
|
await db.prisma.setting.update({ where: { id }, data: { proxyHash: newHash } });
|
||||||
const port = found.ports.main;
|
await haproxy.post(`v2/services/haproxy/configuration/raw`, {
|
||||||
const publicPort = service[type]?.publicPort;
|
searchParams: {
|
||||||
const isRunning = await checkContainer(engine, id);
|
skip_version: true
|
||||||
if (fqdn) {
|
},
|
||||||
const domain = getDomain(fqdn);
|
body: output,
|
||||||
const isHttps = fqdn.startsWith('https://');
|
headers: {
|
||||||
const isWWW = fqdn.includes('www.');
|
'Content-Type': 'text/plain'
|
||||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
|
||||||
if (isRunning) {
|
|
||||||
data.services.push({
|
|
||||||
id,
|
|
||||||
port,
|
|
||||||
publicPort,
|
|
||||||
domain,
|
|
||||||
isRunning,
|
|
||||||
isHttps,
|
|
||||||
redirectValue,
|
|
||||||
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
|
|
||||||
updatedAt: updatedAt.getTime()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -236,9 +246,15 @@ export async function stopCoolifyProxy(
|
|||||||
export async function configureNetworkCoolifyProxy(engine: string): Promise<void> {
|
export async function configureNetworkCoolifyProxy(engine: string): Promise<void> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
const destinations = await db.prisma.destinationDocker.findMany({ where: { engine } });
|
const destinations = await db.prisma.destinationDocker.findMany({ where: { engine } });
|
||||||
|
const { stdout: networks } = await asyncExecShell(
|
||||||
|
`DOCKER_HOST="${host}" docker ps -a --filter name=coolify-haproxy --format '{{json .Networks}}'`
|
||||||
|
);
|
||||||
|
const configuredNetworks = networks.replace(/"/g, '').replace('\n', '').split(',');
|
||||||
for (const destination of destinations) {
|
for (const destination of destinations) {
|
||||||
await asyncExecShell(
|
if (!configuredNetworks.includes(destination.network)) {
|
||||||
`DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-haproxy`
|
await asyncExecShell(
|
||||||
);
|
`DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-haproxy`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import cuid from 'cuid';
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import getPort, { portNumbers } from 'get-port';
|
import getPort, { portNumbers } from 'get-port';
|
||||||
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
||||||
|
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 {
|
||||||
@@ -148,7 +149,8 @@ export async function generateSSLCerts(): Promise<void> {
|
|||||||
plausibleAnalytics: true,
|
plausibleAnalytics: true,
|
||||||
vscodeserver: true,
|
vscodeserver: true,
|
||||||
wordpress: true,
|
wordpress: true,
|
||||||
ghost: true
|
ghost: true,
|
||||||
|
meiliSearch: true
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
});
|
});
|
||||||
@@ -198,16 +200,44 @@ export async function generateSSLCerts(): Promise<void> {
|
|||||||
file.endsWith('.pem') && certificates.push(file.replace(/\.pem$/, ''));
|
file.endsWith('.pem') && certificates.push(file.replace(/\.pem$/, ''));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const resolver = new dns.Resolver({ timeout: 2000 });
|
||||||
|
resolver.setServers(['8.8.8.8', '1.1.1.1']);
|
||||||
|
let ipv4, ipv6;
|
||||||
|
try {
|
||||||
|
ipv4 = await (await asyncExecShell(`curl -4s https://ifconfig.io`)).stdout;
|
||||||
|
} catch (error) {}
|
||||||
|
try {
|
||||||
|
ipv6 = await (await asyncExecShell(`curl -6s https://ifconfig.io`)).stdout;
|
||||||
|
} catch (error) {}
|
||||||
for (const ssl of ssls) {
|
for (const ssl of ssls) {
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
if (
|
if (
|
||||||
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 {
|
||||||
console.log('Generating SSL for', ssl.domain);
|
// Checking DNS entry before generating certificate
|
||||||
await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
if (ipv4 || ipv6) {
|
||||||
|
let domains4 = [];
|
||||||
|
let domains6 = [];
|
||||||
|
try {
|
||||||
|
domains4 = await resolver.resolve4(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
try {
|
||||||
|
domains6 = await resolver.resolve6(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
if (domains4.length > 0 || domains6.length > 0) {
|
||||||
|
if (
|
||||||
|
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
||||||
|
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
||||||
|
) {
|
||||||
|
console.log('Generating SSL for', ssl.domain);
|
||||||
|
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
@@ -216,7 +246,27 @@ export async function generateSSLCerts(): Promise<void> {
|
|||||||
) {
|
) {
|
||||||
console.log(`Certificate for ${ssl.domain} already exists`);
|
console.log(`Certificate for ${ssl.domain} already exists`);
|
||||||
} else {
|
} else {
|
||||||
console.log('Generating SSL for', ssl.domain);
|
// Checking DNS entry before generating certificate
|
||||||
|
if (ipv4 || ipv6) {
|
||||||
|
let domains4 = [];
|
||||||
|
let domains6 = [];
|
||||||
|
try {
|
||||||
|
domains4 = await resolver.resolve4(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
try {
|
||||||
|
domains6 = await resolver.resolve6(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
if (domains4.length > 0 || domains6.length > 0) {
|
||||||
|
if (
|
||||||
|
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
||||||
|
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
||||||
|
) {
|
||||||
|
console.log('Generating SSL for', ssl.domain);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,7 +285,15 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
|
|||||||
networks: [docker.network],
|
networks: [docker.network],
|
||||||
labels,
|
labels,
|
||||||
depends_on: [],
|
depends_on: [],
|
||||||
restart: 'always'
|
restart: 'always',
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -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(
|
||||||
@@ -14,56 +15,23 @@ export default async function (): Promise<void> {
|
|||||||
await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
//console.log(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
//console.log(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
//console.log(error);
|
||||||
|
}
|
||||||
|
// Cleanup old images older than a day
|
||||||
|
try {
|
||||||
|
await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
|
||||||
|
} catch (error) {
|
||||||
|
//console.log(error);
|
||||||
}
|
}
|
||||||
// Tagging images with labels
|
|
||||||
// try {
|
|
||||||
// const images = [
|
|
||||||
// `coollabsio/${defaultProxyImageTcp}`,
|
|
||||||
// `coollabsio/${defaultProxyImageHttp}`,
|
|
||||||
// 'certbot/certbot:latest',
|
|
||||||
// 'node:16.14.0-alpine',
|
|
||||||
// 'alpine:latest',
|
|
||||||
// 'nginx:stable-alpine',
|
|
||||||
// 'node:lts',
|
|
||||||
// 'php:apache',
|
|
||||||
// 'rust:latest'
|
|
||||||
// ];
|
|
||||||
// for (const image of images) {
|
|
||||||
// try {
|
|
||||||
// await asyncExecShell(`DOCKER_HOST=${host} docker image inspect ${image}`);
|
|
||||||
// } catch (error) {
|
|
||||||
// await asyncExecShell(
|
|
||||||
// `DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.image="true" -t "${image}" -`
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } catch (error) {}
|
|
||||||
// if (!dev) {
|
|
||||||
// // Cleanup images that are not managed by coolify
|
|
||||||
// try {
|
|
||||||
// await asyncExecShell(
|
|
||||||
// `DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.image=true' -a -f`
|
|
||||||
// );
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error);
|
|
||||||
// }
|
|
||||||
// // Cleanup old images >3 days
|
|
||||||
// try {
|
|
||||||
// await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import * as Bullmq from 'bullmq';
|
|||||||
import { default as ProdBullmq, QueueScheduler } from 'bullmq';
|
import { default as ProdBullmq, QueueScheduler } from 'bullmq';
|
||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import { prisma } from '$lib/database';
|
import { prisma } from '$lib/database';
|
||||||
|
|
||||||
import builder from './builder';
|
import builder from './builder';
|
||||||
|
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';
|
||||||
|
|
||||||
@@ -27,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();
|
||||||
@@ -52,6 +58,16 @@ const cron = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
new Worker(
|
||||||
|
'proxyTcpHttp',
|
||||||
|
async () => {
|
||||||
|
await proxyTcpHttp();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...connectionOptions
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
new Worker(
|
new Worker(
|
||||||
'ssl',
|
'ssl',
|
||||||
async () => {
|
async () => {
|
||||||
@@ -83,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 } });
|
||||||
@@ -142,5 +159,9 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
|
|||||||
|
|
||||||
const buildLogQueueName = 'log_queue';
|
const buildLogQueueName = 'log_queue';
|
||||||
const buildLogQueue = new Queue(buildLogQueueName, connectionOptions);
|
const buildLogQueue = new Queue(buildLogQueueName, connectionOptions);
|
||||||
|
const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), {
|
||||||
|
concurrency: 1,
|
||||||
|
...connectionOptions
|
||||||
|
});
|
||||||
|
|
||||||
export { buildQueue, buildLogQueue };
|
export { buildQueue, buildLogQueue };
|
||||||
|
|||||||
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',
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ export type ComposeFileService = {
|
|||||||
dockerfile: string;
|
dockerfile: string;
|
||||||
args?: Record<string, unknown>;
|
args?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
deploy?: {
|
||||||
|
restart_policy?: {
|
||||||
|
condition?: string;
|
||||||
|
delay?: string;
|
||||||
|
max_attempts?: number;
|
||||||
|
window?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ComposerFileVersion =
|
export type ComposerFileVersion =
|
||||||
|
|||||||
@@ -36,8 +36,15 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadBranchesByPage(page = 0) {
|
||||||
|
return await get(`${apiUrl}/repos/${selected.repository}/branches?per_page=100&page=${page}`, {
|
||||||
|
Authorization: `token ${$gitTokens.githubToken}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let reposSelectOptions;
|
let reposSelectOptions;
|
||||||
let branchSelectOptions;
|
let branchSelectOptions;
|
||||||
|
|
||||||
async function loadRepositories() {
|
async function loadRepositories() {
|
||||||
let page = 1;
|
let page = 1;
|
||||||
let reposCount = 0;
|
let reposCount = 0;
|
||||||
@@ -58,24 +65,28 @@
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
async function loadBranches(event) {
|
async function loadBranches(event) {
|
||||||
|
branches = [];
|
||||||
selected.repository = event.detail.value;
|
selected.repository = event.detail.value;
|
||||||
loading.branches = true;
|
|
||||||
selected.branch = undefined;
|
|
||||||
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
|
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
|
||||||
try {
|
let page = 1;
|
||||||
branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
|
let branchCount = 0;
|
||||||
Authorization: `token ${$gitTokens.githubToken}`
|
loading.branches = true;
|
||||||
});
|
const loadedBranches = await loadBranchesByPage();
|
||||||
branchSelectOptions = branches.map((branch) => ({
|
branches = branches.concat(loadedBranches);
|
||||||
value: branch.name,
|
branchCount = branches.length;
|
||||||
label: branch.name
|
if (branchCount === 100) {
|
||||||
}));
|
while (branchCount === 100) {
|
||||||
return;
|
page = page + 1;
|
||||||
} catch ({ error }) {
|
const nextBranches = await loadBranchesByPage(page);
|
||||||
return errorNotification(error);
|
branches = branches.concat(nextBranches);
|
||||||
} finally {
|
branchCount = nextBranches.length;
|
||||||
loading.branches = false;
|
}
|
||||||
}
|
}
|
||||||
|
loading.branches = false;
|
||||||
|
branchSelectOptions = branches.map((branch) => ({
|
||||||
|
value: branch.name,
|
||||||
|
label: branch.name
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
async function isBranchAlreadyUsed(event) {
|
async function isBranchAlreadyUsed(event) {
|
||||||
selected.branch = event.detail.value;
|
selected.branch = event.detail.value;
|
||||||
@@ -166,30 +177,36 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
|
<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
|
||||||
<div class="flex-col space-y-3 md:space-y-0 space-x-1">
|
<div class="flex-col space-y-3 md:space-y-0 space-x-1">
|
||||||
<div class="flex gap-4">
|
<div class="flex-col md:flex gap-4">
|
||||||
<div class="custom-select-wrapper">
|
<div class="custom-select-wrapper">
|
||||||
<Select
|
<Select
|
||||||
placeholder={loading.repositories
|
placeholder={loading.repositories
|
||||||
? 'Loading repositories ...'
|
? 'Loading repositories...'
|
||||||
: 'Please select a repository'}
|
: 'Please select a repository'}
|
||||||
id="repository"
|
id="repository"
|
||||||
|
showIndicator={true}
|
||||||
|
isWaiting={loading.repositories}
|
||||||
on:select={loadBranches}
|
on:select={loadBranches}
|
||||||
items={reposSelectOptions}
|
items={reposSelectOptions}
|
||||||
isDisabled={loading.repositories}
|
isDisabled={loading.repositories}
|
||||||
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
||||||
<div class="custom-select-wrapper">
|
<div class="custom-select-wrapper">
|
||||||
<Select
|
<Select
|
||||||
placeholder={loading.branches
|
placeholder={loading.branches
|
||||||
? 'Loading branches ...'
|
? 'Loading branches...'
|
||||||
: !selected.repository
|
: !selected.repository
|
||||||
? 'Please select a repository first'
|
? 'Please select a repository first'
|
||||||
: 'Please select a branch'}
|
: 'Please select a branch'}
|
||||||
id="repository"
|
isWaiting={loading.branches}
|
||||||
|
showIndicator={selected.repository}
|
||||||
|
id="branches"
|
||||||
on:select={isBranchAlreadyUsed}
|
on:select={isBranchAlreadyUsed}
|
||||||
items={branchSelectOptions}
|
items={branchSelectOptions}
|
||||||
isDisabled={loading.branches || !selected.repository}
|
isDisabled={loading.branches || !selected.repository}
|
||||||
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -202,13 +219,6 @@
|
|||||||
class:bg-orange-600={showSave}
|
class:bg-orange-600={showSave}
|
||||||
class:hover:bg-orange-500={showSave}>Save</button
|
class:hover:bg-orange-500={showSave}>Save</button
|
||||||
>
|
>
|
||||||
<!-- <button class="w-40"
|
|
||||||
><a
|
|
||||||
class="no-underline"
|
|
||||||
href="{apiUrl}/apps/{application.gitSource.githubApp.name}/installations/new"
|
|
||||||
>Modify Repositories</a
|
|
||||||
></button
|
|
||||||
> -->
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -45,7 +45,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
volumes: [volume],
|
volumes: [volume],
|
||||||
ulimits,
|
ulimits,
|
||||||
labels,
|
labels,
|
||||||
restart: 'always'
|
restart: 'always',
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import type { RequestHandler } from '@sveltejs/kit';
|
|||||||
export const get: RequestHandler = async (event) => {
|
export const get: RequestHandler = async (event) => {
|
||||||
const { teamId, status, body } = await getUserDetails(event);
|
const { teamId, status, body } = await getUserDetails(event);
|
||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
console.log(teamId);
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
try {
|
try {
|
||||||
const destination = await db.getDestination({ id, teamId });
|
const destination = await db.getDestination({ id, teamId });
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -32,21 +32,12 @@
|
|||||||
|
|
||||||
export let account;
|
export let account;
|
||||||
export let accounts;
|
export let accounts;
|
||||||
|
export let invitations;
|
||||||
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?');
|
||||||
@@ -74,12 +65,51 @@
|
|||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function acceptInvitation(id, teamId) {
|
||||||
|
try {
|
||||||
|
await post(`/iam/team/${teamId}/invitation/accept.json`, { id });
|
||||||
|
return window.location.reload();
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function revokeInvitation(id, teamId) {
|
||||||
|
try {
|
||||||
|
await post(`/iam/team/${teamId}/invitation/revoke.json`, { id });
|
||||||
|
return window.location.reload();
|
||||||
|
} catch ({ error }) {
|
||||||
|
return errorNotification(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
<div class="mr-4 text-2xl tracking-tight">Identity and Access Management</div>
|
<div class="mr-4 text-2xl tracking-tight">Identity and Access Management</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if invitations.length > 0}
|
||||||
|
<div class="mx-auto max-w-4xl px-6 py-4">
|
||||||
|
<div class="title font-bold">Pending invitations</div>
|
||||||
|
<div class="pt-10 text-center">
|
||||||
|
{#each invitations as invitation}
|
||||||
|
<div class="flex justify-center space-x-2">
|
||||||
|
<div>
|
||||||
|
Invited to <span class="font-bold text-pink-600">{invitation.teamName}</span> with
|
||||||
|
<span class="font-bold text-rose-600">{invitation.permission}</span> permission.
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="hover:bg-green-500"
|
||||||
|
on:click={() => acceptInvitation(invitation.id, invitation.teamId)}>Accept</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="hover:bg-red-600"
|
||||||
|
on:click={() => revokeInvitation(invitation.id, invitation.teamId)}>Delete</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="mx-auto max-w-4xl px-6 py-4">
|
<div class="mx-auto max-w-4xl px-6 py-4">
|
||||||
{#if $session.teamId === '0' && accounts.length > 0}
|
{#if $session.teamId === '0' && accounts.length > 0}
|
||||||
<div class="title font-bold">Accounts</div>
|
<div class="title font-bold">Accounts</div>
|
||||||
@@ -127,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>
|
||||||
|
|||||||
@@ -76,10 +76,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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -105,6 +105,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
|
||||||
|
|||||||
@@ -90,7 +90,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
environment: config.ghost.environmentVariables,
|
environment: config.ghost.environmentVariables,
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
labels: makeLabelForServices('ghost'),
|
labels: makeLabelForServices('ghost'),
|
||||||
depends_on: [`${id}-mariadb`]
|
depends_on: [`${id}-mariadb`],
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[`${id}-mariadb`]: {
|
[`${id}-mariadb`]: {
|
||||||
container_name: `${id}-mariadb`,
|
container_name: `${id}-mariadb`,
|
||||||
@@ -98,7 +106,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
networks: [network],
|
networks: [network],
|
||||||
volumes: [config.mariadb.volume],
|
volumes: [config.mariadb.volume],
|
||||||
environment: config.mariadb.environmentVariables,
|
environment: config.mariadb.environmentVariables,
|
||||||
restart: 'always'
|
restart: 'always',
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -43,7 +43,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
environment: config.environmentVariables,
|
environment: config.environmentVariables,
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
volumes: [config.volume],
|
volumes: [config.volume],
|
||||||
labels: makeLabelForServices('languagetool')
|
labels: makeLabelForServices('languagetool'),
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -48,7 +48,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
environment: config.environmentVariables,
|
environment: config.environmentVariables,
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
volumes: [config.volume],
|
volumes: [config.volume],
|
||||||
labels: makeLabelForServices('meilisearch')
|
labels: makeLabelForServices('meilisearch'),
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -67,7 +62,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
networks: [network],
|
networks: [network],
|
||||||
volumes: [config.volume],
|
volumes: [config.volume],
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
labels: makeLabelForServices('minio')
|
labels: makeLabelForServices('minio'),
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -44,7 +44,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
volumes: [config.volume],
|
volumes: [config.volume],
|
||||||
environment: config.environmentVariables,
|
environment: config.environmentVariables,
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
labels: makeLabelForServices('n8n')
|
labels: makeLabelForServices('n8n'),
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -40,7 +40,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
networks: [network],
|
networks: [network],
|
||||||
environment: config.environmentVariables,
|
environment: config.environmentVariables,
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
labels: makeLabelForServices('nocodb')
|
labels: makeLabelForServices('nocodb'),
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -133,7 +133,15 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
|||||||
environment: config.plausibleAnalytics.environmentVariables,
|
environment: config.plausibleAnalytics.environmentVariables,
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
depends_on: [`${id}-postgresql`, `${id}-clickhouse`],
|
depends_on: [`${id}-postgresql`, `${id}-clickhouse`],
|
||||||
labels: makeLabelForServices('plausibleAnalytics')
|
labels: makeLabelForServices('plausibleAnalytics'),
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '10s',
|
||||||
|
max_attempts: 5,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[`${id}-postgresql`]: {
|
[`${id}-postgresql`]: {
|
||||||
container_name: `${id}-postgresql`,
|
container_name: `${id}-postgresql`,
|
||||||
@@ -141,7 +149,15 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
|||||||
networks: [network],
|
networks: [network],
|
||||||
environment: config.postgresql.environmentVariables,
|
environment: config.postgresql.environmentVariables,
|
||||||
volumes: [config.postgresql.volume],
|
volumes: [config.postgresql.volume],
|
||||||
restart: 'always'
|
restart: 'always',
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '10s',
|
||||||
|
max_attempts: 5,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[`${id}-clickhouse`]: {
|
[`${id}-clickhouse`]: {
|
||||||
build: workdir,
|
build: workdir,
|
||||||
@@ -149,7 +165,15 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
|||||||
networks: [network],
|
networks: [network],
|
||||||
environment: config.clickhouse.environmentVariables,
|
environment: config.clickhouse.environmentVariables,
|
||||||
volumes: [config.clickhouse.volume],
|
volumes: [config.clickhouse.volume],
|
||||||
restart: 'always'
|
restart: 'always',
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '10s',
|
||||||
|
max_attempts: 5,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -42,7 +42,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
volumes: [config.volume],
|
volumes: [config.volume],
|
||||||
environment: config.environmentVariables,
|
environment: config.environmentVariables,
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
labels: makeLabelForServices('uptimekuma')
|
labels: makeLabelForServices('uptimekuma'),
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -43,7 +43,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
networks: [network],
|
networks: [network],
|
||||||
volumes: [config.volume],
|
volumes: [config.volume],
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
labels: makeLabelForServices('vaultWarden')
|
labels: makeLabelForServices('vaultWarden'),
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -52,7 +52,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
networks: [network],
|
networks: [network],
|
||||||
volumes: [config.volume],
|
volumes: [config.volume],
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
labels: makeLabelForServices('vscodeServer')
|
labels: makeLabelForServices('vscodeServer'),
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -77,7 +77,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
networks: [network],
|
networks: [network],
|
||||||
restart: 'always',
|
restart: 'always',
|
||||||
depends_on: [`${id}-mysql`],
|
depends_on: [`${id}-mysql`],
|
||||||
labels: makeLabelForServices('wordpress')
|
labels: makeLabelForServices('wordpress'),
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[`${id}-mysql`]: {
|
[`${id}-mysql`]: {
|
||||||
container_name: `${id}-mysql`,
|
container_name: `${id}-mysql`,
|
||||||
@@ -85,7 +93,15 @@ export const post: RequestHandler = async (event) => {
|
|||||||
volumes: [config.mysql.volume],
|
volumes: [config.mysql.volume],
|
||||||
environment: config.mysql.environmentVariables,
|
environment: config.mysql.environmentVariables,
|
||||||
networks: [network],
|
networks: [network],
|
||||||
restart: 'always'
|
restart: 'always',
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
export let source;
|
export let source;
|
||||||
import { page, session } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
@@ -51,7 +52,8 @@
|
|||||||
type: 'github',
|
type: 'github',
|
||||||
name: source.name,
|
name: source.name,
|
||||||
htmlUrl: source.htmlUrl.replace(/\/$/, ''),
|
htmlUrl: source.htmlUrl.replace(/\/$/, ''),
|
||||||
apiUrl: source.apiUrl.replace(/\/$/, '')
|
apiUrl: source.apiUrl.replace(/\/$/, ''),
|
||||||
|
organization: source.organization
|
||||||
});
|
});
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
@@ -97,6 +99,22 @@
|
|||||||
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
||||||
<input name="apiUrl" id="apiUrl" required bind:value={source.apiUrl} />
|
<input name="apiUrl" id="apiUrl" required bind:value={source.apiUrl} />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
|
||||||
|
>Organization</label
|
||||||
|
>
|
||||||
|
<Explainer
|
||||||
|
text="Fill it if you would like to use an organization's as your Git Source. Otherwise your user will be used."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
name="organization"
|
||||||
|
id="organization"
|
||||||
|
placeholder="eg: coollabsio"
|
||||||
|
bind:value={source.organization}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if source.apiUrl && source.htmlUrl && source.name}
|
{#if source.apiUrl && source.htmlUrl && source.name}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
@@ -135,6 +153,21 @@
|
|||||||
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
<label for="apiUrl" class="text-base font-bold text-stone-100">API URL</label>
|
||||||
<input name="apiUrl" id="apiUrl" required bind:value={source.apiUrl} />
|
<input name="apiUrl" id="apiUrl" required bind:value={source.apiUrl} />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="organization" class="pt-2 text-base font-bold text-stone-100"
|
||||||
|
>Organization</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
name="organization"
|
||||||
|
id="organization"
|
||||||
|
placeholder="eg: coollabsio"
|
||||||
|
bind:value={source.organization}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let { type, name, htmlUrl, apiUrl } = await event.request.json();
|
let { type, name, htmlUrl, apiUrl, organization } = await event.request.json();
|
||||||
await db.addGitHubSource({ id, teamId, type, name, htmlUrl, apiUrl });
|
await db.addGitHubSource({ id, teamId, type, name, htmlUrl, apiUrl, organization });
|
||||||
return { status: 201 };
|
return { status: 201 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return ErrorHandler(error);
|
return ErrorHandler(error);
|
||||||
|
|||||||
@@ -36,7 +36,6 @@
|
|||||||
export let settings;
|
export let settings;
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const { organization, id, htmlUrl } = source;
|
const { organization, id, htmlUrl } = source;
|
||||||
console.log(source);
|
|
||||||
const { fqdn } = settings;
|
const { fqdn } = settings;
|
||||||
const host = dev
|
const host = dev
|
||||||
? 'http://localhost:3000'
|
? 'http://localhost:3000'
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ textarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#svelte .listContainer {
|
#svelte .listContainer {
|
||||||
@apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-coollabs scrollbar-track-coolgray-200;
|
@apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-green-500 scrollbar-track-coolgray-200;
|
||||||
|
}
|
||||||
|
#svelte .selectedItem {
|
||||||
|
@apply pl-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#svelte .item.hover {
|
#svelte .item.hover {
|
||||||
|
|||||||
Reference in New Issue
Block a user