mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
28 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 |
@@ -2,10 +2,8 @@ version: '3.8'
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: 'bitnami/redis:6.2'
|
||||
image: redis:6.2-alpine
|
||||
container_name: coolify-redis
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
networks:
|
||||
- coolify-infra
|
||||
ports:
|
||||
|
||||
@@ -21,11 +21,9 @@ services:
|
||||
- coolify-infra
|
||||
depends_on: ['redis']
|
||||
redis:
|
||||
image: bitnami/redis:6.2
|
||||
image: redis:6.2-alpine
|
||||
restart: always
|
||||
container_name: coolify-redis
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
networks:
|
||||
- coolify-infra
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "2.4.6",
|
||||
"version": "2.4.10",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev",
|
||||
|
||||
@@ -9,6 +9,7 @@ import { default as ProdPrisma } from '@prisma/client';
|
||||
import type { Database, DatabaseSettings } from '@prisma/client';
|
||||
import generator from 'generate-password';
|
||||
import forge from 'node-forge';
|
||||
import getPort, { portNumbers } from 'get-port';
|
||||
|
||||
export function generatePassword(length = 24): string {
|
||||
return generator.generate({
|
||||
@@ -251,3 +252,29 @@ export function generateDatabaseConfiguration(database: Database & { settings: D
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFreePort() {
|
||||
const data = await prisma.setting.findFirst();
|
||||
const { minPort, maxPort } = data;
|
||||
|
||||
const dbUsed = await (
|
||||
await prisma.database.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
select: { publicPort: true }
|
||||
})
|
||||
).map((a) => a.publicPort);
|
||||
const wpFtpUsed = await (
|
||||
await prisma.wordpress.findMany({
|
||||
where: { ftpPublicPort: { not: null } },
|
||||
select: { ftpPublicPort: true }
|
||||
})
|
||||
).map((a) => a.ftpPublicPort);
|
||||
const wpUsed = await (
|
||||
await prisma.wordpress.findMany({
|
||||
where: { mysqlPublicPort: { not: null } },
|
||||
select: { mysqlPublicPort: true }
|
||||
})
|
||||
).map((a) => a.mysqlPublicPort);
|
||||
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed];
|
||||
return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ export async function addGitLabSource({
|
||||
appSecret,
|
||||
groupName
|
||||
}) {
|
||||
const encrptedAppSecret = encrypt(appSecret);
|
||||
const encryptedAppSecret = encrypt(appSecret);
|
||||
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name } });
|
||||
return await prisma.gitlabApp.create({
|
||||
data: {
|
||||
|
||||
@@ -46,8 +46,12 @@ export async function login({
|
||||
if (users === 0) {
|
||||
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
|
||||
// Create default network & start Coolify Proxy
|
||||
try {
|
||||
await asyncExecShell(`docker network create --attachable coolify`);
|
||||
} catch (error) {}
|
||||
try {
|
||||
await startCoolifyProxy('/var/run/docker.sock');
|
||||
} catch (error) {}
|
||||
uid = '0';
|
||||
}
|
||||
|
||||
|
||||
@@ -127,10 +127,10 @@ export async function startTcpProxy(
|
||||
|
||||
const containerName = `haproxy-for-${publicPort}`;
|
||||
const found = await checkContainer(engine, containerName);
|
||||
const foundDB = await checkContainer(engine, id);
|
||||
const foundDependentContainer = await checkContainer(engine, id);
|
||||
|
||||
try {
|
||||
if (foundDB && !found) {
|
||||
if (foundDependentContainer && !found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||
);
|
||||
@@ -141,6 +141,11 @@ export async function startTcpProxy(
|
||||
} -d coollabsio/${defaultProxyImageTcp}`
|
||||
);
|
||||
}
|
||||
if (!foundDependentContainer && found) {
|
||||
return await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
@@ -157,10 +162,10 @@ export async function startHttpProxy(
|
||||
|
||||
const containerName = `haproxy-for-${publicPort}`;
|
||||
const found = await checkContainer(engine, containerName);
|
||||
const foundDB = await checkContainer(engine, id);
|
||||
const foundDependentContainer = await checkContainer(engine, id);
|
||||
|
||||
try {
|
||||
if (foundDB && !found) {
|
||||
if (foundDependentContainer && !found) {
|
||||
const { stdout: Config } = await asyncExecShell(
|
||||
`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}`
|
||||
);
|
||||
}
|
||||
if (!foundDependentContainer && found) {
|
||||
return await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
@@ -236,9 +246,15 @@ export async function stopCoolifyProxy(
|
||||
export async function configureNetworkCoolifyProxy(engine: string): Promise<void> {
|
||||
const host = getEngine(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) {
|
||||
if (!configuredNetworks.includes(destination.network)) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-haproxy`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ export async function generateSSLCerts(): Promise<void> {
|
||||
certificates.includes(ssl.domain) ||
|
||||
certificates.includes(ssl.domain.replace('www.', ''))
|
||||
) {
|
||||
console.log(`Certificate for ${ssl.domain} already exists`);
|
||||
// console.log(`Certificate for ${ssl.domain} already exists`);
|
||||
} else {
|
||||
// Checking DNS entry before generating certificate
|
||||
if (ipv4 || ipv6) {
|
||||
@@ -232,7 +232,7 @@ export async function generateSSLCerts(): Promise<void> {
|
||||
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
||||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
||||
) {
|
||||
console.log('Generating SSL for', ssl.domain, '.');
|
||||
console.log('Generating SSL for', ssl.domain);
|
||||
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
||||
}
|
||||
}
|
||||
@@ -261,7 +261,7 @@ export async function generateSSLCerts(): Promise<void> {
|
||||
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
||||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
||||
) {
|
||||
console.log('Generating SSL for', ssl.domain, '.');
|
||||
console.log('Generating SSL for', ssl.domain);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ import { asyncExecShell, getEngine, version } from '$lib/common';
|
||||
import { prisma } from '$lib/database';
|
||||
export default async function (): Promise<void> {
|
||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||
for (const destinationDocker of destinationDockers) {
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
const engines = [...new Set(destinationDockers.map(({ engine }) => engine))];
|
||||
for (const engine of engines) {
|
||||
const host = getEngine(engine);
|
||||
// Cleanup old coolify images
|
||||
try {
|
||||
let { stdout: images } = await asyncExecShell(
|
||||
@@ -28,7 +29,7 @@ export default async function (): Promise<void> {
|
||||
}
|
||||
// Cleanup old images older than a day
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=24h" -a -f`);
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
|
||||
} catch (error) {
|
||||
//console.log(error);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import builder from './builder';
|
||||
import logger from './logger';
|
||||
import cleanup from './cleanup';
|
||||
import proxy from './proxy';
|
||||
import proxyTcpHttp from './proxyTcpHttp';
|
||||
import ssl from './ssl';
|
||||
import sslrenewal from './sslrenewal';
|
||||
|
||||
@@ -29,17 +30,20 @@ const connectionOptions = {
|
||||
|
||||
const cron = async (): Promise<void> => {
|
||||
new QueueScheduler('proxy', connectionOptions);
|
||||
new QueueScheduler('proxyTcpHttp', connectionOptions);
|
||||
new QueueScheduler('cleanup', connectionOptions);
|
||||
new QueueScheduler('ssl', connectionOptions);
|
||||
new QueueScheduler('sslRenew', connectionOptions);
|
||||
|
||||
const queue = {
|
||||
proxy: new Queue('proxy', { ...connectionOptions }),
|
||||
proxyTcpHttp: new Queue('proxyTcpHttp', { ...connectionOptions }),
|
||||
cleanup: new Queue('cleanup', { ...connectionOptions }),
|
||||
ssl: new Queue('ssl', { ...connectionOptions }),
|
||||
sslRenew: new Queue('sslRenew', { ...connectionOptions })
|
||||
};
|
||||
await queue.proxy.drain();
|
||||
await queue.proxyTcpHttp.drain();
|
||||
await queue.cleanup.drain();
|
||||
await queue.ssl.drain();
|
||||
await queue.sslRenew.drain();
|
||||
@@ -54,6 +58,16 @@ const cron = async (): Promise<void> => {
|
||||
}
|
||||
);
|
||||
|
||||
new Worker(
|
||||
'proxyTcpHttp',
|
||||
async () => {
|
||||
await proxyTcpHttp();
|
||||
},
|
||||
{
|
||||
...connectionOptions
|
||||
}
|
||||
);
|
||||
|
||||
new Worker(
|
||||
'ssl',
|
||||
async () => {
|
||||
@@ -85,6 +99,7 @@ const cron = async (): Promise<void> => {
|
||||
);
|
||||
|
||||
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 } });
|
||||
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
|
||||
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
||||
|
||||
55
src/lib/queues/proxyTcpHttp.ts
Normal file
55
src/lib/queues/proxyTcpHttp.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database';
|
||||
import { startCoolifyProxy, startHttpProxy, startTcpProxy } from '$lib/haproxy';
|
||||
|
||||
export default async function (): Promise<void | {
|
||||
status: number;
|
||||
body: { message: string; error: string };
|
||||
}> {
|
||||
try {
|
||||
// Coolify Proxy
|
||||
const localDocker = await prisma.destinationDocker.findFirst({
|
||||
where: { engine: '/var/run/docker.sock' }
|
||||
});
|
||||
if (localDocker && localDocker.isCoolifyProxyUsed) {
|
||||
await startCoolifyProxy('/var/run/docker.sock');
|
||||
}
|
||||
// TCP Proxies
|
||||
const databasesWithPublicPort = await prisma.database.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
include: { settings: true, destinationDocker: true }
|
||||
});
|
||||
for (const database of databasesWithPublicPort) {
|
||||
const { destinationDockerId, destinationDocker, publicPort, id } = database;
|
||||
if (destinationDockerId) {
|
||||
const { privatePort } = generateDatabaseConfiguration(database);
|
||||
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||
}
|
||||
}
|
||||
const wordpressWithFtp = await prisma.wordpress.findMany({
|
||||
where: { ftpPublicPort: { not: null } },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
for (const ftp of wordpressWithFtp) {
|
||||
const { service, ftpPublicPort } = ftp;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId) {
|
||||
await startTcpProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22);
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP Proxies
|
||||
const minioInstances = await prisma.minio.findMany({
|
||||
where: { publicPort: { not: null } },
|
||||
include: { service: { include: { destinationDocker: true } } }
|
||||
});
|
||||
for (const minio of minioInstances) {
|
||||
const { service, publicPort } = minio;
|
||||
const { destinationDockerId, destinationDocker, id } = service;
|
||||
if (destinationDockerId) {
|
||||
await startHttpProxy(destinationDocker, id, publicPort, 9000);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error.response?.body || error);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
export const publicPaths = [
|
||||
'/login',
|
||||
'/register',
|
||||
'/reset',
|
||||
'/reset/password',
|
||||
'/webhooks/success',
|
||||
'/webhooks/github',
|
||||
'/webhooks/github/install',
|
||||
|
||||
@@ -29,10 +29,12 @@
|
||||
disabled={!isRunning}
|
||||
readonly={!isRunning}
|
||||
placeholder="Generated automatically after start"
|
||||
isPasswordField
|
||||
id="rootUserPassword"
|
||||
name="rootUserPassword"
|
||||
bind:value={database.rootUserPassword}
|
||||
/>
|
||||
<Explainer text="Could be changed while the database is running." />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import * as db 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';
|
||||
|
||||
export const del: RequestHandler = async (event) => {
|
||||
@@ -12,7 +12,7 @@ export const del: RequestHandler = async (event) => {
|
||||
const database = await db.getDatabase({ id, teamId });
|
||||
if (database.destinationDockerId) {
|
||||
const everStarted = await stopDatabase(database);
|
||||
if (everStarted) await deleteProxy({ id });
|
||||
if (everStarted) await stopTcpHttpProxy(database.destinationDocker, database.publicPort);
|
||||
}
|
||||
await db.removeDatabase({ id });
|
||||
return { status: 200 };
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
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 type { RequestHandler } from '@sveltejs/kit';
|
||||
import getPort, { portNumbers } from 'get-port';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { status, body, teamId } = await getUserDetails(event);
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const data = await db.prisma.setting.findFirst();
|
||||
const { minPort, maxPort } = data;
|
||||
|
||||
const { isPublic, appendOnly = true } = await event.request.json();
|
||||
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
|
||||
const publicPort = await getFreePort();
|
||||
|
||||
try {
|
||||
await db.setDatabase({ id, isPublic, appendOnly });
|
||||
|
||||
@@ -13,20 +13,25 @@ export const get: RequestHandler = async (event) => {
|
||||
select: { id: true, email: true, teams: true }
|
||||
});
|
||||
let accounts = [];
|
||||
let allTeams = [];
|
||||
if (teamId === '0') {
|
||||
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 teams = await db.prisma.permission.findMany({
|
||||
where: { userId: teamId === '0' ? undefined : userId },
|
||||
include: { team: { include: { _count: { select: { users: true } } } } }
|
||||
const ownTeams = await db.prisma.team.findMany({
|
||||
where: { users: { some: { id: userId } } },
|
||||
include: { permissions: true }
|
||||
});
|
||||
|
||||
const invitations = await db.prisma.teamInvitation.findMany({ where: { uid: userId } });
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
teams,
|
||||
ownTeams,
|
||||
allTeams,
|
||||
invitations,
|
||||
account,
|
||||
accounts
|
||||
|
||||
@@ -36,18 +36,8 @@
|
||||
if (accounts.length === 0) {
|
||||
accounts.push(account);
|
||||
}
|
||||
export let teams;
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
export let ownTeams;
|
||||
export let allTeams;
|
||||
|
||||
async function resetPassword(id) {
|
||||
const sure = window.confirm('Are you sure you want to reset the password?');
|
||||
@@ -167,49 +157,51 @@
|
||||
<div class="title font-bold">Teams</div>
|
||||
<div class="flex items-center justify-center pt-10">
|
||||
<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}
|
||||
<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
|
||||
class="box-selection relative"
|
||||
class:hover:bg-cyan-600={team.team?.id !== '0'}
|
||||
class:hover:bg-red-500={team.team?.id === '0'}
|
||||
class:hover:bg-cyan-600={team.id !== '0'}
|
||||
class:hover:bg-red-500={team.id === '0'}
|
||||
>
|
||||
<div class="truncate text-center text-xl font-bold">
|
||||
{team.team.name}
|
||||
{team.name}
|
||||
</div>
|
||||
<div class="truncate text-center font-bold">
|
||||
{team.team?.id === '0' ? 'root team' : ''}
|
||||
{team.id === '0' ? 'root team' : ''}
|
||||
</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>
|
||||
</a>
|
||||
{/each}
|
||||
</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>
|
||||
{/if}
|
||||
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||
{#each otherTeams as team}
|
||||
<a href="/iam/team/{team.teamId}" class="w-96 p-2 no-underline">
|
||||
<div class="flex flex-row flex-wrap justify-center px-2 md:flex-row">
|
||||
{#each allTeams as team}
|
||||
<a href="/iam/team/{team.id}" class="w-96 p-2 no-underline">
|
||||
<div
|
||||
class="box-selection relative"
|
||||
class:hover:bg-cyan-600={team.team?.id !== '0'}
|
||||
class:hover:bg-red-500={team.team?.id === '0'}
|
||||
class:hover:bg-cyan-600={team.id !== '0'}
|
||||
class:hover:bg-red-500={team.id === '0'}
|
||||
>
|
||||
<div class="truncate text-center text-xl font-bold">
|
||||
{team.team.name}
|
||||
{team.name}
|
||||
</div>
|
||||
<div class="truncate text-center font-bold">
|
||||
{team.team?.id === '0' ? 'root team' : ''}
|
||||
{team.id === '0' ? 'root team' : ''}
|
||||
</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>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,10 +76,6 @@
|
||||
on:click|preventDefault={() => goto('/register')}
|
||||
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>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
let loading = false;
|
||||
|
||||
async function handleSubmit() {
|
||||
if (loading) return;
|
||||
try {
|
||||
loading = true;
|
||||
await post('/new/destination/check.json', { network: payload.network });
|
||||
const { id } = await post('/new/destination/docker.json', {
|
||||
...payload
|
||||
|
||||
@@ -105,6 +105,9 @@
|
||||
{#if userCount === 0}
|
||||
<div class="pt-5">
|
||||
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>
|
||||
{/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">
|
||||
<label for="extraConfig">Extra Config</label>
|
||||
<textarea
|
||||
bind:value={service.wordpress.extraConfig}
|
||||
disabled={isRunning}
|
||||
readonly={isRunning}
|
||||
class:resize-none={isRunning}
|
||||
rows={isRunning ? 1 : 5}
|
||||
rows="5"
|
||||
name="extraConfig"
|
||||
id="extraConfig"
|
||||
placeholder={!isRunning
|
||||
@@ -74,8 +75,8 @@
|
||||
define('WP_ALLOW_MULTISITE', true);
|
||||
define('MULTISITE', true);
|
||||
define('SUBDOMAIN_INSTALL', false);`
|
||||
: 'N/A'}>{service.wordpress.extraConfig}</textarea
|
||||
>
|
||||
: 'N/A'}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<Setting
|
||||
|
||||
@@ -4,9 +4,7 @@ import { promises as fs } from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { startHttpProxy } from '$lib/haproxy';
|
||||
import getPort, { portNumbers } from 'get-port';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { ErrorHandler, getServiceImage } from '$lib/database';
|
||||
import { ErrorHandler, getFreePort, getServiceImage } from '$lib/database';
|
||||
import { makeLabelForServices } from '$lib/buildPacks/common';
|
||||
import type { ComposeFile } from '$lib/types/composeFile';
|
||||
|
||||
@@ -28,13 +26,10 @@ export const post: RequestHandler = async (event) => {
|
||||
serviceSecret
|
||||
} = service;
|
||||
|
||||
const data = await db.prisma.setting.findFirst();
|
||||
const { minPort, maxPort } = data;
|
||||
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
|
||||
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
|
||||
const publicPort = await getFreePort();
|
||||
|
||||
const consolePort = 9001;
|
||||
const apiPort = 9000;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { dev } from '$app/env';
|
||||
import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
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 type { ComposeFile } from '$lib/types/composeFile';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
@@ -16,11 +16,10 @@ export const post: RequestHandler = async (event) => {
|
||||
if (status === 401) return { status, body };
|
||||
|
||||
const { id } = event.params;
|
||||
const data = await db.prisma.setting.findFirst();
|
||||
const { minPort, maxPort } = data;
|
||||
|
||||
const { ftpEnabled } = await event.request.json();
|
||||
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
|
||||
const publicPort = await getFreePort();
|
||||
|
||||
let ftpUser = cuid();
|
||||
let ftpPassword = generatePassword();
|
||||
|
||||
@@ -114,7 +113,7 @@ export const post: RequestHandler = async (event) => {
|
||||
services: {
|
||||
[`${id}-ftp`]: {
|
||||
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'],
|
||||
container_name: `${id}-ftp`,
|
||||
volumes,
|
||||
|
||||
@@ -12,21 +12,44 @@ export const post: RequestHandler = async (event) => {
|
||||
|
||||
try {
|
||||
const service = await db.getService({ id, teamId });
|
||||
const { destinationDockerId, destinationDocker, fqdn } = service;
|
||||
const {
|
||||
destinationDockerId,
|
||||
destinationDocker,
|
||||
fqdn,
|
||||
wordpress: { ftpEnabled }
|
||||
} = service;
|
||||
if (destinationDockerId) {
|
||||
const engine = destinationDocker.engine;
|
||||
try {
|
||||
let found = await checkContainer(engine, id);
|
||||
const found = await checkContainer(engine, id);
|
||||
if (found) {
|
||||
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) {
|
||||
await removeDestinationDocker({ id: `${id}-mysql`, engine });
|
||||
}
|
||||
} catch (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 {
|
||||
|
||||
Reference in New Issue
Block a user