This commit is contained in:
Andras Bacsai 2022-10-28 15:50:57 +02:00
parent 781fd0a1cd
commit c123669828
14 changed files with 198 additions and 62 deletions

View File

@ -1795,25 +1795,25 @@
variables:
- id: $$config_wordpress_db_host
name: WORDPRESS_DB_HOST
label: WordPress DB Host
label: Database Host
defaultValue: $$id-mysql
description: ''
readonly: true
- id: $$config_wordpress_db_user
name: WORDPRESS_DB_USER
label: WordPress DB User
label: Database User
defaultValue: $$config_mysql_user
description: ''
readonly: true
- id: $$secret_wordpress_db_password
name: WORDPRESS_DB_PASSWORD
label: WordPress DB Password
label: Database Password
defaultValue: $$secret_mysql_password
description: ''
readonly: true
- id: $$config_wordpress_db_name
name: WORDPRESS_DB_NAME
label: WordPress DB Name
label: Database Name
defaultValue: $$config_mysql_database
description: ''
readonly: true

View File

@ -23,6 +23,7 @@
"@fastify/jwt": "6.3.2",
"@fastify/multipart": "7.3.0",
"@fastify/static": "6.5.0",
"@fastify/websocket": "^7.1.0",
"@iarna/toml": "2.2.5",
"@ladjs/graceful": "3.0.2",
"@prisma/client": "4.5.0",
@ -59,6 +60,7 @@
"devDependencies": {
"@types/node": "18.11.6",
"@types/node-os-utils": "1.3.0",
"@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "5.41.0",
"@typescript-eslint/parser": "5.41.0",
"esbuild": "0.15.12",

View File

@ -6,6 +6,8 @@ import cookie from '@fastify/cookie';
import multipart from '@fastify/multipart';
import path, { join } from 'path';
import autoLoad from '@fastify/autoload';
import websocket from '@fastify/websocket';
import { asyncExecShell, cleanupDockerStorage, createRemoteEngineConfiguration, decrypt, encrypt, executeDockerCmd, executeSSHCmd, generateDatabaseConfiguration, getDomain, isDev, listSettings, prisma, startTraefikProxy, startTraefikTCPProxy, version } from './lib/common';
import { scheduler } from './lib/scheduler';
import { compareVersions } from 'compare-versions';
@ -16,6 +18,8 @@ import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handler
import { checkContainer } from './lib/docker';
import { migrateServicesToNewTemplate } from './lib';
import { refreshTags, refreshTemplates } from './routes/api/v1/handlers';
import { realtime } from './routes/realtime';
import cuid from 'cuid';
declare module 'fastify' {
interface FastifyInstance {
config: {
@ -105,6 +109,23 @@ const host = '0.0.0.0';
});
fastify.register(cookie)
fastify.register(cors);
fastify.register(websocket, {
options: {
clientTracking: true,
verifyClient: async (info, next) => {
try {
const token = info.req.headers?.cookie.split('; ').find((cookie) => cookie.startsWith('token')).split('=')[1];
if (!token) {
return next(false)
}
fastify.jwt.verify(token)
} catch (error) {
return next(false)
}
next(true)
}
}
})
// To detect allowed origins
// fastify.addHook('onRequest', async (request, reply) => {
// let allowedList = ['coolify:3000'];
@ -127,6 +148,19 @@ const host = '0.0.0.0';
try {
fastify.decorate('wssend', function (data: any) {
fastify.websocketServer.clients.forEach((client) => {
client.send(JSON.stringify(data));
})
})
fastify.register(async function (fastify) {
fastify.get('/realtime', { websocket: true }, (connection) => {
// connection.socket.on('message', message => {
// realtime(fastify, connection, message)
// })
})
})
await fastify.listen({ port, host })
console.log(`Coolify's API is listening on ${host}:${port}`);

View File

@ -293,28 +293,47 @@ async function ghost(service: any, template: any) {
async function wordpress(service: any, template: any) {
const { extraConfig, tablePrefix, ownMysql, mysqlHost, mysqlPort, mysqlUser, mysqlPassword, mysqlRootUser, mysqlRootUserPassword, mysqlDatabase, ftpEnabled, ftpUser, ftpPassword, ftpPublicPort, ftpHostKey, ftpHostKeyPrivate } = service.wordpress
const secrets = [
`MYSQL_ROOT_PASSWORD@@@${mysqlRootUserPassword}`,
`MYSQL_PASSWORD@@@${mysqlPassword}`,
ftpPassword && `COOLIFY_FTP_PASSWORD@@@${ftpPassword}`,
ftpHostKeyPrivate && `COOLIFY_FTP_HOST_KEY_PRIVATE@@@${ftpHostKeyPrivate}`,
ftpHostKey && `COOLIFY_FTP_HOST_KEY@@@${ftpHostKey}`,
]
const settings = [
`MYSQL_ROOT_USER@@@${mysqlRootUser}`,
`MYSQL_USER@@@${mysqlUser}`,
`MYSQL_DATABASE@@@${mysqlDatabase}`,
`MYSQL_HOST@@@${ownMysql ? mysqlHost : `${service.id}-mysql`}`,
`MYSQL_PORT@@@${mysqlPort}`,
`WORDPRESS_CONFIG_EXTRA@@@${extraConfig}`,
`WORDPRESS_TABLE_PREFIX@@@${tablePrefix}`,
`WORDPRESS_DB_HOST@@@${ownMysql ? mysqlHost : `${service.id}-mysql`}`,
`COOLIFY_OWN_DB@@@${ownMysql}`,
`COOLIFY_FTP_ENABLED@@@${ftpEnabled}`,
`COOLIFY_FTP_USER@@@${ftpUser}`,
`COOLIFY_FTP_PUBLIC_PORT@@@${ftpPublicPort}`,
]
let settings = []
let secrets = []
if (ownMysql) {
secrets = [
`WORDPRESS_DB_PASSWORD@@@${mysqlPassword}`,
ftpPassword && `COOLIFY_FTP_PASSWORD@@@${ftpPassword}`,
ftpHostKeyPrivate && `COOLIFY_FTP_HOST_KEY_PRIVATE@@@${ftpHostKeyPrivate}`,
ftpHostKey && `COOLIFY_FTP_HOST_KEY@@@${ftpHostKey}`,
]
settings = [
`WORDPRESS_CONFIG_EXTRA@@@${extraConfig}`,
`WORDPRESS_DB_HOST@@@${mysqlHost}`,
`WORDPRESS_DB_PORT@@@${mysqlPort}`,
`WORDPRESS_DB_USER@@@${mysqlUser}`,
`WORDPRESS_DB_NAME@@@${mysqlDatabase}`,
]
} else {
secrets = [
`MYSQL_ROOT_PASSWORD@@@${mysqlRootUserPassword}`,
`MYSQL_PASSWORD@@@${mysqlPassword}`,
ftpPassword && `COOLIFY_FTP_PASSWORD@@@${ftpPassword}`,
ftpHostKeyPrivate && `COOLIFY_FTP_HOST_KEY_PRIVATE@@@${ftpHostKeyPrivate}`,
ftpHostKey && `COOLIFY_FTP_HOST_KEY@@@${ftpHostKey}`,
]
settings = [
`MYSQL_ROOT_USER@@@${mysqlRootUser}`,
`MYSQL_USER@@@${mysqlUser}`,
`MYSQL_DATABASE@@@${mysqlDatabase}`,
`MYSQL_HOST@@@${service.id}-mysql`,
`MYSQL_PORT@@@${mysqlPort}`,
`WORDPRESS_CONFIG_EXTRA@@@${extraConfig}`,
`WORDPRESS_TABLE_PREFIX@@@${tablePrefix}`,
`WORDPRESS_DB_HOST@@@${service.id}-mysql`,
`COOLIFY_OWN_DB@@@${ownMysql}`,
`COOLIFY_FTP_ENABLED@@@${ftpEnabled}`,
`COOLIFY_FTP_USER@@@${ftpUser}`,
`COOLIFY_FTP_PUBLIC_PORT@@@${ftpPublicPort}`,
]
}
await migrateSettings(settings, service, template);
await migrateSecrets(secrets, service);
if (ownMysql) {

View File

@ -29,7 +29,7 @@ export async function stopService(request: FastifyRequest<ServiceStartStop>) {
return errorHandler({ status, message })
}
}
export async function startService(request: FastifyRequest<ServiceStartStop>) {
export async function startService(request: FastifyRequest<ServiceStartStop>, fastify: any) {
try {
const { id } = request.params;
const teamId = request.user.teamId;
@ -42,12 +42,7 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
const template: any = await parseAndFindServiceTemplates(service, workdir, true)
const network = destinationDockerId && destinationDocker.network;
const config = {};
const isWordpress = type === 'wordpress';
for (const s in template.services) {
if (isWordpress && service.wordpress.ownMysql && s.name === 'MySQL') {
console.log('skipping predefined mysql')
continue;
}
let newEnvironments = []
if (arm) {
if (template.services[s]?.environmentArm?.length > 0) {
@ -90,23 +85,7 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
}
}
}
if (isWordpress) {
const { wordpress: { mysqlHost, mysqlPort, mysqlUser, mysqlPassword, mysqlDatabase, ownMysql } } = service
console.log({ mysqlHost, mysqlPort, mysqlUser, mysqlPassword, mysqlDatabase, ownMysql })
if (ownMysql) {
let envsToRemove = ['WORDPRESS_DB_HOST', 'WORDPRESS_DB_USER', 'WORDPRESS_DB_PASSWORD', 'WORDPRESS_DB_NAME']
for (const env of newEnvironments) {
if (envsToRemove.includes(env.split('=')[0])) {
console.log('removing', env)
newEnvironments = newEnvironments.filter(e => e !== env)
}
}
newEnvironments.push(`WORDPRESS_DB_HOST=${mysqlHost}:${mysqlPort}`)
newEnvironments.push(`WORDPRESS_DB_USER=${mysqlUser}`)
newEnvironments.push(`WORDPRESS_DB_PASSWORD=${mysqlPassword}`)
newEnvironments.push(`WORDPRESS_DB_NAME=${mysqlDatabase}`)
}
}
config[s] = {
container_name: s,
build: template.services[s].build || undefined,
@ -141,7 +120,7 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
Dockerfile += `
COPY ./${path.basename(source)} ${destination}`
}
await fs.writeFile(`${workdir}/Dockerfile.${servsice}`, Dockerfile);
await fs.writeFile(`${workdir}/Dockerfile.${service}`, Dockerfile);
}
}
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
@ -157,21 +136,26 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
}
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await startServiceContainers(destinationDocker.id, composeFileDestination)
await startServiceContainers(fastify, id, teamId, destinationDocker.id, composeFileDestination)
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
async function startServiceContainers(dockerId, composeFileDestination) {
async function startServiceContainers(fastify, id, teamId, dockerId, composeFileDestination) {
try {
fastify.wssend({ teamId, type: 'service', id, message: 'Pulling images...' })
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} pull` })
} catch (error) { }
fastify.wssend({ teamId, type: 'service', id, message: 'Building...' })
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} build --no-cache` })
fastify.wssend({ teamId, type: 'service', id, message: 'Creating containers...' })
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} create` })
fastify.wssend({ teamId, type: 'service', id, message: 'Starting containers...' })
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} start` })
await asyncSleep(1000);
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} up -d` })
fastify.wssend({ teamId, type: 'service', id, message: 'ending' })
}
export async function migrateAppwriteDB(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try {

View File

@ -1,9 +1,6 @@
import { FastifyPluginAsync } from 'fastify';
import { checkUpdate, login, showDashboard, update, resetQueue, getCurrentUser, cleanupManually, restartCoolify, refreshTemplates } from './handlers';
import { GetCurrentUser } from './types';
import pump from 'pump'
import fs from 'fs'
import { asyncExecShell, encrypt, errorHandler, prisma } from '../../../lib/common';
export interface Update {
Body: { latestVersion: string }

View File

@ -70,7 +70,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get<OnlyId>('/:id/usage', async (request) => await getServiceUsage(request));
fastify.get<GetServiceLogs>('/:id/logs/:containerId', async (request) => await getServiceLogs(request));
fastify.post<ServiceStartStop>('/:id/start', async (request) => await startService(request));
fastify.post<ServiceStartStop>('/:id/start', async (request) => await startService(request, fastify));
fastify.post<ServiceStartStop>('/:id/stop', async (request) => await stopService(request));
fastify.post<ServiceStartStop & SetWordpressSettings & SetGlitchTipSettings>('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply));

View File

@ -0,0 +1,7 @@
export function realtime(fastify, connection, message) {
const { socket } = connection
const data = JSON.parse(message);
if (data.type === 'subscribe') {
socket.send(JSON.stringify({ type: 'subscribe', message: 'pong' }))
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,6 @@
import { dev } from '$app/env';
import cuid from 'cuid';
import Cookies from 'js-cookie';
import { writable, readable, type Writable } from 'svelte/store';
interface AppSession {
@ -82,6 +83,7 @@ export const status: Writable<any> = writable({
statuses: [],
overallStatus: 'stopped',
loading: false,
startup: {},
initialLoading: true
},
database: {
@ -159,4 +161,57 @@ export const addToast = (toast: AddToast) => {
toasts.update((all: any) => [t, ...all])
}
export const selectedBuildId: any = writable(null)
export const selectedBuildId: any = writable(null)
type State = {
requests: Array<Request>;
};
export const state = writable<State>({
requests: [],
});
export const connect = () => {
const token = Cookies.get('token')
if (token) {
let url = "ws://localhost:3000/realtime"
if (dev) {
url = "ws://localhost:3001/realtime"
}
const ws = new WebSocket(url);
ws.addEventListener("message", (message: any) => {
appSession.subscribe((session: any) => {
const data: Request = { ...JSON.parse(message.data), timestamp: message.timeStamp };
if (data.teamId === session.teamId) {
if (data.type === 'service') {
const ending = data.message === "ending"
status.update((status: any) => ({
...status,
service: {
...status.service,
startup: {
...status.service.startup,
...(ending ? { [data.id]: undefined } : { [data.id]: data.message })
}
}
}))
}
}
})
});
ws.addEventListener('open', (event) => {
ws.send(JSON.stringify({ type: 'subscribe', message: 'ping' }))
});
ws.addEventListener('error', (event) => {
console.log('error with ws');
console.log(event)
});
ws.addEventListener('close', (event) => {
if (!ws || ws.readyState == 3) {
setTimeout(() => {
connect()
}, 1000)
}
});
}
};

View File

@ -81,6 +81,7 @@
export let permission: string;
export let isAdmin: boolean;
import{ status, connect } from '$lib/store';
import '../tailwind.css';
import Cookies from 'js-cookie';
import { fade } from 'svelte/transition';
@ -93,6 +94,7 @@
import { appSession } from '$lib/store';
import Toasts from '$lib/components/Toasts.svelte';
import Tooltip from '$lib/components/Tooltip.svelte';
import { onMount } from 'svelte';
if (userId) $appSession.userId = userId;
if (teamId) $appSession.teamId = teamId;
@ -107,6 +109,9 @@
return errorNotification(error);
}
}
onMount(async () => {
connect();
});
</script>
<svelte:head>

View File

@ -127,7 +127,7 @@
await post(`/services/${id}/wordpress/ftp`, {
ftpEnabled: false
});
window.location.reload()
service.wordpress?.ftpEnabled && window.location.reload()
}
} catch (error) {
return errorNotification(error);
@ -287,7 +287,7 @@
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
<line x1="11" y1="19.94" x2="11" y2="19.95" />
</svg>
Loading...
{$status.service.startup[id] || 'Loading...'}
</button>
{:else if $status.service.overallStatus === 'healthy'}
<button

View File

@ -399,10 +399,10 @@
/>
</div>
</div>
<div>
<div class="pt-6">
{#each Object.keys(template) as oneService}
<div
class="flex flex-row my-2 space-x-2"
class="flex flex-row my-2 space-x-2 mb-6"
class:my-6={template[oneService].environment.length > 0 &&
template[oneService].environment.find((env) => env.main === oneService)}
class:border-b={template[oneService].environment.length > 0 &&
@ -420,7 +420,7 @@
{#if template[oneService].environment.length > 0}
{#each template[oneService].environment as variable}
{#if variable.main === oneService}
<div class="grid grid-cols-2 items-center gap-2">
<div class="grid grid-cols-2 items-center gap-2" >
<label class="h-10" for={variable.name}
>{variable.label || variable.name}
{#if variable.description}

View File

@ -22,11 +22,13 @@ importers:
'@fastify/jwt': 6.3.2
'@fastify/multipart': 7.3.0
'@fastify/static': 6.5.0
'@fastify/websocket': ^7.1.0
'@iarna/toml': 2.2.5
'@ladjs/graceful': 3.0.2
'@prisma/client': 4.5.0
'@types/node': 18.11.6
'@types/node-os-utils': 1.3.0
'@types/ws': ^8.5.3
'@typescript-eslint/eslint-plugin': 5.41.0
'@typescript-eslint/parser': 5.41.0
bcryptjs: 2.4.3
@ -76,6 +78,7 @@ importers:
'@fastify/jwt': 6.3.2
'@fastify/multipart': 7.3.0
'@fastify/static': 6.5.0
'@fastify/websocket': 7.1.0
'@iarna/toml': 2.2.5
'@ladjs/graceful': 3.0.2
'@prisma/client': 4.5.0_prisma@4.5.0
@ -111,6 +114,7 @@ importers:
devDependencies:
'@types/node': 18.11.6
'@types/node-os-utils': 1.3.0
'@types/ws': 8.5.3
'@typescript-eslint/eslint-plugin': 5.41.0_huremdigmcnkianavgfk3x6iou
'@typescript-eslint/parser': 5.41.0_wyqvi574yv7oiwfeinomdzmc3m
esbuild: 0.15.12
@ -408,6 +412,16 @@ packages:
- supports-color
dev: false
/@fastify/websocket/7.1.0:
resolution: {integrity: sha512-aEHlymGnMOCY5pTSI6gOlLBX/5pIhQ485zyEx/m2+kITSa9vWtL08jbtAzbsaQBcqOz+bbVvxzerbHZdqQp2qQ==}
dependencies:
fastify-plugin: 4.3.0
ws: 8.10.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
dev: false
/@floating-ui/core/1.0.1:
resolution: {integrity: sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA==}
dev: true
@ -700,6 +714,12 @@ packages:
resolution: {integrity: sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==}
dev: true
/@types/ws/8.5.3:
resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==}
dependencies:
'@types/node': 18.11.6
dev: true
/@typescript-eslint/eslint-plugin/5.41.0_huremdigmcnkianavgfk3x6iou:
resolution: {integrity: sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -6100,6 +6120,19 @@ packages:
/wrappy/1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
/ws/8.10.0:
resolution: {integrity: sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: false
/xtend/4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}