From c917135bd34cd04bfaa1dfb22bcfef88c1e5bf77 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 May 2022 11:02:21 +0200 Subject: [PATCH 01/44] fix: Service checks --- src/routes/applications/[id]/check.json.ts | 2 +- .../services/[id]/_Services/_Services.svelte | 5 +++- src/routes/services/[id]/check.json.ts | 23 ++++++++++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/routes/applications/[id]/check.json.ts b/src/routes/applications/[id]/check.json.ts index 021cb5975..6e3b77a6d 100644 --- a/src/routes/applications/[id]/check.json.ts +++ b/src/routes/applications/[id]/check.json.ts @@ -52,7 +52,7 @@ export const post: RequestHandler = async (event) => { exposePort = Number(exposePort); if (exposePort < 1024 || exposePort > 65535) { - throw { message: `Expose Port needs to be between 1024 and 65535.` }; + throw { message: `Exposed Port needs to be between 1024 and 65535.` }; } const publicPort = await getPort({ port: exposePort }); diff --git a/src/routes/services/[id]/_Services/_Services.svelte b/src/routes/services/[id]/_Services/_Services.svelte index facf713c1..00ef5d305 100644 --- a/src/routes/services/[id]/_Services/_Services.svelte +++ b/src/routes/services/[id]/_Services/_Services.svelte @@ -31,7 +31,10 @@ async function handleSubmit() { loading = true; try { - await post(`/services/${id}/check.json`, { fqdn: service.fqdn }); + await post(`/services/${id}/check.json`, { + fqdn: service.fqdn, + exposePort: service.exposePort + }); await post(`/services/${id}/${service.type}.json`, { ...service }); return window.location.reload(); } catch ({ error }) { diff --git a/src/routes/services/[id]/check.json.ts b/src/routes/services/[id]/check.json.ts index 9a9193524..2214de3bf 100644 --- a/src/routes/services/[id]/check.json.ts +++ b/src/routes/services/[id]/check.json.ts @@ -1,19 +1,40 @@ import { asyncExecShell, getDomain, getEngine, getUserDetails } from '$lib/common'; import * as db from '$lib/database'; import { ErrorHandler } from '$lib/database'; +import { t } from '$lib/translations'; import type { RequestHandler } from '@sveltejs/kit'; +import getPort from 'get-port'; export const post: RequestHandler = async (event) => { const { status, body } = await getUserDetails(event); if (status === 401) return { status, body }; const { id } = event.params; - let { fqdn } = await event.request.json(); + let { fqdn, exposePort } = await event.request.json(); if (fqdn) fqdn = fqdn.toLowerCase(); try { const found = await db.isDomainConfigured({ id, fqdn }); + if (found) { + throw { + message: t.get('application.domain_already_in_use', { + domain: getDomain(fqdn).replace('www.', '') + }) + }; + } + if (exposePort) { + exposePort = Number(exposePort); + + if (exposePort < 1024 || exposePort > 65535) { + throw { message: `Exposed Port needs to be between 1024 and 65535.` }; + } + + const publicPort = await getPort({ port: exposePort }); + if (publicPort !== exposePort) { + throw { message: `Port ${exposePort} is already in use.` }; + } + } return { status: found ? 500 : 200, body: { From 242bc61e2d76fb9a32f614fca55d61000f9b4399 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 May 2022 11:02:28 +0200 Subject: [PATCH 02/44] Readme update --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f4b7af6ef..da200223a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ ## Live Demo (If it is unresponsive, that means someone overloaded the server. 🙃) ## Feedback + If you have a new service / build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community! ## How to install @@ -62,6 +63,7 @@ ### Applications - Rust - Docker - Python +- Deno ### Databases From cef571b8cc5ac2a84feece37edab029d34beeb6b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 May 2022 11:03:01 +0200 Subject: [PATCH 03/44] chore: version++ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f5bea178..0c6bde272 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "2.8.1", + "version": "2.8.2", "license": "AGPL-3.0", "scripts": { "dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0", From 3435f92fcb41de56fe577c4d25d6284c4f58a009 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 May 2022 12:02:09 +0200 Subject: [PATCH 04/44] WIP: Appwrite --- src/lib/components/common.ts | 11 + .../services/[id]/appwrite/index.json.ts | 21 + .../services/[id]/appwrite/start.json.ts | 519 ++++++++++++++++++ .../services/[id]/appwrite/stop.json.ts | 35 ++ 4 files changed, 586 insertions(+) create mode 100644 src/routes/services/[id]/appwrite/index.json.ts create mode 100644 src/routes/services/[id]/appwrite/start.json.ts create mode 100644 src/routes/services/[id]/appwrite/stop.json.ts diff --git a/src/lib/components/common.ts b/src/lib/components/common.ts index d42fcb726..3ded5d12b 100644 --- a/src/lib/components/common.ts +++ b/src/lib/components/common.ts @@ -219,6 +219,17 @@ export const supportedServiceTypesAndVersions = [ ports: { main: 3000 } + }, + { + name: 'appwrite', + fancyName: 'AppWrite', + baseImage: 'appwrite/appwrite', + images: ['appwrite/influxdb', 'appwrite/telegraf', 'mariadb:10.7', 'redis:6.0-alpine3.12'], + versions: ['latest', '0.13.0'], + recommendedVersion: '0.13.0', + ports: { + main: 3000 + } } ]; diff --git a/src/routes/services/[id]/appwrite/index.json.ts b/src/routes/services/[id]/appwrite/index.json.ts new file mode 100644 index 000000000..e269e8fe7 --- /dev/null +++ b/src/routes/services/[id]/appwrite/index.json.ts @@ -0,0 +1,21 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { ErrorHandler } from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + const { id } = event.params; + + let { name, fqdn, exposePort } = await event.request.json(); + if (fqdn) fqdn = fqdn.toLowerCase(); + if (exposePort) exposePort = Number(exposePort); + + try { + await db.updateService({ id, fqdn, name, exposePort }); + return { status: 201 }; + } catch (error) { + return ErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/appwrite/start.json.ts b/src/routes/services/[id]/appwrite/start.json.ts new file mode 100644 index 000000000..70748a0a6 --- /dev/null +++ b/src/routes/services/[id]/appwrite/start.json.ts @@ -0,0 +1,519 @@ +import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { promises as fs } from 'fs'; +import yaml from 'js-yaml'; +import type { RequestHandler } from '@sveltejs/kit'; +import { ErrorHandler, getServiceImage } from '$lib/database'; +import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; +import { getServiceMainPort } from '$lib/components/common'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } = + service; + const network = destinationDockerId && destinationDocker.network; + const host = getEngine(destinationDocker.engine); + const port = getServiceMainPort('n8n'); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + const image = getServiceImage(type); + + if (serviceSecret.length > 0) { + serviceSecret.forEach((secret) => { + variables[secret.name] = secret.value; + }); + } + + const variables = { + _APP_ENV: 'production', + _APP_VERSION: '', + _APP_LOCALE: '', + _APP_OPTIONS_ABUSE: '', + _APP_OPTIONS_FORCE_HTTPS: '', + _APP_OPENSSL_KEY_V1: '', + _APP_DOMAIN: '', + _APP_DOMAIN_TARGET: '', + _APP_CONSOLE_WHITELIST_ROOT: '', + _APP_CONSOLE_WHITELIST_EMAILS: '', + _APP_CONSOLE_WHITELIST_IPS: '', + _APP_SYSTEM_EMAIL_NAME: '', + _APP_SYSTEM_EMAIL_ADDRESS: '', + _APP_SYSTEM_RESPONSE_FORMAT: '', + _APP_SYSTEM_SECURITY_EMAIL_ADDRESS: '', + _APP_USAGE_STATS: '', + _APP_LOGGING_PROVIDER: '', + _APP_LOGGING_CONFIG: '', + _APP_USAGE_AGGREGATION_INTERVAL: '', + _APP_WORKER_PER_CORE: '', + _APP_REDIS_HOST: '', + _APP_REDIS_PORT: '', + _APP_REDIS_USER: '', + _APP_REDIS_PASS: '', + _APP_DB_HOST: '', + _APP_DB_PORT: '', + _APP_DB_SCHEMA: '', + _APP_DB_USER: '', + _APP_DB_PASS: '', + _APP_DB_ROOT_PASS: '', + _APP_INFLUXDB_HOST: '', + _APP_INFLUXDB_PORT: '', + _APP_STATSD_HOST: '', + _APP_STATSD_PORT: '', + _APP_SMTP_HOST: '', + _APP_SMTP_PORT: '', + _APP_SMTP_SECURE: '', + _APP_SMTP_USERNAME: '', + _APP_SMTP_PASSWORD: '', + _APP_STORAGE_LIMIT: '', + _APP_STORAGE_ANTIVIRUS: '', + _APP_STORAGE_ANTIVIRUS_HOST: '', + _APP_STORAGE_ANTIVIRUS_PORT: '', + _APP_STORAGE_DEVICE: '', + _APP_STORAGE_S3_ACCESS_KEY: '', + _APP_STORAGE_S3_SECRET: '', + _APP_STORAGE_S3_REGION: '', + _APP_STORAGE_S3_BUCKET: '', + _APP_STORAGE_DO_SPACES_ACCESS_KEY: '', + _APP_STORAGE_DO_SPACES_SECRET: '', + _APP_STORAGE_DO_SPACES_REGION: '', + _APP_STORAGE_DO_SPACES_BUCKET: '', + _APP_FUNCTIONS_SIZE_LIMIT: '', + _APP_FUNCTIONS_TIMEOUT: '', + _APP_FUNCTIONS_BUILD_TIMEOUT: '', + _APP_FUNCTIONS_CONTAINERS: '', + _APP_FUNCTIONS_CPUS: '', + _APP_FUNCTIONS_MEMORY: '', + _APP_FUNCTIONS_MEMORY_SWAP: '', + _APP_FUNCTIONS_RUNTIMES: '', + _APP_EXECUTOR_SECRET: '', + _APP_EXECUTOR_RUNTIME_NETWORK: '', + _APP_FUNCTIONS_ENVS: '', + _APP_FUNCTIONS_INACTIVE_THRESHOLD: '', + DOCKERHUB_PULL_USERNAME: '', + DOCKERHUB_PULL_PASSWORD: '', + DOCKERHUB_PULL_EMAIL: '', + _APP_MAINTENANCE_INTERVAL: '', + _APP_MAINTENANCE_RETENTION_EXECUTION: '', + _APP_MAINTENANCE_RETENTION_ABUSE: '', + _APP_MAINTENANCE_RETENTION_AUDIT: '' + }; + const config = { + appwrite: { + image: `${image}:${version}`, + volumes: [ + `${id}-appwrite-uploads:/storage/uploads`, + `${id}-appwrite-cache:/storage/cache`, + `${id}-appwrite-config:/storage/config`, + `${id}-appwrite-certificates:/storage/certificates`, + `${id}-appwrite-functions:/storage/functions` + ], + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_WORKER_PER_CORE: variables._APP_WORKER_PER_CORE, + _APP_LOCALE: variables._APP_LOCALE, + _APP_CONSOLE_WHITELIST_ROOT: variables._APP_CONSOLE_WHITELIST_ROOT, + _APP_CONSOLE_WHITELIST_EMAILS: variables._APP_CONSOLE_WHITELIST_EMAILS, + _APP_CONSOLE_WHITELIST_IPS: variables._APP_CONSOLE_WHITELIST_IPS, + _APP_SYSTEM_EMAIL_NAME: variables._APP_SYSTEM_EMAIL_NAME, + _APP_SYSTEM_EMAIL_ADDRESS: variables._APP_SYSTEM_EMAIL_ADDRESS, + _APP_SYSTEM_SECURITY_EMAIL_ADDRESS: variables._APP_SYSTEM_SECURITY_EMAIL_ADDRESS, + _APP_SYSTEM_RESPONSE_FORMAT: variables._APP_SYSTEM_RESPONSE_FORMAT, + _APP_OPTIONS_ABUSE: variables._APP_OPTIONS_ABUSE, + _APP_OPTIONS_FORCE_HTTPS: variables._APP_OPTIONS_FORCE_HTTPS, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_DOMAIN: variables._APP_DOMAIN, + _APP_DOMAIN_TARGET: variables._APP_DOMAIN_TARGET, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS, + _APP_DB_HOST: variables._APP_DB_HOST, + _APP_DB_PORT: variables._APP_DB_PORT, + _APP_DB_SCHEMA: variables._APP_DB_SCHEMA, + _APP_DB_USER: variables._APP_DB_USER, + _APP_DB_PASS: variables._APP_DB_PASS, + _APP_SMTP_HOST: variables._APP_SMTP_HOST, + _APP_SMTP_PORT: variables._APP_SMTP_PORT, + _APP_SMTP_SECURE: variables._APP_SMTP_SECURE, + _APP_SMTP_USERNAME: variables._APP_SMTP_USERNAME, + _APP_SMTP_PASSWORD: variables._APP_SMTP_PASSWORD, + _APP_USAGE_STATS: variables._APP_USAGE_STATS, + _APP_INFLUXDB_HOST: variables._APP_INFLUXDB_HOST, + _APP_INFLUXDB_PORT: variables._APP_INFLUXDB_PORT, + _APP_STORAGE_LIMIT: variables._APP_STORAGE_LIMIT, + _APP_STORAGE_ANTIVIRUS: variables._APP_STORAGE_ANTIVIRUS, + _APP_STORAGE_ANTIVIRUS_HOST: variables._APP_STORAGE_ANTIVIRUS_HOST, + _APP_STORAGE_ANTIVIRUS_PORT: variables._APP_STORAGE_ANTIVIRUS_PORT, + _APP_STORAGE_DEVICE: variables._APP_STORAGE_DEVICE, + _APP_STORAGE_S3_ACCESS_KEY: variables._APP_STORAGE_S3_ACCESS_KEY, + _APP_STORAGE_S3_SECRET: variables._APP_STORAGE_S3_SECRET, + _APP_STORAGE_S3_REGION: variables._APP_STORAGE_S3_REGION, + _APP_STORAGE_S3_BUCKET: variables._APP_STORAGE_S3_BUCKET, + _APP_STORAGE_DO_SPACES_ACCESS_KEY: variables._APP_STORAGE_DO_SPACES_ACCESS_KEY, + _APP_STORAGE_DO_SPACES_SECRET: variables._APP_STORAGE_DO_SPACES_SECRET, + _APP_STORAGE_DO_SPACES_REGION: variables._APP_STORAGE_DO_SPACES_REGION, + _APP_STORAGE_DO_SPACES_BUCKET: variables._APP_STORAGE_DO_SPACES_BUCKET, + _APP_FUNCTIONS_SIZE_LIMIT: variables._APP_FUNCTIONS_SIZE_LIMIT, + _APP_FUNCTIONS_TIMEOUT: variables._APP_FUNCTIONS_TIMEOUT, + _APP_FUNCTIONS_BUILD_TIMEOUT: variables._APP_FUNCTIONS_BUILD_TIMEOUT, + _APP_FUNCTIONS_CONTAINERS: variables._APP_FUNCTIONS_CONTAINERS, + _APP_FUNCTIONS_CPUS: variables._APP_FUNCTIONS_CPUS, + _APP_FUNCTIONS_MEMORY: variables._APP_FUNCTIONS_MEMORY, + _APP_FUNCTIONS_MEMORY_SWAP: variables._APP_FUNCTIONS_MEMORY_SWAP, + _APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET, + _APP_FUNCTIONS_RUNTIMES: variables._APP_FUNCTIONS_RUNTIMES, + _APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER, + _APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG, + _APP_STATSD_HOST: variables._APP_STATSD_HOST, + _APP_STATSD_PORT: variables._APP_STATSD_PORT, + _APP_MAINTENANCE_INTERVAL: variables._APP_MAINTENANCE_INTERVAL, + _APP_MAINTENANCE_RETENTION_EXECUTION: variables._APP_MAINTENANCE_RETENTION_EXECUTION, + _APP_MAINTENANCE_RETENTION_ABUSE: variables._APP_MAINTENANCE_RETENTION_ABUSE, + _APP_MAINTENANCE_RETENTION_AUDIT: variables._APP_MAINTENANCE_RETENTION_AUDIT + } + }, + appwriteRealtime: { + image: `${image}:${version}`, + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_WORKER_PER_CORE: variables._APP_WORKER_PER_CORE, + _APP_OPTIONS_ABUSE: variables._APP_OPTIONS_ABUSE, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_DB_HOST: variables._APP_DB_HOST, + _APP_DB_PORT: variables._APP_DB_PORT, + _APP_DB_SCHEMA: variables._APP_DB_SCHEMA, + _APP_DB_USER: variables._APP_DB_USER, + _APP_DB_PASS: variables._APP_DB_PASS, + _APP_USAGE_STATS: variables._APP_USAGE_STATS, + _APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER, + _APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG + } + }, + appwriteExecutor: { + image: `${image}:${version}`, + volumes: [ + `${id}-appwrite-functions:/storage/functions`, + `/tmp:/tmp`, + '/var/run/docker.sock:/var/run/docker.sock' + ], + environmentVariables: { + DOCKERHUB_PULL_USERNAME: variables.DOCKERHUB_PULL_USERNAME, + DOCKERHUB_PULL_PASSWORD: variables.DOCKERHUB_PULL_PASSWORD, + _APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER, + _APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG, + _APP_VERSION: variables._APP_VERSION, + _APP_ENV: variables._APP_ENV, + _APP_STORAGE_DEVICE: variables._APP_STORAGE_DEVICE, + _APP_STORAGE_S3_ACCESS_KEY: variables._APP_STORAGE_S3_ACCESS_KEY, + _APP_STORAGE_S3_SECRET: variables._APP_STORAGE_S3_SECRET, + _APP_STORAGE_S3_REGION: variables._APP_STORAGE_S3_REGION, + _APP_STORAGE_S3_BUCKET: variables._APP_STORAGE_S3_BUCKET, + _APP_STORAGE_DO_SPACES_ACCESS_KEY: variables._APP_STORAGE_DO_SPACES_ACCESS_KEY, + _APP_STORAGE_DO_SPACES_SECRET: variables._APP_STORAGE_DO_SPACES_SECRET, + _APP_STORAGE_DO_SPACES_REGION: variables._APP_STORAGE_DO_SPACES_REGION, + _APP_STORAGE_DO_SPACES_BUCKET: variables._APP_STORAGE_DO_SPACES_BUCKET, + _APP_FUNCTIONS_CPUS: variables._APP_FUNCTIONS_CPUS, + _APP_FUNCTIONS_MEMORY: variables._APP_FUNCTIONS_MEMORY, + _APP_FUNCTIONS_MEMORY_SWAP: variables._APP_FUNCTIONS_MEMORY_SWAP, + _APP_FUNCTIONS_TIMEOUT: variables._APP_FUNCTIONS_TIMEOUT, + _APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET, + _APP_FUNCTIONS_RUNTIMES: variables._APP_FUNCTIONS_RUNTIMES, + _APP_FUNCTIONS_INACTIVE_THRESHOLD: variables._APP_FUNCTIONS_INACTIVE_THRESHOLD, + _APP_EXECUTOR_RUNTIME_NETWORK: variables._APP_EXECUTOR_RUNTIME_NETWORK + } + }, + appwriteWorkerDatabase: { + image: `${image}:${version}`, + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS, + _APP_DB_HOST: variables._APP_DB_HOST, + _APP_DB_PORT: variables._APP_DB_PORT, + _APP_DB_SCHEMA: variables._APP_DB_SCHEMA, + _APP_DB_USER: variables._APP_DB_USER, + _APP_DB_PASS: variables._APP_DB_PASS, + _APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER, + _APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG + } + }, + appwriteWorkerBuilds: { + image: `${image}:${version}`, + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS, + _APP_DB_HOST: variables._APP_DB_HOST, + _APP_DB_PORT: variables._APP_DB_PORT, + _APP_DB_SCHEMA: variables._APP_DB_SCHEMA, + _APP_DB_USER: variables._APP_DB_USER, + _APP_DB_PASS: variables._APP_DB_PASS, + _APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER, + _APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG + } + }, + appwriteWorkerAudits: { + image: `${image}:${version}`, + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS, + _APP_DB_HOST: variables._APP_DB_HOST, + _APP_DB_PORT: variables._APP_DB_PORT, + _APP_DB_SCHEMA: variables._APP_DB_SCHEMA, + _APP_DB_USER: variables._APP_DB_USER, + _APP_DB_PASS: variables._APP_DB_PASS, + _APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER, + _APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG + } + }, + appwriteWorkerWebhooks: { + image: `${image}:${version}`, + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_SYSTEM_SECURITY_EMAIL_ADDRESS: variables._APP_SYSTEM_SECURITY_EMAIL_ADDRESS, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS, + _APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER, + _APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG + } + }, + appwriteWorkerDeletes: { + image: `${image}:${version}`, + volumes: [ + `${id}-appwrite-uploads:/storage/uploads`, + `${id}-appwrite-cache:/storage/cache`, + `${id}-appwrite-certificates:/storage/certificates` + ], + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS, + _APP_DB_HOST: variables._APP_DB_HOST, + _APP_DB_PORT: variables._APP_DB_PORT, + _APP_DB_SCHEMA: variables._APP_DB_SCHEMA, + _APP_DB_USER: variables._APP_DB_USER, + _APP_DB_PASS: variables._APP_DB_PASS, + _APP_STORAGE_DEVICE: variables._APP_STORAGE_DEVICE, + _APP_STORAGE_S3_ACCESS_KEY: variables._APP_STORAGE_S3_ACCESS_KEY, + _APP_STORAGE_S3_SECRET: variables._APP_STORAGE_S3_SECRET, + _APP_STORAGE_S3_REGION: variables._APP_STORAGE_S3_REGION, + _APP_STORAGE_S3_BUCKET: variables._APP_STORAGE_S3_BUCKET, + _APP_STORAGE_DO_SPACES_ACCESS_KEY: variables._APP_STORAGE_DO_SPACES_ACCESS_KEY, + _APP_STORAGE_DO_SPACES_SECRET: variables._APP_STORAGE_DO_SPACES_SECRET, + _APP_STORAGE_DO_SPACES_REGION: variables._APP_STORAGE_DO_SPACES_REGION, + _APP_STORAGE_DO_SPACES_BUCKET: variables._APP_STORAGE_DO_SPACES_BUCKET, + _APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER, + _APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG + } + }, + appwriteWorkerCertificates: { + image: `${image}:${version}`, + volumes: [ + `${id}-appwrite-config:/storage/config`, + `${id}-appwrite-certificates:/storage/certificates` + ], + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_SYSTEM_SECURITY_EMAIL_ADDRESS: variables._APP_SYSTEM_SECURITY_EMAIL_ADDRESS, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS, + _APP_DOMAIN_TARGET: variables._APP_DOMAIN_TARGET, + _APP_DB_HOST: variables._APP_DB_HOST, + _APP_DB_PORT: variables._APP_DB_PORT, + _APP_DB_SCHEMA: variables._APP_DB_SCHEMA, + _APP_DB_USER: variables._APP_DB_USER, + _APP_DB_PASS: variables._APP_DB_PASS, + _APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER, + _APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG + } + }, + appwriteWorkerFunctions: { + image: `${image}:${version}`, + envvironmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS, + _APP_DB_HOST: variables._APP_DB_HOST, + _APP_DB_PORT: variables._APP_DB_PORT, + _APP_DB_SCHEMA: variables._APP_DB_SCHEMA, + _APP_DB_USER: variables._APP_DB_USER, + _APP_DB_PASS: variables._APP_DB_PASS, + _APP_FUNCTIONS_TIMEOUT: variables._APP_FUNCTIONS_TIMEOUT, + _APP_EXECUTOR_SECRET: variables._APP_EXECUTOR_SECRET, + _APP_USAGE_STATS: variables._APP_USAGE_STATS, + DOCKERHUB_PULL_USERNAME: variables.DOCKERHUB_PULL_USERNAME, + DOCKERHUB_PULL_PASSWORD: variables.DOCKERHUB_PULL_PASSWORD + } + }, + appwriteWorkerMails: { + image: `${image}:${version}`, + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_SYSTEM_EMAIL_NAME: variables._APP_SYSTEM_EMAIL_NAME, + _APP_SYSTEM_EMAIL_ADDRESS: variables._APP_SYSTEM_EMAIL_ADDRESS, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS, + _APP_SMTP_HOST: variables._APP_SMTP_HOST, + _APP_SMTP_PORT: variables._APP_SMTP_PORT, + _APP_SMTP_SECURE: variables._APP_SMTP_SECURE, + _APP_SMTP_USERNAME: variables._APP_SMTP_USERNAME, + _APP_SMTP_PASSWORD: variables._APP_SMTP_PASSWORD, + _APP_LOGGING_PROVIDER: variables._APP_LOGGING_PROVIDER, + _APP_LOGGING_CONFIG: variables._APP_LOGGING_CONFIG + } + }, + appwriteMaintenance: { + image: `${image}:${version}`, + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS, + _APP_MAINTENANCE_INTERVAL: variables._APP_MAINTENANCE_INTERVAL, + _APP_MAINTENANCE_RETENTION_EXECUTION: variables._APP_MAINTENANCE_RETENTION_EXECUTION, + _APP_MAINTENANCE_RETENTION_ABUSE: variables._APP_MAINTENANCE_RETENTION_ABUSE, + _APP_MAINTENANCE_RETENTION_AUDIT: variables._APP_MAINTENANCE_RETENTION_AUDIT + } + }, + appwriteUsage: { + image: `${image}:${version}`, + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_OPENSSL_KEY_V1: variables._APP_OPENSSL_KEY_V1, + _APP_DB_HOST: variables._APP_DB_HOST, + _APP_DB_PORT: variables._APP_DB_PORT, + _APP_DB_SCHEMA: variables._APP_DB_SCHEMA, + _APP_DB_USER: variables._APP_DB_USER, + _APP_DB_PASS: variables._APP_DB_PASS, + _APP_INFLUXDB_HOST: variables._APP_INFLUXDB_HOST, + _APP_INFLUXDB_PORT: variables._APP_INFLUXDB_PORT, + _APP_USAGE_AGGREGATION_INTERVAL: variables._APP_USAGE_AGGREGATION_INTERVAL, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS + } + }, + appwriteSchedule: { + image: `${image}:${version}`, + environmentVariables: { + _APP_ENV: variables._APP_ENV, + _APP_REDIS_HOST: variables._APP_REDIS_HOST, + _APP_REDIS_PORT: variables._APP_REDIS_PORT, + _APP_REDIS_USER: variables._APP_REDIS_USER, + _APP_REDIS_PASS: variables._APP_REDIS_PASS + } + }, + mariadb: { + image: 'mariadb:10.7', + volumes: [`${id}-appwrite-mariadb:/var/lib/mysql`], + environmentVariables: { + MYSQL_ROOT_PASSWORD: variables._APP_DB_ROOT_PASS, + MYSQL_DATABASE: variables._APP_DB_SCHEMA, + MYSQL_USER: variables._APP_DB_USER, + MYSQL_PASSWORD: variables._APP_DB_PASS + } + }, + redis: { + image: 'redis:6.0-alpine3.12', + volumes: [`${id}-appwrite-redis:/data`] + }, + influxdb: { + image: 'appwrite/influxdb:1.0.0', + volumes: [`${id}-appwrite-influxdb:/var/lib/influxdb`] + }, + telegraf: { + image: 'appwrite/telegraf:1.0.0', + environmentVariables: { + _APP_INFLUXDB_HOST: variables._APP_INFLUXDB_HOST, + _APP_INFLUXDB_PORT: variables._APP_INFLUXDB_PORT + } + } + }; + + const composeFile: ComposeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image: config.image, + networks: [network], + volumes: [...config.appwrite.volumes], + environment: config.environmentVariables, + restart: 'always', + labels: makeLabelForServices('appwrite'), + ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), + deploy: { + restart_policy: { + condition: 'on-failure', + delay: '5s', + max_attempts: 3, + window: '120s' + } + } + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [config.volume.split(':')[0]]: { + name: config.volume.split(':')[0] + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + + try { + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + return { + status: 200 + }; + } catch (error) { + return ErrorHandler(error); + } + } catch (error) { + return ErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/appwrite/stop.json.ts b/src/routes/services/[id]/appwrite/stop.json.ts new file mode 100644 index 000000000..c604e1cc3 --- /dev/null +++ b/src/routes/services/[id]/appwrite/stop.json.ts @@ -0,0 +1,35 @@ +import { getUserDetails, removeDestinationDocker } from '$lib/common'; +import * as db from '$lib/database'; +import { ErrorHandler } from '$lib/database'; +import { checkContainer } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + try { + const service = await db.getService({ id, teamId }); + const { destinationDockerId, destinationDocker, fqdn } = service; + if (destinationDockerId) { + const engine = destinationDocker.engine; + + try { + const found = await checkContainer(engine, id); + if (found) { + await removeDestinationDocker({ id, engine }); + } + } catch (error) { + console.error(error); + } + } + + return { + status: 200 + }; + } catch (error) { + return ErrorHandler(error); + } +}; From 16b7c1708bcfc5c780cb06b0de0c90f46ecd72de Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 May 2022 16:15:34 +0200 Subject: [PATCH 05/44] WIP: Testing WS --- package.json | 6 +- pnpm-lock.yaml | 732 ++++++++++++++++++++++++++++++++++++- server/index.js | 20 + src/lib/common.ts | 16 +- src/lib/realtime.ts | 3 + src/routes/__layout.svelte | 6 + src/routes/index.svelte | 1 - svelte.config.js | 12 + 8 files changed, 783 insertions(+), 13 deletions(-) create mode 100644 server/index.js create mode 100644 src/lib/realtime.ts diff --git a/package.json b/package.json index 0c6bde272..c36a7b4be 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "dev:stop": "docker-compose -f docker-compose-dev.yaml down", "dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10", "studio": "npx prisma studio", - "start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node build/index.js", + "start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node server/index.js", "build": "svelte-kit build", "preview": "svelte-kit preview", "check": "svelte-check --tsconfig ./tsconfig.json", @@ -66,6 +66,7 @@ "@iarna/toml": "2.2.5", "@prisma/client": "3.11.1", "@sentry/node": "6.19.7", + "@types/express": "4.17.13", "bcryptjs": "2.4.3", "bullmq": "1.81.4", "compare-versions": "4.1.3", @@ -74,6 +75,7 @@ "dayjs": "1.11.2", "dockerode": "3.3.1", "dotenv-extended": "2.9.0", + "express": "^4.18.1", "generate-password": "1.7.0", "get-port": "6.1.2", "got": "12.0.4", @@ -85,6 +87,8 @@ "node-forge": "1.3.1", "node-os-utils": "1.3.6", "p-limit": "4.0.0", + "socket.io": "4.5.0", + "socket.io-client": "4.5.0", "svelte-kit-cookie-session": "2.1.4", "tailwindcss-scrollbar": "0.1.0", "unique-names-generator": "4.7.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e98d0f2e8..9d2505928 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,7 @@ specifiers: '@sentry/node': 6.19.7 '@sveltejs/adapter-node': 1.0.0-next.73 '@sveltejs/kit': 1.0.0-next.326 + '@types/express': ^4.17.13 '@types/js-cookie': 3.0.2 '@types/js-yaml': 4.0.5 '@types/node': 17.0.31 @@ -27,6 +28,7 @@ specifiers: eslint: 7.32.0 eslint-config-prettier: 8.5.0 eslint-plugin-svelte3: 3.4.1 + express: ^4.18.1 generate-password: 1.7.0 get-port: 6.1.2 got: 12.0.4 @@ -45,6 +47,8 @@ specifiers: prettier-plugin-svelte: 2.7.0 prettier-plugin-tailwindcss: 0.1.10 prisma: 3.11.1 + socket.io: ^4.5.0 + socket.io-client: ^4.5.0 svelte: 3.48.0 svelte-check: 2.7.0 svelte-kit-cookie-session: 2.1.4 @@ -62,6 +66,7 @@ dependencies: '@iarna/toml': 2.2.5 '@prisma/client': 3.11.1_prisma@3.11.1 '@sentry/node': 6.19.7 + '@types/express': 4.17.13 bcryptjs: 2.4.3 bullmq: 1.81.4 compare-versions: 4.1.3 @@ -70,6 +75,7 @@ dependencies: dayjs: 1.11.2 dockerode: 3.3.1 dotenv-extended: 2.9.0 + express: 4.18.1 generate-password: 1.7.0 get-port: 6.1.2 got: 12.0.4 @@ -81,6 +87,8 @@ dependencies: node-forge: 1.3.1 node-os-utils: 1.3.6 p-limit: 4.0.0 + socket.io: 4.5.0 + socket.io-client: 4.5.0 svelte-kit-cookie-session: 2.1.4 tailwindcss-scrollbar: 0.1.0_tailwindcss@3.0.24 unique-names-generator: 4.7.1 @@ -371,6 +379,13 @@ packages: engines: { node: '>=10' } dev: false + /@socket.io/component-emitter/3.1.0: + resolution: + { + integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + } + dev: false + /@sveltejs/adapter-node/1.0.0-next.73: resolution: { @@ -487,6 +502,16 @@ packages: } dev: true + /@types/body-parser/1.19.2: + resolution: + { + integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + } + dependencies: + '@types/connect': 3.4.35 + '@types/node': 17.0.31 + dev: false + /@types/cacheable-request/6.0.2: resolution: { @@ -499,6 +524,59 @@ packages: '@types/responselike': 1.0.0 dev: false + /@types/component-emitter/1.2.11: + resolution: + { + integrity: sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ== + } + dev: false + + /@types/connect/3.4.35: + resolution: + { + integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + } + dependencies: + '@types/node': 17.0.31 + dev: false + + /@types/cookie/0.4.1: + resolution: + { + integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + } + dev: false + + /@types/cors/2.8.12: + resolution: + { + integrity: sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== + } + dev: false + + /@types/express-serve-static-core/4.17.28: + resolution: + { + integrity: sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + } + dependencies: + '@types/node': 17.0.31 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 + dev: false + + /@types/express/4.17.13: + resolution: + { + integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + } + dependencies: + '@types/body-parser': 1.19.2 + '@types/express-serve-static-core': 4.17.28 + '@types/qs': 6.9.7 + '@types/serve-static': 1.13.10 + dev: false + /@types/http-cache-semantics/4.0.1: resolution: { @@ -536,6 +614,13 @@ packages: '@types/node': 17.0.31 dev: false + /@types/mime/1.3.2: + resolution: + { + integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + } + dev: false + /@types/node-forge/1.0.2: resolution: { @@ -558,6 +643,20 @@ packages: } dev: true + /@types/qs/6.9.7: + resolution: + { + integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + } + dev: false + + /@types/range-parser/1.2.4: + resolution: + { + integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + } + dev: false + /@types/responselike/1.0.0: resolution: { @@ -576,6 +675,16 @@ packages: '@types/node': 17.0.31 dev: true + /@types/serve-static/1.13.10: + resolution: + { + integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + } + dependencies: + '@types/mime': 1.3.2 + '@types/node': 17.0.31 + dev: false + /@typescript-eslint/eslint-plugin/4.31.1_lii63oz3usekbu5ehvrcuwn5jy: resolution: { @@ -709,6 +818,17 @@ packages: } dev: true + /accepts/1.3.8: + resolution: + { + integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + } + engines: { node: '>= 0.6' } + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + /acorn-jsx/5.3.2_acorn@7.4.1: resolution: { @@ -925,6 +1045,10 @@ packages: } dev: false + /array-flatten/1.1.1: + resolution: { integrity: sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= } + dev: false + /array-union/2.1.0: resolution: { @@ -1608,6 +1732,14 @@ packages: } dev: false + /base64id/2.0.0: + resolution: + { + integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + } + engines: { node: ^4.5.0 || >= 5.9 } + dev: false + /bcrypt-pbkdf/1.0.2: resolution: { integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= } dependencies: @@ -1637,6 +1769,27 @@ packages: readable-stream: 3.6.0 dev: false + /body-parser/1.20.0: + resolution: + { + integrity: sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== + } + engines: { node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16 } + dependencies: + bytes: 3.1.2 + content-type: 1.0.4 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.10.3 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + dev: false + /brace-expansion/1.1.11: resolution: { @@ -1708,6 +1861,14 @@ packages: - supports-color dev: false + /bytes/3.1.2: + resolution: + { + integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + } + engines: { node: '>= 0.8' } + dev: false + /cacheable-lookup/6.0.4: resolution: { @@ -1732,6 +1893,16 @@ packages: responselike: 2.0.0 dev: false + /call-bind/1.0.2: + resolution: + { + integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + } + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.1.1 + dev: false + /callsites/3.1.0: resolution: { @@ -1928,9 +2099,34 @@ packages: } dev: false + /component-emitter/1.3.0: + resolution: + { + integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + } + dev: false + /concat-map/0.0.1: resolution: { integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= } + /content-disposition/0.5.4: + resolution: + { + integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + } + engines: { node: '>= 0.6' } + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type/1.0.4: + resolution: + { + integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + } + engines: { node: '>= 0.6' } + dev: false + /convert-source-map/1.8.0: resolution: { @@ -1940,6 +2136,10 @@ packages: safe-buffer: 5.1.2 dev: true + /cookie-signature/1.0.6: + resolution: { integrity: sha1-4wOogrNCzD7oylE6eZmXNNqzriw= } + dev: false + /cookie/0.4.2: resolution: { @@ -1965,6 +2165,17 @@ packages: requiresBuild: true dev: true + /cors/2.8.5: + resolution: + { + integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + } + engines: { node: '>= 0.10' } + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + dev: false + /cpu-features/0.0.2: resolution: { @@ -2065,7 +2276,6 @@ packages: } dependencies: ms: 2.0.0 - dev: true /debug/4.3.3: resolution: @@ -2134,6 +2344,22 @@ packages: engines: { node: '>=0.10' } dev: false + /depd/2.0.0: + resolution: + { + integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + } + engines: { node: '>= 0.8' } + dev: false + + /destroy/1.2.0: + resolution: + { + integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + } + engines: { node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16 } + dev: false + /detect-indent/4.0.0: resolution: { integrity: sha1-920GQ1LN9Docts5hnE7jqUdd4gg= } engines: { node: '>=0.10.0' } @@ -2270,6 +2496,10 @@ packages: safe-buffer: 5.2.1 dev: false + /ee-first/1.1.1: + resolution: { integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= } + dev: false + /electron-to-chromium/1.4.137: resolution: { @@ -2291,6 +2521,11 @@ packages: } dev: true + /encodeurl/1.0.2: + resolution: { integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= } + engines: { node: '>= 0.8' } + dev: false + /end-of-stream/1.4.4: resolution: { @@ -2300,6 +2535,54 @@ packages: once: 1.4.0 dev: false + /engine.io-client/6.2.2: + resolution: + { + integrity: sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ== + } + dependencies: + '@socket.io/component-emitter': 3.1.0 + debug: 4.3.3 + engine.io-parser: 5.0.4 + ws: 8.2.3 + xmlhttprequest-ssl: 2.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + + /engine.io-parser/5.0.4: + resolution: + { + integrity: sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg== + } + engines: { node: '>=10.0.0' } + dev: false + + /engine.io/6.2.0: + resolution: + { + integrity: sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg== + } + engines: { node: '>=10.0.0' } + dependencies: + '@types/cookie': 0.4.1 + '@types/cors': 2.8.12 + '@types/node': 17.0.31 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.4.2 + cors: 2.8.5 + debug: 4.3.3 + engine.io-parser: 5.0.4 + ws: 8.2.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + /enquirer/2.3.6: resolution: { @@ -2593,6 +2876,10 @@ packages: engines: { node: '>=6' } dev: true + /escape-html/1.0.3: + resolution: { integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= } + dev: false + /escape-string-regexp/1.0.5: resolution: { integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= } engines: { node: '>=0.8.0' } @@ -2806,6 +3093,11 @@ packages: engines: { node: '>=0.10.0' } dev: true + /etag/1.8.1: + resolution: { integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= } + engines: { node: '>= 0.6' } + dev: false + /execa/5.1.1: resolution: { @@ -2829,6 +3121,46 @@ packages: engines: { node: '>= 0.8.0' } dev: true + /express/4.18.1: + resolution: + { + integrity: sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== + } + engines: { node: '>= 0.10.0' } + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.0 + content-disposition: 0.5.4 + content-type: 1.0.4 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.10.3 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + dev: false + /fast-deep-equal/3.1.3: resolution: { @@ -2890,6 +3222,22 @@ packages: to-regex-range: 5.0.1 dev: true + /finalhandler/1.2.0: + resolution: + { + integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + } + engines: { node: '>= 0.8' } + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + dev: false + /flat-cache/3.0.4: resolution: { @@ -2915,6 +3263,14 @@ packages: } dev: false + /forwarded/0.2.0: + resolution: + { + integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + } + engines: { node: '>= 0.6' } + dev: false + /fraction.js/4.2.0: resolution: { @@ -2922,6 +3278,11 @@ packages: } dev: true + /fresh/0.5.2: + resolution: { integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= } + engines: { node: '>= 0.6' } + dev: false + /fs-constants/1.0.0: resolution: { @@ -2948,7 +3309,6 @@ packages: { integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== } - dev: true /function.name/1.0.13: resolution: @@ -2970,6 +3330,17 @@ packages: } dev: false + /get-intrinsic/1.1.1: + resolution: + { + integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + } + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + dev: false + /get-port/5.1.1: resolution: { @@ -3132,6 +3503,14 @@ packages: engines: { node: '>=8' } dev: true + /has-symbols/1.0.3: + resolution: + { + integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + } + engines: { node: '>= 0.4' } + dev: false + /has/1.0.3: resolution: { @@ -3140,7 +3519,6 @@ packages: engines: { node: '>= 0.4.0' } dependencies: function-bind: 1.1.1 - dev: true /home-or-tmp/2.0.0: resolution: { integrity: sha1-42w/LSyufXRqhX440Y1fMqeILbg= } @@ -3157,6 +3535,20 @@ packages: } dev: false + /http-errors/2.0.0: + resolution: + { + integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + } + engines: { node: '>= 0.8' } + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + /http2-wrapper/2.1.10: resolution: { @@ -3198,6 +3590,16 @@ packages: hasBin: true dev: true + /iconv-lite/0.4.24: + resolution: + { + integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + } + engines: { node: '>=0.10.0' } + dependencies: + safer-buffer: 2.1.2 + dev: false + /ieee754/1.2.1: resolution: { @@ -3296,6 +3698,14 @@ packages: engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } dev: false + /ipaddr.js/1.9.1: + resolution: + { + integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + } + engines: { node: '>= 0.10' } + dev: false + /is-binary-path/2.1.0: resolution: { @@ -3742,6 +4152,15 @@ packages: } dev: true + /media-typer/0.3.0: + resolution: { integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= } + engines: { node: '>= 0.6' } + dev: false + + /merge-descriptors/1.0.1: + resolution: { integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= } + dev: false + /merge-stream/2.0.0: resolution: { @@ -3757,6 +4176,11 @@ packages: engines: { node: '>= 8' } dev: true + /methods/1.1.2: + resolution: { integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= } + engines: { node: '>= 0.6' } + dev: false + /micromatch/4.0.4: resolution: { @@ -3768,6 +4192,33 @@ packages: picomatch: 2.3.0 dev: true + /mime-db/1.52.0: + resolution: + { + integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + } + engines: { node: '>= 0.6' } + dev: false + + /mime-types/2.1.35: + resolution: + { + integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + } + engines: { node: '>= 0.6' } + dependencies: + mime-db: 1.52.0 + dev: false + + /mime/1.6.0: + resolution: + { + integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + } + engines: { node: '>=4' } + hasBin: true + dev: false + /mimic-fn/2.1.0: resolution: { @@ -3842,7 +4293,6 @@ packages: /ms/2.0.0: resolution: { integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= } - dev: true /ms/2.1.2: resolution: @@ -3850,6 +4300,13 @@ packages: integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== } + /ms/2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + } + dev: false + /msgpackr-extract/1.0.15: resolution: { @@ -3900,6 +4357,14 @@ packages: resolution: { integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= } dev: true + /negotiator/0.6.3: + resolution: + { + integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + } + engines: { node: '>= 0.6' } + dev: false + /node-forge/1.3.1: resolution: { @@ -3969,6 +4434,11 @@ packages: path-key: 3.1.1 dev: true + /object-assign/4.1.1: + resolution: { integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= } + engines: { node: '>=0.10.0' } + dev: false + /object-hash/3.0.0: resolution: { @@ -3982,7 +4452,16 @@ packages: { integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== } - dev: true + + /on-finished/2.4.1: + resolution: + { + integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + } + engines: { node: '>= 0.8' } + dependencies: + ee-first: 1.1.1 + dev: false /once/1.4.0: resolution: { integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E= } @@ -4070,6 +4549,14 @@ packages: callsites: 3.1.0 dev: true + /parseurl/1.3.3: + resolution: + { + integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + } + engines: { node: '>= 0.8' } + dev: false + /path-is-absolute/1.0.1: resolution: { integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18= } engines: { node: '>=0.10.0' } @@ -4088,6 +4575,10 @@ packages: } dev: true + /path-to-regexp/0.1.7: + resolution: { integrity: sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= } + dev: false + /path-type/4.0.0: resolution: { @@ -4267,6 +4758,17 @@ packages: engines: { node: '>=0.4.0' } dev: true + /proxy-addr/2.0.7: + resolution: + { + integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + } + engines: { node: '>= 0.10' } + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: false + /pseudomap/1.0.2: resolution: { integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM= } dev: true @@ -4289,6 +4791,16 @@ packages: engines: { node: '>=6' } dev: true + /qs/6.10.3: + resolution: + { + integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== + } + engines: { node: '>=0.6' } + dependencies: + side-channel: 1.0.4 + dev: false + /queue-microtask/1.2.3: resolution: { @@ -4303,6 +4815,27 @@ packages: } engines: { node: '>=10' } + /range-parser/1.2.1: + resolution: + { + integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + } + engines: { node: '>= 0.6' } + dev: false + + /raw-body/2.5.1: + resolution: + { + integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + } + engines: { node: '>= 0.8' } + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + /readable-stream/3.6.0: resolution: { @@ -4593,6 +5126,48 @@ packages: lru-cache: 6.0.0 dev: true + /send/0.18.0: + resolution: + { + integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + } + engines: { node: '>= 0.8.0' } + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + dev: false + + /serve-static/1.15.0: + resolution: + { + integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + } + engines: { node: '>= 0.8.0' } + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + dev: false + + /setprototypeof/1.2.0: + resolution: + { + integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + } + dev: false + /shebang-command/1.2.0: resolution: { integrity: sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= } engines: { node: '>=0.10.0' } @@ -4621,6 +5196,17 @@ packages: } engines: { node: '>=8' } + /side-channel/1.0.4: + resolution: + { + integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + } + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.1 + object-inspect: 1.12.0 + dev: false + /signal-exit/3.0.5: resolution: { @@ -4676,6 +5262,76 @@ packages: is-fullwidth-code-point: 4.0.0 dev: true + /socket.io-adapter/2.4.0: + resolution: + { + integrity: sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg== + } + dev: false + + /socket.io-client/4.5.0: + resolution: + { + integrity: sha512-HW61c1G7OrYGxaI79WRn17+b03iBCdvhBj4iqyXHBoL5M8w2MSO/vChsjA93knG4GYEai1/vbXWJna9dzxXtSg== + } + engines: { node: '>=10.0.0' } + dependencies: + '@socket.io/component-emitter': 3.1.0 + debug: 4.3.3 + engine.io-client: 6.2.2 + socket.io-parser: 4.2.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + + /socket.io-parser/4.0.4: + resolution: + { + integrity: sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g== + } + engines: { node: '>=10.0.0' } + dependencies: + '@types/component-emitter': 1.2.11 + component-emitter: 1.3.0 + debug: 4.3.3 + transitivePeerDependencies: + - supports-color + dev: false + + /socket.io-parser/4.2.0: + resolution: + { + integrity: sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng== + } + engines: { node: '>=10.0.0' } + dependencies: + '@socket.io/component-emitter': 3.1.0 + debug: 4.3.3 + transitivePeerDependencies: + - supports-color + dev: false + + /socket.io/4.5.0: + resolution: + { + integrity: sha512-slTYqU2jCgMjXwresG8grhUi/cC6GjzmcfqArzaH3BN/9I/42eZk9yamNvZJdBfTubkjEdKAKs12NEztId+bUA== + } + engines: { node: '>=10.0.0' } + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + debug: 4.3.3 + engine.io: 6.2.0 + socket.io-adapter: 2.4.0 + socket.io-parser: 4.0.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + /sorcery/0.10.0: resolution: { integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc= } hasBin: true @@ -4753,6 +5409,14 @@ packages: } dev: false + /statuses/2.0.1: + resolution: + { + integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + } + engines: { node: '>= 0.8' } + dev: false + /string-argv/0.3.1: resolution: { @@ -5138,6 +5802,14 @@ packages: is-number: 7.0.0 dev: true + /toidentifier/1.0.1: + resolution: + { + integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + } + engines: { node: '>=0.6' } + dev: false + /trim-right/1.0.1: resolution: { integrity: sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= } engines: { node: '>=0.10.0' } @@ -5233,6 +5905,17 @@ packages: engines: { node: '>=10' } dev: true + /type-is/1.6.18: + resolution: + { + integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + } + engines: { node: '>= 0.6' } + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + /typescript/4.6.4: resolution: { @@ -5259,6 +5942,11 @@ packages: engines: { node: '>=8' } dev: false + /unpipe/1.0.0: + resolution: { integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= } + engines: { node: '>= 0.8' } + dev: false + /uri-js/4.4.1: resolution: { @@ -5271,6 +5959,11 @@ packages: /util-deprecate/1.0.2: resolution: { integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= } + /utils-merge/1.0.1: + resolution: { integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= } + engines: { node: '>= 0.4.0' } + dev: false + /uuid/8.3.2: resolution: { @@ -5293,6 +5986,11 @@ packages: } dev: true + /vary/1.1.2: + resolution: { integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= } + engines: { node: '>= 0.8' } + dev: false + /vite/2.9.1: resolution: { @@ -5375,6 +6073,30 @@ packages: /wrappy/1.0.2: resolution: { integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= } + /ws/8.2.3: + resolution: + { + integrity: sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== + } + 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 + + /xmlhttprequest-ssl/2.0.0: + resolution: + { + integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + } + engines: { node: '>=0.4.0' } + dev: false + /xtend/4.0.2: resolution: { diff --git a/server/index.js b/server/index.js new file mode 100644 index 000000000..154a8899d --- /dev/null +++ b/server/index.js @@ -0,0 +1,20 @@ +import express from 'express'; +import { createServer } from 'http'; +import { Server } from 'socket.io'; + +import { handler } from '../build/handler.js'; + +const app = express(); +const server = createServer(app); + +const io = new Server(server); + +io.on('connection', (socket) => { + socket.emit('eventFromServer', 'Hello, World 👋'); +}); + +app.use(handler); + +server.listen(port, () => { + console.log(`Listening on port ${port}`); +}); diff --git a/src/lib/common.ts b/src/lib/common.ts index 61009d42f..cae9350ce 100644 --- a/src/lib/common.ts +++ b/src/lib/common.ts @@ -96,12 +96,16 @@ export const getUserDetails = async ( const userId = event?.locals?.session?.data?.userId || null; let permission = 'read'; if (teamId && userId) { - const data = await db.prisma.permission.findFirst({ - where: { teamId, userId }, - select: { permission: true }, - rejectOnNotFound: true - }); - if (data.permission) permission = data.permission; + try { + const data = await db.prisma.permission.findFirst({ + where: { teamId, userId }, + select: { permission: true }, + rejectOnNotFound: true + }); + if (data.permission) permission = data.permission; + } catch (error) { + console.log(error); + } } const payload = { diff --git a/src/lib/realtime.ts b/src/lib/realtime.ts new file mode 100644 index 000000000..9fda419b9 --- /dev/null +++ b/src/lib/realtime.ts @@ -0,0 +1,3 @@ +import ioClient from 'socket.io-client'; +const socket = ioClient('http://localhost:3000'); +export const io = socket; diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index 6ead964da..bbc06ea31 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -43,6 +43,7 @@ import { del, get, post } from '$lib/api'; import { dev } from '$app/env'; import { features } from '$lib/store'; + import { io } from '$lib/realtime'; let isUpdateAvailable = false; let updateStatus = { @@ -51,6 +52,11 @@ success: null }; let latestVersion = 'latest'; + + io.on('eventFromServer', (message) => { + console.log(message); + }); + onMount(async () => { if ($session.userId) { const overrideVersion = $features.latestVersion; diff --git a/src/routes/index.svelte b/src/routes/index.svelte index bd441afdb..858a0387b 100644 --- a/src/routes/index.svelte +++ b/src/routes/index.svelte @@ -23,7 +23,6 @@ import { t } from '$lib/translations'; import { get } from '$lib/api'; import { onDestroy, onMount } from 'svelte'; - import Loading from './applications/[id]/logs/_Loading.svelte'; import Trend from './_Trend.svelte'; import { session } from '$app/stores'; diff --git a/svelte.config.js b/svelte.config.js index 352c3e330..329abfeee 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,6 +1,17 @@ import preprocess from 'svelte-preprocess'; import adapter from '@sveltejs/adapter-node'; +import { Server } from 'socket.io'; +const webSocketServer = { + name: 'webSocketServer', + configureServer(server) { + const io = new Server(server.httpServer); + io.on('connection', (socket) => { + socket.emit('eventFromServer', 'Hello, World 👋'); + }); + } +}; + const config = { preprocess: preprocess(), kit: { @@ -10,6 +21,7 @@ const config = { }, floc: true, vite: { + plugins: [webSocketServer], optimizeDeps: { exclude: ['svelte-kit-cookie-session'] }, From 60a428a952f44fe7f0d881dc68c112d0abe67f77 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 11 May 2022 23:21:45 +0200 Subject: [PATCH 06/44] Remove WS for now --- package.json | 7 +- pnpm-lock.yaml | 743 +------------------------------------ server/index.js | 20 - src/lib/realtime.ts | 6 +- src/routes/__layout.svelte | 5 - svelte.config.js | 19 +- 6 files changed, 27 insertions(+), 773 deletions(-) delete mode 100644 server/index.js diff --git a/package.json b/package.json index c36a7b4be..8648f9c47 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "dev:stop": "docker-compose -f docker-compose-dev.yaml down", "dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10", "studio": "npx prisma studio", - "start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node server/index.js", + "start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js", "build": "svelte-kit build", "preview": "svelte-kit preview", "check": "svelte-check --tsconfig ./tsconfig.json", @@ -30,6 +30,7 @@ }, "devDependencies": { "@sveltejs/adapter-node": "1.0.0-next.73", + "@sveltejs/adapter-static": "1.0.0-next.29", "@sveltejs/kit": "1.0.0-next.326", "@types/js-cookie": "3.0.2", "@types/js-yaml": "4.0.5", @@ -66,7 +67,6 @@ "@iarna/toml": "2.2.5", "@prisma/client": "3.11.1", "@sentry/node": "6.19.7", - "@types/express": "4.17.13", "bcryptjs": "2.4.3", "bullmq": "1.81.4", "compare-versions": "4.1.3", @@ -75,7 +75,6 @@ "dayjs": "1.11.2", "dockerode": "3.3.1", "dotenv-extended": "2.9.0", - "express": "^4.18.1", "generate-password": "1.7.0", "get-port": "6.1.2", "got": "12.0.4", @@ -87,8 +86,6 @@ "node-forge": "1.3.1", "node-os-utils": "1.3.6", "p-limit": "4.0.0", - "socket.io": "4.5.0", - "socket.io-client": "4.5.0", "svelte-kit-cookie-session": "2.1.4", "tailwindcss-scrollbar": "0.1.0", "unique-names-generator": "4.7.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d2505928..28b0ea786 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,8 +5,8 @@ specifiers: '@prisma/client': 3.11.1 '@sentry/node': 6.19.7 '@sveltejs/adapter-node': 1.0.0-next.73 + '@sveltejs/adapter-static': 1.0.0-next.29 '@sveltejs/kit': 1.0.0-next.326 - '@types/express': ^4.17.13 '@types/js-cookie': 3.0.2 '@types/js-yaml': 4.0.5 '@types/node': 17.0.31 @@ -28,7 +28,6 @@ specifiers: eslint: 7.32.0 eslint-config-prettier: 8.5.0 eslint-plugin-svelte3: 3.4.1 - express: ^4.18.1 generate-password: 1.7.0 get-port: 6.1.2 got: 12.0.4 @@ -47,8 +46,6 @@ specifiers: prettier-plugin-svelte: 2.7.0 prettier-plugin-tailwindcss: 0.1.10 prisma: 3.11.1 - socket.io: ^4.5.0 - socket.io-client: ^4.5.0 svelte: 3.48.0 svelte-check: 2.7.0 svelte-kit-cookie-session: 2.1.4 @@ -66,7 +63,6 @@ dependencies: '@iarna/toml': 2.2.5 '@prisma/client': 3.11.1_prisma@3.11.1 '@sentry/node': 6.19.7 - '@types/express': 4.17.13 bcryptjs: 2.4.3 bullmq: 1.81.4 compare-versions: 4.1.3 @@ -75,7 +71,6 @@ dependencies: dayjs: 1.11.2 dockerode: 3.3.1 dotenv-extended: 2.9.0 - express: 4.18.1 generate-password: 1.7.0 get-port: 6.1.2 got: 12.0.4 @@ -87,14 +82,13 @@ dependencies: node-forge: 1.3.1 node-os-utils: 1.3.6 p-limit: 4.0.0 - socket.io: 4.5.0 - socket.io-client: 4.5.0 svelte-kit-cookie-session: 2.1.4 tailwindcss-scrollbar: 0.1.0_tailwindcss@3.0.24 unique-names-generator: 4.7.1 devDependencies: '@sveltejs/adapter-node': 1.0.0-next.73 + '@sveltejs/adapter-static': 1.0.0-next.29 '@sveltejs/kit': 1.0.0-next.326_svelte@3.48.0 '@types/js-cookie': 3.0.2 '@types/js-yaml': 4.0.5 @@ -379,13 +373,6 @@ packages: engines: { node: '>=10' } dev: false - /@socket.io/component-emitter/3.1.0: - resolution: - { - integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== - } - dev: false - /@sveltejs/adapter-node/1.0.0-next.73: resolution: { @@ -395,6 +382,15 @@ packages: tiny-glob: 0.2.9 dev: true + /@sveltejs/adapter-static/1.0.0-next.29: + resolution: + { + integrity: sha512-0hjGnfT3BRyoHnzJ2w0/xL+xICRpKneDTm45ZzggiRrc0r71WJfF6toGeg8N4QUQnj8EJ3Itm453gsS1kt7VUQ== + } + dependencies: + tiny-glob: 0.2.9 + dev: true + /@sveltejs/kit/1.0.0-next.326_svelte@3.48.0: resolution: { @@ -502,16 +498,6 @@ packages: } dev: true - /@types/body-parser/1.19.2: - resolution: - { - integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - } - dependencies: - '@types/connect': 3.4.35 - '@types/node': 17.0.31 - dev: false - /@types/cacheable-request/6.0.2: resolution: { @@ -524,59 +510,6 @@ packages: '@types/responselike': 1.0.0 dev: false - /@types/component-emitter/1.2.11: - resolution: - { - integrity: sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ== - } - dev: false - - /@types/connect/3.4.35: - resolution: - { - integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - } - dependencies: - '@types/node': 17.0.31 - dev: false - - /@types/cookie/0.4.1: - resolution: - { - integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== - } - dev: false - - /@types/cors/2.8.12: - resolution: - { - integrity: sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== - } - dev: false - - /@types/express-serve-static-core/4.17.28: - resolution: - { - integrity: sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== - } - dependencies: - '@types/node': 17.0.31 - '@types/qs': 6.9.7 - '@types/range-parser': 1.2.4 - dev: false - - /@types/express/4.17.13: - resolution: - { - integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== - } - dependencies: - '@types/body-parser': 1.19.2 - '@types/express-serve-static-core': 4.17.28 - '@types/qs': 6.9.7 - '@types/serve-static': 1.13.10 - dev: false - /@types/http-cache-semantics/4.0.1: resolution: { @@ -614,13 +547,6 @@ packages: '@types/node': 17.0.31 dev: false - /@types/mime/1.3.2: - resolution: - { - integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== - } - dev: false - /@types/node-forge/1.0.2: resolution: { @@ -643,20 +569,6 @@ packages: } dev: true - /@types/qs/6.9.7: - resolution: - { - integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - } - dev: false - - /@types/range-parser/1.2.4: - resolution: - { - integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - } - dev: false - /@types/responselike/1.0.0: resolution: { @@ -675,16 +587,6 @@ packages: '@types/node': 17.0.31 dev: true - /@types/serve-static/1.13.10: - resolution: - { - integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== - } - dependencies: - '@types/mime': 1.3.2 - '@types/node': 17.0.31 - dev: false - /@typescript-eslint/eslint-plugin/4.31.1_lii63oz3usekbu5ehvrcuwn5jy: resolution: { @@ -818,17 +720,6 @@ packages: } dev: true - /accepts/1.3.8: - resolution: - { - integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - } - engines: { node: '>= 0.6' } - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - dev: false - /acorn-jsx/5.3.2_acorn@7.4.1: resolution: { @@ -1045,10 +936,6 @@ packages: } dev: false - /array-flatten/1.1.1: - resolution: { integrity: sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= } - dev: false - /array-union/2.1.0: resolution: { @@ -1732,14 +1619,6 @@ packages: } dev: false - /base64id/2.0.0: - resolution: - { - integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== - } - engines: { node: ^4.5.0 || >= 5.9 } - dev: false - /bcrypt-pbkdf/1.0.2: resolution: { integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= } dependencies: @@ -1769,27 +1648,6 @@ packages: readable-stream: 3.6.0 dev: false - /body-parser/1.20.0: - resolution: - { - integrity: sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== - } - engines: { node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16 } - dependencies: - bytes: 3.1.2 - content-type: 1.0.4 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.10.3 - raw-body: 2.5.1 - type-is: 1.6.18 - unpipe: 1.0.0 - dev: false - /brace-expansion/1.1.11: resolution: { @@ -1861,14 +1719,6 @@ packages: - supports-color dev: false - /bytes/3.1.2: - resolution: - { - integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - } - engines: { node: '>= 0.8' } - dev: false - /cacheable-lookup/6.0.4: resolution: { @@ -1893,16 +1743,6 @@ packages: responselike: 2.0.0 dev: false - /call-bind/1.0.2: - resolution: - { - integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - } - dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.1.1 - dev: false - /callsites/3.1.0: resolution: { @@ -2099,34 +1939,9 @@ packages: } dev: false - /component-emitter/1.3.0: - resolution: - { - integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - } - dev: false - /concat-map/0.0.1: resolution: { integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= } - /content-disposition/0.5.4: - resolution: - { - integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - } - engines: { node: '>= 0.6' } - dependencies: - safe-buffer: 5.2.1 - dev: false - - /content-type/1.0.4: - resolution: - { - integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - } - engines: { node: '>= 0.6' } - dev: false - /convert-source-map/1.8.0: resolution: { @@ -2136,10 +1951,6 @@ packages: safe-buffer: 5.1.2 dev: true - /cookie-signature/1.0.6: - resolution: { integrity: sha1-4wOogrNCzD7oylE6eZmXNNqzriw= } - dev: false - /cookie/0.4.2: resolution: { @@ -2165,17 +1976,6 @@ packages: requiresBuild: true dev: true - /cors/2.8.5: - resolution: - { - integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - } - engines: { node: '>= 0.10' } - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - dev: false - /cpu-features/0.0.2: resolution: { @@ -2276,6 +2076,7 @@ packages: } dependencies: ms: 2.0.0 + dev: true /debug/4.3.3: resolution: @@ -2344,22 +2145,6 @@ packages: engines: { node: '>=0.10' } dev: false - /depd/2.0.0: - resolution: - { - integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - } - engines: { node: '>= 0.8' } - dev: false - - /destroy/1.2.0: - resolution: - { - integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - } - engines: { node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16 } - dev: false - /detect-indent/4.0.0: resolution: { integrity: sha1-920GQ1LN9Docts5hnE7jqUdd4gg= } engines: { node: '>=0.10.0' } @@ -2496,10 +2281,6 @@ packages: safe-buffer: 5.2.1 dev: false - /ee-first/1.1.1: - resolution: { integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= } - dev: false - /electron-to-chromium/1.4.137: resolution: { @@ -2521,11 +2302,6 @@ packages: } dev: true - /encodeurl/1.0.2: - resolution: { integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= } - engines: { node: '>= 0.8' } - dev: false - /end-of-stream/1.4.4: resolution: { @@ -2535,54 +2311,6 @@ packages: once: 1.4.0 dev: false - /engine.io-client/6.2.2: - resolution: - { - integrity: sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ== - } - dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.3 - engine.io-parser: 5.0.4 - ws: 8.2.3 - xmlhttprequest-ssl: 2.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - - /engine.io-parser/5.0.4: - resolution: - { - integrity: sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg== - } - engines: { node: '>=10.0.0' } - dev: false - - /engine.io/6.2.0: - resolution: - { - integrity: sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg== - } - engines: { node: '>=10.0.0' } - dependencies: - '@types/cookie': 0.4.1 - '@types/cors': 2.8.12 - '@types/node': 17.0.31 - accepts: 1.3.8 - base64id: 2.0.0 - cookie: 0.4.2 - cors: 2.8.5 - debug: 4.3.3 - engine.io-parser: 5.0.4 - ws: 8.2.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - /enquirer/2.3.6: resolution: { @@ -2876,10 +2604,6 @@ packages: engines: { node: '>=6' } dev: true - /escape-html/1.0.3: - resolution: { integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= } - dev: false - /escape-string-regexp/1.0.5: resolution: { integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= } engines: { node: '>=0.8.0' } @@ -3093,11 +2817,6 @@ packages: engines: { node: '>=0.10.0' } dev: true - /etag/1.8.1: - resolution: { integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= } - engines: { node: '>= 0.6' } - dev: false - /execa/5.1.1: resolution: { @@ -3121,46 +2840,6 @@ packages: engines: { node: '>= 0.8.0' } dev: true - /express/4.18.1: - resolution: - { - integrity: sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== - } - engines: { node: '>= 0.10.0' } - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.0 - content-disposition: 0.5.4 - content-type: 1.0.4 - cookie: 0.5.0 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.2.0 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.1 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.7 - proxy-addr: 2.0.7 - qs: 6.10.3 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - dev: false - /fast-deep-equal/3.1.3: resolution: { @@ -3222,22 +2901,6 @@ packages: to-regex-range: 5.0.1 dev: true - /finalhandler/1.2.0: - resolution: - { - integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - } - engines: { node: '>= 0.8' } - dependencies: - debug: 2.6.9 - encodeurl: 1.0.2 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - dev: false - /flat-cache/3.0.4: resolution: { @@ -3263,14 +2926,6 @@ packages: } dev: false - /forwarded/0.2.0: - resolution: - { - integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - } - engines: { node: '>= 0.6' } - dev: false - /fraction.js/4.2.0: resolution: { @@ -3278,11 +2933,6 @@ packages: } dev: true - /fresh/0.5.2: - resolution: { integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= } - engines: { node: '>= 0.6' } - dev: false - /fs-constants/1.0.0: resolution: { @@ -3309,6 +2959,7 @@ packages: { integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== } + dev: true /function.name/1.0.13: resolution: @@ -3330,17 +2981,6 @@ packages: } dev: false - /get-intrinsic/1.1.1: - resolution: - { - integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - } - dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-symbols: 1.0.3 - dev: false - /get-port/5.1.1: resolution: { @@ -3503,14 +3143,6 @@ packages: engines: { node: '>=8' } dev: true - /has-symbols/1.0.3: - resolution: - { - integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - } - engines: { node: '>= 0.4' } - dev: false - /has/1.0.3: resolution: { @@ -3519,6 +3151,7 @@ packages: engines: { node: '>= 0.4.0' } dependencies: function-bind: 1.1.1 + dev: true /home-or-tmp/2.0.0: resolution: { integrity: sha1-42w/LSyufXRqhX440Y1fMqeILbg= } @@ -3535,20 +3168,6 @@ packages: } dev: false - /http-errors/2.0.0: - resolution: - { - integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - } - engines: { node: '>= 0.8' } - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - dev: false - /http2-wrapper/2.1.10: resolution: { @@ -3590,16 +3209,6 @@ packages: hasBin: true dev: true - /iconv-lite/0.4.24: - resolution: - { - integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - } - engines: { node: '>=0.10.0' } - dependencies: - safer-buffer: 2.1.2 - dev: false - /ieee754/1.2.1: resolution: { @@ -3698,14 +3307,6 @@ packages: engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } dev: false - /ipaddr.js/1.9.1: - resolution: - { - integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - } - engines: { node: '>= 0.10' } - dev: false - /is-binary-path/2.1.0: resolution: { @@ -4152,15 +3753,6 @@ packages: } dev: true - /media-typer/0.3.0: - resolution: { integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= } - engines: { node: '>= 0.6' } - dev: false - - /merge-descriptors/1.0.1: - resolution: { integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= } - dev: false - /merge-stream/2.0.0: resolution: { @@ -4176,11 +3768,6 @@ packages: engines: { node: '>= 8' } dev: true - /methods/1.1.2: - resolution: { integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= } - engines: { node: '>= 0.6' } - dev: false - /micromatch/4.0.4: resolution: { @@ -4192,33 +3779,6 @@ packages: picomatch: 2.3.0 dev: true - /mime-db/1.52.0: - resolution: - { - integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - } - engines: { node: '>= 0.6' } - dev: false - - /mime-types/2.1.35: - resolution: - { - integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - } - engines: { node: '>= 0.6' } - dependencies: - mime-db: 1.52.0 - dev: false - - /mime/1.6.0: - resolution: - { - integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - } - engines: { node: '>=4' } - hasBin: true - dev: false - /mimic-fn/2.1.0: resolution: { @@ -4293,6 +3853,7 @@ packages: /ms/2.0.0: resolution: { integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= } + dev: true /ms/2.1.2: resolution: @@ -4300,13 +3861,6 @@ packages: integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== } - /ms/2.1.3: - resolution: - { - integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - } - dev: false - /msgpackr-extract/1.0.15: resolution: { @@ -4357,14 +3911,6 @@ packages: resolution: { integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= } dev: true - /negotiator/0.6.3: - resolution: - { - integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - } - engines: { node: '>= 0.6' } - dev: false - /node-forge/1.3.1: resolution: { @@ -4434,11 +3980,6 @@ packages: path-key: 3.1.1 dev: true - /object-assign/4.1.1: - resolution: { integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= } - engines: { node: '>=0.10.0' } - dev: false - /object-hash/3.0.0: resolution: { @@ -4452,16 +3993,7 @@ packages: { integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== } - - /on-finished/2.4.1: - resolution: - { - integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - } - engines: { node: '>= 0.8' } - dependencies: - ee-first: 1.1.1 - dev: false + dev: true /once/1.4.0: resolution: { integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E= } @@ -4549,14 +4081,6 @@ packages: callsites: 3.1.0 dev: true - /parseurl/1.3.3: - resolution: - { - integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - } - engines: { node: '>= 0.8' } - dev: false - /path-is-absolute/1.0.1: resolution: { integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18= } engines: { node: '>=0.10.0' } @@ -4575,10 +4099,6 @@ packages: } dev: true - /path-to-regexp/0.1.7: - resolution: { integrity: sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= } - dev: false - /path-type/4.0.0: resolution: { @@ -4758,17 +4278,6 @@ packages: engines: { node: '>=0.4.0' } dev: true - /proxy-addr/2.0.7: - resolution: - { - integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - } - engines: { node: '>= 0.10' } - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - dev: false - /pseudomap/1.0.2: resolution: { integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM= } dev: true @@ -4791,16 +4300,6 @@ packages: engines: { node: '>=6' } dev: true - /qs/6.10.3: - resolution: - { - integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - } - engines: { node: '>=0.6' } - dependencies: - side-channel: 1.0.4 - dev: false - /queue-microtask/1.2.3: resolution: { @@ -4815,27 +4314,6 @@ packages: } engines: { node: '>=10' } - /range-parser/1.2.1: - resolution: - { - integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - } - engines: { node: '>= 0.6' } - dev: false - - /raw-body/2.5.1: - resolution: - { - integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - } - engines: { node: '>= 0.8' } - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - dev: false - /readable-stream/3.6.0: resolution: { @@ -5126,48 +4604,6 @@ packages: lru-cache: 6.0.0 dev: true - /send/0.18.0: - resolution: - { - integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - } - engines: { node: '>= 0.8.0' } - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - dev: false - - /serve-static/1.15.0: - resolution: - { - integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - } - engines: { node: '>= 0.8.0' } - dependencies: - encodeurl: 1.0.2 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.18.0 - dev: false - - /setprototypeof/1.2.0: - resolution: - { - integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - } - dev: false - /shebang-command/1.2.0: resolution: { integrity: sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= } engines: { node: '>=0.10.0' } @@ -5196,17 +4632,6 @@ packages: } engines: { node: '>=8' } - /side-channel/1.0.4: - resolution: - { - integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - } - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.1.1 - object-inspect: 1.12.0 - dev: false - /signal-exit/3.0.5: resolution: { @@ -5262,76 +4687,6 @@ packages: is-fullwidth-code-point: 4.0.0 dev: true - /socket.io-adapter/2.4.0: - resolution: - { - integrity: sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg== - } - dev: false - - /socket.io-client/4.5.0: - resolution: - { - integrity: sha512-HW61c1G7OrYGxaI79WRn17+b03iBCdvhBj4iqyXHBoL5M8w2MSO/vChsjA93knG4GYEai1/vbXWJna9dzxXtSg== - } - engines: { node: '>=10.0.0' } - dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.3 - engine.io-client: 6.2.2 - socket.io-parser: 4.2.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - - /socket.io-parser/4.0.4: - resolution: - { - integrity: sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g== - } - engines: { node: '>=10.0.0' } - dependencies: - '@types/component-emitter': 1.2.11 - component-emitter: 1.3.0 - debug: 4.3.3 - transitivePeerDependencies: - - supports-color - dev: false - - /socket.io-parser/4.2.0: - resolution: - { - integrity: sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng== - } - engines: { node: '>=10.0.0' } - dependencies: - '@socket.io/component-emitter': 3.1.0 - debug: 4.3.3 - transitivePeerDependencies: - - supports-color - dev: false - - /socket.io/4.5.0: - resolution: - { - integrity: sha512-slTYqU2jCgMjXwresG8grhUi/cC6GjzmcfqArzaH3BN/9I/42eZk9yamNvZJdBfTubkjEdKAKs12NEztId+bUA== - } - engines: { node: '>=10.0.0' } - dependencies: - accepts: 1.3.8 - base64id: 2.0.0 - debug: 4.3.3 - engine.io: 6.2.0 - socket.io-adapter: 2.4.0 - socket.io-parser: 4.0.4 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - /sorcery/0.10.0: resolution: { integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc= } hasBin: true @@ -5409,14 +4764,6 @@ packages: } dev: false - /statuses/2.0.1: - resolution: - { - integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - } - engines: { node: '>= 0.8' } - dev: false - /string-argv/0.3.1: resolution: { @@ -5802,14 +5149,6 @@ packages: is-number: 7.0.0 dev: true - /toidentifier/1.0.1: - resolution: - { - integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - } - engines: { node: '>=0.6' } - dev: false - /trim-right/1.0.1: resolution: { integrity: sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= } engines: { node: '>=0.10.0' } @@ -5905,17 +5244,6 @@ packages: engines: { node: '>=10' } dev: true - /type-is/1.6.18: - resolution: - { - integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - } - engines: { node: '>= 0.6' } - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - dev: false - /typescript/4.6.4: resolution: { @@ -5942,11 +5270,6 @@ packages: engines: { node: '>=8' } dev: false - /unpipe/1.0.0: - resolution: { integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= } - engines: { node: '>= 0.8' } - dev: false - /uri-js/4.4.1: resolution: { @@ -5959,11 +5282,6 @@ packages: /util-deprecate/1.0.2: resolution: { integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= } - /utils-merge/1.0.1: - resolution: { integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= } - engines: { node: '>= 0.4.0' } - dev: false - /uuid/8.3.2: resolution: { @@ -5986,11 +5304,6 @@ packages: } dev: true - /vary/1.1.2: - resolution: { integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= } - engines: { node: '>= 0.8' } - dev: false - /vite/2.9.1: resolution: { @@ -6073,30 +5386,6 @@ packages: /wrappy/1.0.2: resolution: { integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= } - /ws/8.2.3: - resolution: - { - integrity: sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== - } - 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 - - /xmlhttprequest-ssl/2.0.0: - resolution: - { - integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== - } - engines: { node: '>=0.4.0' } - dev: false - /xtend/4.0.2: resolution: { diff --git a/server/index.js b/server/index.js deleted file mode 100644 index 154a8899d..000000000 --- a/server/index.js +++ /dev/null @@ -1,20 +0,0 @@ -import express from 'express'; -import { createServer } from 'http'; -import { Server } from 'socket.io'; - -import { handler } from '../build/handler.js'; - -const app = express(); -const server = createServer(app); - -const io = new Server(server); - -io.on('connection', (socket) => { - socket.emit('eventFromServer', 'Hello, World 👋'); -}); - -app.use(handler); - -server.listen(port, () => { - console.log(`Listening on port ${port}`); -}); diff --git a/src/lib/realtime.ts b/src/lib/realtime.ts index 9fda419b9..419bb154b 100644 --- a/src/lib/realtime.ts +++ b/src/lib/realtime.ts @@ -1,3 +1,3 @@ -import ioClient from 'socket.io-client'; -const socket = ioClient('http://localhost:3000'); -export const io = socket; +// import ioClient from 'socket.io-client'; +// const socket = ioClient('http://localhost:3000'); +// export const io = socket; diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index bbc06ea31..d8a578c1a 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -43,7 +43,6 @@ import { del, get, post } from '$lib/api'; import { dev } from '$app/env'; import { features } from '$lib/store'; - import { io } from '$lib/realtime'; let isUpdateAvailable = false; let updateStatus = { @@ -53,10 +52,6 @@ }; let latestVersion = 'latest'; - io.on('eventFromServer', (message) => { - console.log(message); - }); - onMount(async () => { if ($session.userId) { const overrideVersion = $features.latestVersion; diff --git a/svelte.config.js b/svelte.config.js index 329abfeee..8fa7ba43e 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,17 +1,5 @@ import preprocess from 'svelte-preprocess'; import adapter from '@sveltejs/adapter-node'; - -import { Server } from 'socket.io'; -const webSocketServer = { - name: 'webSocketServer', - configureServer(server) { - const io = new Server(server.httpServer); - io.on('connection', (socket) => { - socket.emit('eventFromServer', 'Hello, World 👋'); - }); - } -}; - const config = { preprocess: preprocess(), kit: { @@ -21,7 +9,12 @@ const config = { }, floc: true, vite: { - plugins: [webSocketServer], + proxy: { + '/api/v2': { + target: 'http://localhost:3001/api/v2', + changeOrigin: true + } + }, optimizeDeps: { exclude: ['svelte-kit-cookie-session'] }, From 6bba37c36d2cd43e1bcc5d2520035ef8ecc54549 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 May 2022 13:02:14 +0200 Subject: [PATCH 07/44] WIP: Traefik?! --- docker-compose-traefik.yaml | 28 ++++++ package.json | 2 +- prisma/schema.prisma | 1 + src/lib/queues/proxyTcpHttp.ts | 5 +- src/routes/__layout.svelte | 14 +++ src/routes/traefik.json.ts | 152 +++++++++++++++++++++++++++++++++ src/routes/update.json.ts | 16 ++++ 7 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 docker-compose-traefik.yaml create mode 100644 src/routes/traefik.json.ts diff --git a/docker-compose-traefik.yaml b/docker-compose-traefik.yaml new file mode 100644 index 000000000..6d7ca2487 --- /dev/null +++ b/docker-compose-traefik.yaml @@ -0,0 +1,28 @@ +version: '3.8' + +services: + proxy: + image: traefik:v2.6 + command: + - --api.insecure=true + - --entrypoints.web.address=:80 + - --providers.docker=false + - --providers.docker.exposedbydefault=false + - --providers.http.endpoint=http://host.docker.internal:3000/traefik.json + - --providers.http.pollTimeout=5s + - --log.level=error + ports: + - '80:80' + - '443:443' + - '8080:8080' + volumes: + - /var/run/docker.sock:/var/run/docker.sock + extra_hosts: + - 'host.docker.internal:host-gateway' + networks: + - coolify-infra + +networks: + coolify-infra: + attachable: true + name: coolify-infra diff --git a/package.json b/package.json index 8648f9c47..782e43f09 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "dev:stop": "docker-compose -f docker-compose-dev.yaml down", "dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10", "studio": "npx prisma studio", - "start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js", + "start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node build/index.js", "build": "svelte-kit build", "preview": "svelte-kit preview", "check": "svelte-check --tsconfig ./tsconfig.json", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b0c2e233a..8196a6e51 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,6 +20,7 @@ model Setting { proxyHash String? isAutoUpdateEnabled Boolean @default(false) isDNSCheckEnabled Boolean @default(true) + disableHaproxy Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } diff --git a/src/lib/queues/proxyTcpHttp.ts b/src/lib/queues/proxyTcpHttp.ts index c268d2aa0..3d48c5c1d 100644 --- a/src/lib/queues/proxyTcpHttp.ts +++ b/src/lib/queues/proxyTcpHttp.ts @@ -6,11 +6,14 @@ export default async function (): Promise { try { + const settings = await prisma.setting.findFirst(); // Coolify Proxy const localDocker = await prisma.destinationDocker.findFirst({ where: { engine: '/var/run/docker.sock' } }); - if (localDocker && localDocker.isCoolifyProxyUsed) { + console.log(settings.disableHaproxy); + if (localDocker && localDocker.isCoolifyProxyUsed && !settings.disableHaproxy) { + console.log('asd'); await startCoolifyProxy('/var/run/docker.sock'); } // TCP Proxies diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index d8a578c1a..0f5b481f5 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -124,6 +124,13 @@ return errorNotification(error); } } + async function migrateToTraefik() { + try { + await post(`/update.json`, { type: 'migrateToTraefik' }); + } catch ({ error }) { + return errorNotification(error); + } + } @@ -515,6 +522,13 @@ >Powered by Coolify {/if} + + + {/if}
diff --git a/src/routes/traefik.json.ts b/src/routes/traefik.json.ts new file mode 100644 index 000000000..ee7745c9c --- /dev/null +++ b/src/routes/traefik.json.ts @@ -0,0 +1,152 @@ +import { asyncExecShell, getDomain, getEngine } from '$lib/common'; +import * as db from '$lib/database'; +import { checkContainer } from '$lib/haproxy'; + +export const get = async () => { + const applications = await db.prisma.application.findMany({ + include: { destinationDocker: true, settings: true } + }); + const data = { + applications: [], + services: [], + coolify: [] + }; + for (const application of applications) { + const { + fqdn, + id, + port, + destinationDocker, + destinationDockerId, + settings: { previews }, + updatedAt + } = application; + if (destinationDockerId) { + const { engine, network } = destinationDocker; + const isRunning = await checkContainer(engine, id); + if (fqdn) { + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + const isWWW = fqdn.includes('www.'); + const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`; + if (isRunning) { + data.applications.push({ + id, + port: port || 3000, + domain, + isRunning, + isHttps, + redirectValue, + redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain, + updatedAt: updatedAt.getTime() + }); + } + if (previews) { + const host = getEngine(engine); + const { stdout } = await asyncExecShell( + `DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` + ); + const containers = stdout + .trim() + .split('\n') + .filter((a) => a) + .map((c) => c.replace(/"/g, '')); + if (containers.length > 0) { + for (const container of containers) { + const previewDomain = `${container.split('-')[1]}.${domain}`; + data.applications.push({ + id: container, + port: port || 3000, + domain: previewDomain, + isRunning, + isHttps, + redirectValue, + redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain, + updatedAt: updatedAt.getTime() + }); + } + } + } + } + } + } + + const traefik = { + http: { + routers: {}, + services: {} + } + }; + for (const application of data.applications) { + const { id, port, domain, isHttps, redirectValue, redirectTo, updatedAt } = application; + traefik.http.routers[id] = { + entrypoints: ['web'], + rule: `Host(\`${domain}\`)`, + service: id + }; + traefik.http.services[id] = { + loadbalancer: { + servers: [ + { + url: `http://${id}:${port}` + } + ] + } + }; + } + return { + status: 200, + body: { + ...traefik + // "http": { + // "routers": { + // "coolify": { + // "entrypoints": [ + // "web" + // ], + // "middlewares": [ + // "coolify-hc" + // ], + // "rule": "Host(`staging.coolify.io`)", + // "service": "coolify" + // }, + // "static.example.coolify.io": { + // "entrypoints": [ + // "web" + // ], + // "rule": "Host(`static.example.coolify.io`)", + // "service": "static.example.coolify.io" + // } + // }, + // "services": { + // "coolify": { + // "loadbalancer": { + // "servers": [ + // { + // "url": "http://coolify:3000" + // } + // ] + // } + // }, + // "static.example.coolify.io": { + // "loadbalancer": { + // "servers": [ + // { + // "url": "http://cl32p06f58068518cs3thg6vbc7:80" + // } + // ] + // } + // } + // }, + // "middlewares": { + // "coolify-hc": { + // "replacepathregex": { + // "regex": "/dead.json", + // "replacement": "/undead.json" + // } + // } + // } + // } + } + }; +}; diff --git a/src/routes/update.json.ts b/src/routes/update.json.ts index 19845b90e..d584cde7b 100644 --- a/src/routes/update.json.ts +++ b/src/routes/update.json.ts @@ -61,6 +61,22 @@ export const post: RequestHandler = async (event) => { } catch (error) { return ErrorHandler(error); } + } else if (type === 'migrateToTraefik') { + try { + const settings = await db.prisma.setting.findFirst({}); + await db.prisma.setting.update({ + where: { id: settings.id }, + data: { disableHaproxy: true } + }); + await asyncExecShell(`docker stop -t 0 coolify-haproxy`); + await asyncExecShell(`docker rm coolify-haproxy`); + return { + status: 200, + body: {} + }; + } catch (error) { + return ErrorHandler(error); + } } return { status: 500 From c095cb58b301a5698db7a5ccfb9fd2e92a79f7e6 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 May 2022 13:02:40 +0200 Subject: [PATCH 08/44] migration --- .../migration.sql | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 prisma/migrations/20220512110234_disable_haproxy/migration.sql diff --git a/prisma/migrations/20220512110234_disable_haproxy/migration.sql b/prisma/migrations/20220512110234_disable_haproxy/migration.sql new file mode 100644 index 000000000..afb5dd97b --- /dev/null +++ b/prisma/migrations/20220512110234_disable_haproxy/migration.sql @@ -0,0 +1,24 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Setting" ( + "id" TEXT NOT NULL PRIMARY KEY, + "fqdn" TEXT, + "isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false, + "dualCerts" BOOLEAN NOT NULL DEFAULT false, + "minPort" INTEGER NOT NULL DEFAULT 9000, + "maxPort" INTEGER NOT NULL DEFAULT 9100, + "proxyPassword" TEXT NOT NULL, + "proxyUser" TEXT NOT NULL, + "proxyHash" TEXT, + "isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false, + "isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true, + "disableHaproxy" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); +INSERT INTO "new_Setting" ("createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting"; +DROP TABLE "Setting"; +ALTER TABLE "new_Setting" RENAME TO "Setting"; +CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; From ae5d90eb474a3381d60fd6962c1096256367b0e6 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 May 2022 16:53:22 +0200 Subject: [PATCH 09/44] WIP: Traefik --- data/traefik/docker-compose-tcp.yaml | 23 + docker-compose-traefik.yaml | 1 + .../migration.sql | 24 - prisma/schema.prisma | 2 +- src/lib/database/destinations.ts | 17 +- src/lib/haproxy/index.ts | 190 +++++++- src/lib/queues/proxy.ts | 7 +- src/lib/queues/proxyTcpHttp.ts | 53 ++- src/lib/store.ts | 2 + src/routes/__layout.svelte | 30 +- src/routes/dashboard.json.ts | 4 +- src/routes/databases/[id]/settings.json.ts | 9 +- src/routes/destinations/[id]/index.json.ts | 6 +- src/routes/destinations/[id]/restart.json.ts | 18 +- src/routes/destinations/[id]/start.json.ts | 22 +- src/routes/destinations/[id]/stop.json.ts | 11 +- .../[id]/plausibleanalytics/start.json.ts | 1 - .../services/[id]/wordpress/ftp.json.ts | 15 +- src/routes/settings/index.json.ts | 3 +- src/routes/settings/index.svelte | 41 +- src/routes/traefik.json.ts | 416 ++++++++++++------ src/routes/update.json.ts | 42 +- 22 files changed, 721 insertions(+), 216 deletions(-) create mode 100644 data/traefik/docker-compose-tcp.yaml delete mode 100644 prisma/migrations/20220512110234_disable_haproxy/migration.sql diff --git a/data/traefik/docker-compose-tcp.yaml b/data/traefik/docker-compose-tcp.yaml new file mode 100644 index 000000000..110630d2e --- /dev/null +++ b/data/traefik/docker-compose-tcp.yaml @@ -0,0 +1,23 @@ +version: '3.5' + +services: + ${ID}: + container_name: proxy-for-${PORT} + image: traefik:v2.6 + command: + - --api.insecure=true + - --entrypoints.web.address=:${PORT} + - --providers.docker=false + - --providers.docker.exposedbydefault=false + - --providers.http.endpoint=http://host.docker.internal:3000/traefik.json?id=${ID} + - --providers.http.pollTimeout=5s + - --log.level=error + ports: + - '${PORT}:${PORT}' + networks: + - ${NETWORK} + +networks: + net: + external: false + name: ${NETWORK} diff --git a/docker-compose-traefik.yaml b/docker-compose-traefik.yaml index 6d7ca2487..09fd32525 100644 --- a/docker-compose-traefik.yaml +++ b/docker-compose-traefik.yaml @@ -6,6 +6,7 @@ services: command: - --api.insecure=true - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 - --providers.docker=false - --providers.docker.exposedbydefault=false - --providers.http.endpoint=http://host.docker.internal:3000/traefik.json diff --git a/prisma/migrations/20220512110234_disable_haproxy/migration.sql b/prisma/migrations/20220512110234_disable_haproxy/migration.sql deleted file mode 100644 index afb5dd97b..000000000 --- a/prisma/migrations/20220512110234_disable_haproxy/migration.sql +++ /dev/null @@ -1,24 +0,0 @@ --- RedefineTables -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_Setting" ( - "id" TEXT NOT NULL PRIMARY KEY, - "fqdn" TEXT, - "isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false, - "dualCerts" BOOLEAN NOT NULL DEFAULT false, - "minPort" INTEGER NOT NULL DEFAULT 9000, - "maxPort" INTEGER NOT NULL DEFAULT 9100, - "proxyPassword" TEXT NOT NULL, - "proxyUser" TEXT NOT NULL, - "proxyHash" TEXT, - "isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false, - "isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true, - "disableHaproxy" BOOLEAN NOT NULL DEFAULT false, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); -INSERT INTO "new_Setting" ("createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting"; -DROP TABLE "Setting"; -ALTER TABLE "new_Setting" RENAME TO "Setting"; -CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn"); -PRAGMA foreign_key_check; -PRAGMA foreign_keys=ON; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8196a6e51..a20b3455c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,7 +20,7 @@ model Setting { proxyHash String? isAutoUpdateEnabled Boolean @default(false) isDNSCheckEnabled Boolean @default(true) - disableHaproxy Boolean @default(false) + isTraefikUsed Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } diff --git a/src/lib/database/destinations.ts b/src/lib/database/destinations.ts index 2f6cdb488..f378874b0 100644 --- a/src/lib/database/destinations.ts +++ b/src/lib/database/destinations.ts @@ -1,6 +1,6 @@ import { asyncExecShell, getEngine } from '$lib/common'; import { dockerInstance } from '$lib/docker'; -import { startCoolifyProxy } from '$lib/haproxy'; +import { startCoolifyProxy, startTraefikProxy } from '$lib/haproxy'; import { getDatabaseImage } from '.'; import { prisma } from './common'; import type { DestinationDocker, Service, Application, Prisma } from '@prisma/client'; @@ -125,7 +125,14 @@ export async function newLocalDestination({ } await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } }); } - if (isCoolifyProxyUsed) await startCoolifyProxy(engine); + if (isCoolifyProxyUsed) { + const settings = await prisma.setting.findFirst(); + if (settings?.isTraefikUsed) { + await startTraefikProxy(engine); + } else { + await startCoolifyProxy(engine); + } + } return destination.id; } export async function removeDestination({ id }: Pick): Promise { @@ -133,12 +140,14 @@ export async function removeDestination({ id }: Pick): if (destination.isCoolifyProxyUsed) { const host = getEngine(destination.engine); const { network } = destination; + const settings = await prisma.setting.findFirst(); + const containerName = settings.isTraefikUsed ? 'coolify-proxy' : 'coolify-haproxy'; const { stdout: found } = await asyncExecShell( - `DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=coolify-haproxy --format '{{.}}'` + `DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=${containerName} --format '{{.}}'` ); if (found) { await asyncExecShell( - `DOCKER_HOST="${host}" docker network disconnect ${network} coolify-haproxy` + `DOCKER_HOST="${host}" docker network disconnect ${network} ${containerName}` ); await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`); } diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index 5b713e343..361015fb3 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -3,12 +3,17 @@ import { asyncExecShell, getEngine } from '$lib/common'; import got, { type Got, type Response } from 'got'; import * as db from '$lib/database'; import type { DestinationDocker } from '@prisma/client'; - +import fs from 'fs/promises'; +import yaml from 'js-yaml'; const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555'; export const defaultProxyImage = `coolify-haproxy-alpine:latest`; export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`; export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`; +export const defaultTraefikImage = `traefik:v2.6`; +const coolifyEndpoint = dev + ? 'http://host.docker.internal:3000/traefik.json' + : 'http://coolify:3000/traefik.json'; export async function haproxyInstance(): Promise { const { proxyPassword } = await db.listSettings(); @@ -99,11 +104,17 @@ export async function checkHAProxy(haproxy?: Got): Promise { export async function stopTcpHttpProxy( destinationDocker: DestinationDocker, - publicPort: number + publicPort: number, + forceName: string = null ): Promise<{ stdout: string; stderr: string } | Error> { const { engine } = destinationDocker; const host = getEngine(engine); - const containerName = `haproxy-for-${publicPort}`; + const settings = await db.listSettings(); + let containerName = `proxy-for-${publicPort}`; + if (!settings.isTraefikUsed) { + containerName = `haproxy-for-${publicPort}`; + } + if (forceName) containerName = forceName; const found = await checkContainer(engine, containerName); try { if (found) { @@ -115,6 +126,67 @@ export async function stopTcpHttpProxy( return error; } } +export async function startTraefikTCPProxy( + destinationDocker: DestinationDocker, + id: string, + publicPort: number, + privatePort: number, + volume?: string +): Promise<{ stdout: string; stderr: string } | Error> { + const { network, engine } = destinationDocker; + const host = getEngine(engine); + + const containerName = `proxy-for-${publicPort}`; + const found = await checkContainer(engine, containerName, true); + const foundDependentContainer = await checkContainer(engine, id, true); + + try { + if (foundDependentContainer && !found) { + const { stdout: Config } = await asyncExecShell( + `DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'` + ); + const ip = JSON.parse(Config)[0].Gateway; + const tcpProxy = { + version: '3.5', + services: { + [id]: { + container_name: `proxy-for-${publicPort}`, + image: 'traefik:v2.6', + command: [ + `--entrypoints.tcp.address=:${publicPort}`, + `--providers.http.endpoint=${coolifyEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp`, + '--providers.http.pollTimeout=2s', + '--log.level=debug' + ], + ports: [`${publicPort}:${publicPort}`], + extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`], + volumes: ['/var/run/docker.sock:/var/run/docker.sock'], + networks: [network] + } + }, + networks: { + [network]: { + external: false, + name: network + } + } + }; + await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy)); + await asyncExecShell( + `DOCKER_HOST=${host} docker compose -f /tmp/docker-compose-${id}.yaml up -d` + ); + await fs.rm(`/tmp/docker-compose-${id}.yaml`); + } + if (!foundDependentContainer && found) { + return await asyncExecShell( + `DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}` + ); + } + } catch (error) { + console.log(error); + return error; + } +} export async function startTcpProxy( destinationDocker: DestinationDocker, id: string, @@ -151,6 +223,65 @@ export async function startTcpProxy( } } +export async function startTraefikHTTPProxy( + destinationDocker: DestinationDocker, + id: string, + publicPort: number, + privatePort: number +): Promise<{ stdout: string; stderr: string } | Error> { + const { network, engine } = destinationDocker; + const host = getEngine(engine); + + const containerName = `proxy-for-${publicPort}`; + const found = await checkContainer(engine, containerName, true); + const foundDependentContainer = await checkContainer(engine, id, true); + + try { + if (foundDependentContainer && !found) { + const { stdout: Config } = await asyncExecShell( + `DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'` + ); + const ip = JSON.parse(Config)[0].Gateway; + const tcpProxy = { + version: '3.5', + services: { + [id]: { + container_name: `proxy-for-${publicPort}`, + image: 'traefik:v2.6', + command: [ + `--entrypoints.http.address=:${publicPort}`, + `--providers.http.endpoint=${coolifyEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`, + '--providers.http.pollTimeout=2s', + '--log.level=debug' + ], + ports: [`${publicPort}:${publicPort}`], + extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`], + volumes: ['/var/run/docker.sock:/var/run/docker.sock'], + networks: [network] + } + }, + networks: { + [network]: { + external: false, + name: network + } + } + }; + await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy)); + await asyncExecShell( + `DOCKER_HOST=${host} docker compose -f /tmp/docker-compose-${id}.yaml up -d` + ); + await fs.rm(`/tmp/docker-compose-${id}.yaml`); + } + if (!foundDependentContainer && found) { + return await asyncExecShell( + `DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}` + ); + } + } catch (error) { + return error; + } +} export async function startHttpProxy( destinationDocker: DestinationDocker, id: string, @@ -197,10 +328,29 @@ export async function startCoolifyProxy(engine: string): Promise { `DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}` ); await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } }); + await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true }); } await configureNetworkCoolifyProxy(engine); } +export async function startTraefikProxy(engine: string): Promise { + const host = getEngine(engine); + const found = await checkContainer(engine, 'coolify-proxy', true); + const { id } = await db.listSettings(); + if (!found) { + const { stdout: Config } = await asyncExecShell( + `DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'` + ); + const ip = JSON.parse(Config)[0].Gateway; + await asyncExecShell( + `DOCKER_HOST="${host}" docker run --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl -v /var/run/docker.sock:/var/run/docker.sock --network coolify-infra -p "80:80" -p "443:443" -p "8080:8080" --name coolify-proxy -d ${defaultTraefikImage} --api.insecure=true --entrypoints.web.address=:80 --entrypoints.websecure.address=:443 --providers.docker=false --providers.docker.exposedbydefault=false --providers.http.endpoint=${coolifyEndpoint} --providers.http.pollTimeout=5s --log.level=error` + ); + await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } }); + await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true }); + } + await configureNetworkTraefikProxy(engine); +} + export async function isContainerExited(engine: string, containerName: string): Promise { let isExited = false; const host = getEngine(engine); @@ -263,6 +413,24 @@ export async function stopCoolifyProxy( return error; } } +export async function stopTraefikProxy( + engine: string +): Promise<{ stdout: string; stderr: string } | Error> { + const host = getEngine(engine); + const found = await checkContainer(engine, 'coolify-proxy'); + await db.setDestinationSettings({ engine, isCoolifyProxyUsed: false }); + const { id } = await db.prisma.setting.findFirst({}); + await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } }); + try { + if (found) { + await asyncExecShell( + `DOCKER_HOST="${host}" docker stop -t 0 coolify-proxy && docker rm coolify-proxy` + ); + } + } catch (error) { + return error; + } +} export async function configureNetworkCoolifyProxy(engine: string): Promise { const host = getEngine(engine); @@ -279,3 +447,19 @@ export async function configureNetworkCoolifyProxy(engine: string): Promise { + 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-proxy --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-proxy` + ); + } + } +} diff --git a/src/lib/queues/proxy.ts b/src/lib/queues/proxy.ts index db8742e74..245fb3b02 100644 --- a/src/lib/queues/proxy.ts +++ b/src/lib/queues/proxy.ts @@ -1,4 +1,4 @@ -import { ErrorHandler } from '$lib/database'; +import { ErrorHandler, prisma } from '$lib/database'; import { configureHAProxy } from '$lib/haproxy/configuration'; export default async function (): Promise { try { - return await configureHAProxy(); + const settings = await prisma.setting.findFirst(); + if (!settings.isTraefikUsed) { + return await configureHAProxy(); + } } catch (error) { return ErrorHandler(error.response?.body || error); } diff --git a/src/lib/queues/proxyTcpHttp.ts b/src/lib/queues/proxyTcpHttp.ts index 3d48c5c1d..b23b1119f 100644 --- a/src/lib/queues/proxyTcpHttp.ts +++ b/src/lib/queues/proxyTcpHttp.ts @@ -1,5 +1,13 @@ import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database'; -import { startCoolifyProxy, startHttpProxy, startTcpProxy } from '$lib/haproxy'; +import { + startCoolifyProxy, + startHttpProxy, + startTcpProxy, + startTraefikHTTPProxy, + startTraefikProxy, + startTraefikTCPProxy, + stopTcpHttpProxy +} from '$lib/haproxy'; export default async function (): Promise = read beta: browser && window.localStorage.getItem('beta') === 'true', latestVersion: browser && window.localStorage.getItem('latestVersion') }); + +export const isTraefikUsed: Writable = writable(false); diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index 0f5b481f5..0f0843f8f 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -34,6 +34,7 @@ @@ -522,13 +518,15 @@ >Powered by Coolify {/if} - - - + {#if !$isTraefikUsed} + + + + {/if} {/if}
diff --git a/src/routes/dashboard.json.ts b/src/routes/dashboard.json.ts index 297ce1a14..1b9528861 100644 --- a/src/routes/dashboard.json.ts +++ b/src/routes/dashboard.json.ts @@ -49,6 +49,7 @@ export const get: RequestHandler = async (event) => { where: { userId }, include: { team: { include: { _count: { select: { users: true } } } } } }); + const settings = await db.prisma.setting.findFirst(); return { body: { teams, @@ -57,7 +58,8 @@ export const get: RequestHandler = async (event) => { destinationsCount, teamsCount, databasesCount, - servicesCount + servicesCount, + settings } }; } catch (error) { diff --git a/src/routes/databases/[id]/settings.json.ts b/src/routes/databases/[id]/settings.json.ts index 1f09946c6..e4263ce9a 100644 --- a/src/routes/databases/[id]/settings.json.ts +++ b/src/routes/databases/[id]/settings.json.ts @@ -1,7 +1,7 @@ import { getUserDetails } from '$lib/common'; import * as db from '$lib/database'; import { generateDatabaseConfiguration, ErrorHandler, getFreePort } from '$lib/database'; -import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy'; +import { startTcpProxy, startTraefikTCPProxy, stopTcpHttpProxy } from '$lib/haproxy'; import type { RequestHandler } from '@sveltejs/kit'; export const post: RequestHandler = async (event) => { @@ -13,6 +13,7 @@ export const post: RequestHandler = async (event) => { const publicPort = await getFreePort(); try { + const settings = await db.listSettings(); await db.setDatabase({ id, isPublic, appendOnly }); const database = await db.getDatabase({ id, teamId }); const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database; @@ -21,7 +22,11 @@ export const post: RequestHandler = async (event) => { if (destinationDockerId) { if (isPublic) { await db.prisma.database.update({ where: { id }, data: { publicPort } }); - await startTcpProxy(destinationDocker, id, publicPort, privatePort); + if (settings.isTraefikUsed) { + await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); + } else { + await startTcpProxy(destinationDocker, id, publicPort, privatePort); + } } else { await db.prisma.database.update({ where: { id }, data: { publicPort: null } }); await stopTcpHttpProxy(destinationDocker, oldPublicPort); diff --git a/src/routes/destinations/[id]/index.json.ts b/src/routes/destinations/[id]/index.json.ts index aa9be6aea..8c4c67cee 100644 --- a/src/routes/destinations/[id]/index.json.ts +++ b/src/routes/destinations/[id]/index.json.ts @@ -26,8 +26,12 @@ export const get: RequestHandler = async (event) => { // // await saveSshKey(destination); // payload.state = await checkContainer(engine, 'coolify-haproxy'); } else { + let containerName = 'coolify-proxy'; + if (!settings.isTraefikUsed) { + containerName = 'coolify-haproxy'; + } payload.state = - destination?.engine && (await checkContainer(destination.engine, 'coolify-haproxy')); + destination?.engine && (await checkContainer(destination.engine, containerName)); } return { status: 200, diff --git a/src/routes/destinations/[id]/restart.json.ts b/src/routes/destinations/[id]/restart.json.ts index 9d2da459a..c2cf88fa4 100644 --- a/src/routes/destinations/[id]/restart.json.ts +++ b/src/routes/destinations/[id]/restart.json.ts @@ -1,7 +1,12 @@ import { getUserDetails } from '$lib/common'; import { ErrorHandler } from '$lib/database'; import * as db from '$lib/database'; -import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy'; +import { + startCoolifyProxy, + startTraefikProxy, + stopCoolifyProxy, + stopTraefikProxy +} from '$lib/haproxy'; import type { RequestHandler } from '@sveltejs/kit'; export const post: RequestHandler = async (event) => { @@ -11,9 +16,16 @@ export const post: RequestHandler = async (event) => { const { engine } = await event.request.json(); try { - await stopCoolifyProxy(engine); - await startCoolifyProxy(engine); + const settings = await db.prisma.setting.findFirst({}); + if (settings?.isTraefikUsed) { + await stopTraefikProxy(engine); + await startTraefikProxy(engine); + } else { + await stopCoolifyProxy(engine); + await startCoolifyProxy(engine); + } await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true }); + return { status: 200 }; diff --git a/src/routes/destinations/[id]/start.json.ts b/src/routes/destinations/[id]/start.json.ts index f5272418a..865187542 100644 --- a/src/routes/destinations/[id]/start.json.ts +++ b/src/routes/destinations/[id]/start.json.ts @@ -1,6 +1,12 @@ import { getUserDetails } from '$lib/common'; import { ErrorHandler } from '$lib/database'; -import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy'; +import * as db from '$lib/database'; +import { + startCoolifyProxy, + startTraefikProxy, + stopCoolifyProxy, + stopTraefikProxy +} from '$lib/haproxy'; import type { RequestHandler } from '@sveltejs/kit'; export const post: RequestHandler = async (event) => { @@ -8,14 +14,24 @@ export const post: RequestHandler = async (event) => { if (status === 401) return { status, body }; const { engine } = await event.request.json(); + const settings = await db.prisma.setting.findFirst({}); try { - await startCoolifyProxy(engine); + if (settings?.isTraefikUsed) { + await startTraefikProxy(engine); + } else { + await startCoolifyProxy(engine); + } + return { status: 200 }; } catch (error) { - await stopCoolifyProxy(engine); + if (settings?.isTraefikUsed) { + await stopTraefikProxy(engine); + } else { + await stopCoolifyProxy(engine); + } return ErrorHandler(error); } }; diff --git a/src/routes/destinations/[id]/stop.json.ts b/src/routes/destinations/[id]/stop.json.ts index 495c7612e..a1e01d218 100644 --- a/src/routes/destinations/[id]/stop.json.ts +++ b/src/routes/destinations/[id]/stop.json.ts @@ -1,6 +1,7 @@ import { getUserDetails } from '$lib/common'; import { ErrorHandler } from '$lib/database'; -import { stopCoolifyProxy } from '$lib/haproxy'; +import * as db from '$lib/database'; +import { stopCoolifyProxy, stopTraefikProxy } from '$lib/haproxy'; import type { RequestHandler } from '@sveltejs/kit'; export const post: RequestHandler = async (event) => { @@ -9,7 +10,13 @@ export const post: RequestHandler = async (event) => { const { engine } = await event.request.json(); try { - await stopCoolifyProxy(engine); + const settings = await db.prisma.setting.findFirst({}); + if (settings?.isTraefikUsed) { + await stopTraefikProxy(engine); + } else { + await stopCoolifyProxy(engine); + } + return { status: 200 }; diff --git a/src/routes/services/[id]/plausibleanalytics/start.json.ts b/src/routes/services/[id]/plausibleanalytics/start.json.ts index 81c8f2bd8..e98e7093a 100644 --- a/src/routes/services/[id]/plausibleanalytics/start.json.ts +++ b/src/routes/services/[id]/plausibleanalytics/start.json.ts @@ -195,7 +195,6 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`; } }; const composeFileDestination = `${workdir}/docker-compose.yaml`; - console.log(JSON.stringify(composeFile, null, 2)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); await asyncExecShell( diff --git a/src/routes/services/[id]/wordpress/ftp.json.ts b/src/routes/services/[id]/wordpress/ftp.json.ts index cee85bc41..8267e1807 100644 --- a/src/routes/services/[id]/wordpress/ftp.json.ts +++ b/src/routes/services/[id]/wordpress/ftp.json.ts @@ -3,7 +3,12 @@ import { asyncExecShell, getEngine, getUserDetails } from '$lib/common'; import { decrypt, encrypt } from '$lib/crypto'; import * as db from '$lib/database'; import { ErrorHandler, generatePassword, getFreePort } from '$lib/database'; -import { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy'; +import { + checkContainer, + startTcpProxy, + startTraefikTCPProxy, + stopTcpHttpProxy +} from '$lib/haproxy'; import type { ComposeFile } from '$lib/types/composeFile'; import type { RequestHandler } from '@sveltejs/kit'; import cuid from 'cuid'; @@ -142,8 +147,12 @@ export const post: RequestHandler = async (event) => { await asyncExecShell( `DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` ); - - await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22); + const settings = await db.prisma.setting.findFirst(); + if (settings.isTraefikUsed) { + await startTraefikTCPProxy(destinationDocker, `${id}-ftp`, publicPort, 22); + } else { + await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22); + } } else { await db.prisma.wordpress.update({ where: { serviceId: id }, diff --git a/src/routes/settings/index.json.ts b/src/routes/settings/index.json.ts index 21f4907f2..e57b159c6 100644 --- a/src/routes/settings/index.json.ts +++ b/src/routes/settings/index.json.ts @@ -72,8 +72,7 @@ export const post: RequestHandler = async (event) => { minPort, maxPort, isAutoUpdateEnabled, - isDNSCheckEnabled, - forceSave + isDNSCheckEnabled } = await event.request.json(); try { const { id } = await db.listSettings(); diff --git a/src/routes/settings/index.svelte b/src/routes/settings/index.svelte index c601db4ed..9b97570a0 100644 --- a/src/routes/settings/index.svelte +++ b/src/routes/settings/index.svelte @@ -37,12 +37,13 @@ import { getDomain } from '$lib/components/common'; import { toast } from '@zerodevx/svelte-toast'; import { t } from '$lib/translations'; - import { features } from '$lib/store'; + import { features, isTraefikUsed } from '$lib/store'; let isRegistrationEnabled = settings.isRegistrationEnabled; let dualCerts = settings.dualCerts; let isAutoUpdateEnabled = settings.isAutoUpdateEnabled; let isDNSCheckEnabled = settings.isDNSCheckEnabled; + $isTraefikUsed = settings.isTraefikUsed; let minPort = settings.minPort; let maxPort = settings.maxPort; @@ -55,7 +56,8 @@ let isFqdnSet = !!settings.fqdn; let loading = { save: false, - remove: false + remove: false, + proxyMigration: false }; async function removeFqdn() { @@ -86,6 +88,7 @@ if (name === 'isDNSCheckEnabled') { isDNSCheckEnabled = !isDNSCheckEnabled; } + await post(`/settings.json`, { isRegistrationEnabled, dualCerts, @@ -156,6 +159,20 @@ function resetView() { forceSave = false; } + async function migrateProxy(to) { + if (loading.proxyMigration) return; + try { + loading.proxyMigration = true; + await post(`/update.json`, { type: to }); + const data = await get(`/settings.json`); + $isTraefikUsed = data.settings.isTraefikUsed; + return toast.push('Proxy migration completed.'); + } catch ({ error }) { + return errorNotification(error); + } finally { + loading.proxyMigration = false; + } + }
@@ -192,6 +209,26 @@
+
+
+
+
New Proxy Available!
+ +
+
+ +
diff --git a/src/routes/traefik.json.ts b/src/routes/traefik.json.ts index ee7745c9c..d14438d9e 100644 --- a/src/routes/traefik.json.ts +++ b/src/routes/traefik.json.ts @@ -1,152 +1,308 @@ +import { dev } from '$app/env'; import { asyncExecShell, getDomain, getEngine } from '$lib/common'; +import { supportedServiceTypesAndVersions } from '$lib/components/common'; import * as db from '$lib/database'; +import { listServicesWithIncludes } from '$lib/database'; import { checkContainer } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; -export const get = async () => { - const applications = await db.prisma.application.findMany({ - include: { destinationDocker: true, settings: true } - }); - const data = { - applications: [], - services: [], - coolify: [] - }; - for (const application of applications) { - const { - fqdn, - id, - port, - destinationDocker, - destinationDockerId, - settings: { previews }, - updatedAt - } = application; - if (destinationDockerId) { - const { engine, network } = destinationDocker; - const isRunning = await checkContainer(engine, id); - if (fqdn) { - const domain = getDomain(fqdn); - const isHttps = fqdn.startsWith('https://'); - const isWWW = fqdn.includes('www.'); - const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`; - if (isRunning) { - data.applications.push({ - id, - port: port || 3000, - domain, - isRunning, - isHttps, - redirectValue, - redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain, - updatedAt: updatedAt.getTime() - }); +export const get: RequestHandler = async (event) => { + const id = event.url.searchParams.get('id'); + if (id) { + const privatePort = event.url.searchParams.get('privatePort'); + const publicPort = event.url.searchParams.get('publicPort'); + const type = event.url.searchParams.get('type'); + let traefik = {}; + if (publicPort) { + traefik = { + [type]: { + routers: { + [id]: { + entrypoints: [type], + rule: `HostSNI(\`*\`)`, + service: id + } + }, + services: { + [id]: { + loadbalancer: { + servers: [] + } + } + } } - if (previews) { - const host = getEngine(engine); - const { stdout } = await asyncExecShell( - `DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` - ); - const containers = stdout - .trim() - .split('\n') - .filter((a) => a) - .map((c) => c.replace(/"/g, '')); - if (containers.length > 0) { - for (const container of containers) { - const previewDomain = `${container.split('-')[1]}.${domain}`; - data.applications.push({ - id: container, - port: port || 3000, - domain: previewDomain, + }; + } + if (type === 'tcp') { + traefik[type].services[id].loadbalancer.servers.push({ address: `${id}:${privatePort}` }); + } else { + traefik[type].services[id].loadbalancer.servers.push({ url: `http://${id}:${privatePort}` }); + } + return { + status: 200, + body: { + ...traefik + } + }; + } else { + const applications = await db.prisma.application.findMany({ + include: { destinationDocker: true, settings: true } + }); + const data = { + applications: [], + services: [], + coolify: [] + }; + for (const application of applications) { + const { + fqdn, + id, + port, + destinationDocker, + destinationDockerId, + settings: { previews }, + updatedAt + } = application; + if (destinationDockerId) { + const { engine, network } = destinationDocker; + const isRunning = await checkContainer(engine, id); + if (fqdn) { + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + const isWWW = fqdn.includes('www.'); + const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`; + if (isRunning) { + data.applications.push({ + id, + port: port || 3000, + domain, + isRunning, + isHttps, + redirectValue, + redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain, + updatedAt: updatedAt.getTime() + }); + } + if (previews) { + const host = getEngine(engine); + const { stdout } = await asyncExecShell( + `DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` + ); + const containers = stdout + .trim() + .split('\n') + .filter((a) => a) + .map((c) => c.replace(/"/g, '')); + if (containers.length > 0) { + for (const container of containers) { + const previewDomain = `${container.split('-')[1]}.${domain}`; + data.applications.push({ + id: container, + port: port || 3000, + domain: previewDomain, + isRunning, + isHttps, + redirectValue, + redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain, + updatedAt: updatedAt.getTime() + }); + } + } + } + } + } + } + const services = await listServicesWithIncludes(); + + for (const service of services) { + const { + fqdn, + id, + type, + destinationDocker, + destinationDockerId, + updatedAt, + plausibleAnalytics + } = service; + if (destinationDockerId) { + const { engine } = destinationDocker; + const found = supportedServiceTypesAndVersions.find((a) => a.name === type); + if (found) { + const port = found.ports.main; + const publicPort = service[type]?.publicPort; + const isRunning = await checkContainer(engine, id); + if (fqdn) { + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + const isWWW = fqdn.includes('www.'); + const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`; + if (isRunning) { + // Plausible Analytics custom script + let scriptName = false; + if ( + type === 'plausibleanalytics' && + plausibleAnalytics.scriptName !== 'plausible.js' + ) { + scriptName = plausibleAnalytics.scriptName; + } + + data.services.push({ + id, + port, + publicPort, + domain, isRunning, isHttps, redirectValue, - redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain, - updatedAt: updatedAt.getTime() + redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain, + updatedAt: updatedAt.getTime(), + scriptName }); } } } } } - } - const traefik = { - http: { - routers: {}, - services: {} + const { fqdn } = await db.prisma.setting.findFirst(); + if (fqdn) { + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + const isWWW = fqdn.includes('www.'); + const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`; + data.coolify.push({ + id: dev ? 'host.docker.internal' : 'coolify', + port: 3000, + domain, + isHttps, + redirectValue, + redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain + }); } - }; - for (const application of data.applications) { - const { id, port, domain, isHttps, redirectValue, redirectTo, updatedAt } = application; - traefik.http.routers[id] = { - entrypoints: ['web'], - rule: `Host(\`${domain}\`)`, - service: id + const traefik = { + http: { + routers: {}, + services: {} + } }; - traefik.http.services[id] = { - loadbalancer: { - servers: [ - { - url: `http://${id}:${port}` + for (const application of data.applications) { + const { id, port, domain, isHttps, redirectValue, redirectTo, updatedAt } = application; + traefik.http.routers[id] = { + entrypoints: ['web'], + rule: `Host(\`${domain}\`)`, + service: id + }; + traefik.http.services[id] = { + loadbalancer: { + servers: [ + { + url: `http://${id}:${port}` + } + ] + } + }; + } + for (const application of data.services) { + const { id, port, domain, isHttps, redirectValue, redirectTo, updatedAt, scriptName } = + application; + + traefik.http.routers[id] = { + entrypoints: ['web'], + rule: `Host(\`${domain}\`)`, + service: id + }; + traefik.http.services[id] = { + loadbalancer: { + servers: [ + { + url: `http://${id}:${port}` + } + ] + } + }; + if (scriptName) { + if (!traefik.http.middlewares) traefik.http.middlewares = {}; + traefik.http.middlewares[`${id}-redir`] = { + replacepathregex: { + regex: `/js/${scriptName}`, + replacement: '/js/plausible.js' } - ] + }; + traefik.http.routers[id].middlewares = [`${id}-redir`]; + } + } + for (const application of data.coolify) { + const { domain, id, port } = application; + traefik.http.routers['coolify'] = { + entrypoints: ['web'], + rule: `Host(\`${domain}\`)`, + service: id + }; + traefik.http.services[id] = { + loadbalancer: { + servers: [ + { + url: `http://${id}:${port}` + } + ] + } + }; + } + + return { + status: 200, + body: { + ...traefik + // "http": { + // "routers": { + // "coolify": { + // "entrypoints": [ + // "web" + // ], + // "middlewares": [ + // "coolify-hc" + // ], + // "rule": "Host(`staging.coolify.io`)", + // "service": "coolify" + // }, + // "static.example.coolify.io": { + // "entrypoints": [ + // "web" + // ], + // "rule": "Host(`static.example.coolify.io`)", + // "service": "static.example.coolify.io" + // } + // }, + // "services": { + // "coolify": { + // "loadbalancer": { + // "servers": [ + // { + // "url": "http://coolify:3000" + // } + // ] + // } + // }, + // "static.example.coolify.io": { + // "loadbalancer": { + // "servers": [ + // { + // "url": "http://cl32p06f58068518cs3thg6vbc7:80" + // } + // ] + // } + // } + // }, + // "middlewares": { + // "coolify-hc": { + // "replacepathregex": { + // "regex": "/dead.json", + // "replacement": "/undead.json" + // } + // } + // } + // } } }; } - return { - status: 200, - body: { - ...traefik - // "http": { - // "routers": { - // "coolify": { - // "entrypoints": [ - // "web" - // ], - // "middlewares": [ - // "coolify-hc" - // ], - // "rule": "Host(`staging.coolify.io`)", - // "service": "coolify" - // }, - // "static.example.coolify.io": { - // "entrypoints": [ - // "web" - // ], - // "rule": "Host(`static.example.coolify.io`)", - // "service": "static.example.coolify.io" - // } - // }, - // "services": { - // "coolify": { - // "loadbalancer": { - // "servers": [ - // { - // "url": "http://coolify:3000" - // } - // ] - // } - // }, - // "static.example.coolify.io": { - // "loadbalancer": { - // "servers": [ - // { - // "url": "http://cl32p06f58068518cs3thg6vbc7:80" - // } - // ] - // } - // } - // }, - // "middlewares": { - // "coolify-hc": { - // "replacepathregex": { - // "regex": "/dead.json", - // "replacement": "/undead.json" - // } - // } - // } - // } - } - }; }; diff --git a/src/routes/update.json.ts b/src/routes/update.json.ts index d584cde7b..429030db1 100644 --- a/src/routes/update.json.ts +++ b/src/routes/update.json.ts @@ -6,6 +6,12 @@ import * as db from '$lib/database'; import type { RequestHandler } from '@sveltejs/kit'; import compare from 'compare-versions'; import got from 'got'; +import { + checkContainer, + configureNetworkTraefikProxy, + startCoolifyProxy, + startTraefikProxy +} from '$lib/haproxy'; export const get: RequestHandler = async (request) => { try { @@ -34,14 +40,14 @@ export const get: RequestHandler = async (request) => { export const post: RequestHandler = async (event) => { const { type, latestVersion } = await event.request.json(); + const settings = await db.prisma.setting.findFirst(); if (type === 'update') { try { if (!dev) { - const { isAutoUpdateEnabled } = await db.prisma.setting.findFirst(); await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`); await asyncExecShell(`env | grep COOLIFY > .env`); await asyncExecShell( - `sed -i '/COOLIFY_AUTO_UPDATE=/c\COOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` + `sed -i '/COOLIFY_AUTO_UPDATE=/c\COOLIFY_AUTO_UPDATE=${settings.isAutoUpdateEnabled}' .env` ); await asyncExecShell( `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-redis && docker rm coolify coolify-redis && docker compose up -d --force-recreate"` @@ -61,15 +67,37 @@ export const post: RequestHandler = async (event) => { } catch (error) { return ErrorHandler(error); } - } else if (type === 'migrateToTraefik') { + } else if (type === 'traefik') { try { - const settings = await db.prisma.setting.findFirst({}); + const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy'); + if (found) { + await asyncExecShell(`docker stop -t 0 coolify-haproxy`); + await asyncExecShell(`docker rm coolify-haproxy`); + } + await startTraefikProxy('/var/run/docker.sock'); await db.prisma.setting.update({ where: { id: settings.id }, - data: { disableHaproxy: true } + data: { isTraefikUsed: true } + }); + return { + status: 200, + body: {} + }; + } catch (error) { + return ErrorHandler(error); + } + } else if (type === 'haproxy') { + try { + const found = await checkContainer('/var/run/docker.sock', 'coolify-proxy'); + if (found) { + await asyncExecShell(`docker stop -t 0 coolify-proxy`); + await asyncExecShell(`docker rm coolify-proxy`); + } + await startCoolifyProxy('/var/run/docker.sock'); + await db.prisma.setting.update({ + where: { id: settings.id }, + data: { isTraefikUsed: false } }); - await asyncExecShell(`docker stop -t 0 coolify-haproxy`); - await asyncExecShell(`docker rm coolify-haproxy`); return { status: 200, body: {} From e717c1d59927dc58ad2d2313b63585f4e19e60d5 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 May 2022 17:49:38 +0200 Subject: [PATCH 10/44] add migration --- .../20220513154929_traefik/migration.sql | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 prisma/migrations/20220513154929_traefik/migration.sql diff --git a/prisma/migrations/20220513154929_traefik/migration.sql b/prisma/migrations/20220513154929_traefik/migration.sql new file mode 100644 index 000000000..164bed387 --- /dev/null +++ b/prisma/migrations/20220513154929_traefik/migration.sql @@ -0,0 +1,24 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Setting" ( + "id" TEXT NOT NULL PRIMARY KEY, + "fqdn" TEXT, + "isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false, + "dualCerts" BOOLEAN NOT NULL DEFAULT false, + "minPort" INTEGER NOT NULL DEFAULT 9000, + "maxPort" INTEGER NOT NULL DEFAULT 9100, + "proxyPassword" TEXT NOT NULL, + "proxyUser" TEXT NOT NULL, + "proxyHash" TEXT, + "isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false, + "isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true, + "isTraefikUsed" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); +INSERT INTO "new_Setting" ("createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting"; +DROP TABLE "Setting"; +ALTER TABLE "new_Setting" RENAME TO "Setting"; +CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; From 436e0e3a2bcf8e9596ce13ace2d2e9adec0bdc1b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 May 2022 18:09:12 +0200 Subject: [PATCH 11/44] WIP: Traefik --- src/lib/haproxy/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index 361015fb3..77a61974e 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -336,14 +336,14 @@ export async function startCoolifyProxy(engine: string): Promise { export async function startTraefikProxy(engine: string): Promise { const host = getEngine(engine); const found = await checkContainer(engine, 'coolify-proxy', true); - const { id } = await db.listSettings(); + const { id, proxyPassword, proxyUser } = await db.listSettings(); if (!found) { const { stdout: Config } = await asyncExecShell( `DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'` ); const ip = JSON.parse(Config)[0].Gateway; await asyncExecShell( - `DOCKER_HOST="${host}" docker run --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl -v /var/run/docker.sock:/var/run/docker.sock --network coolify-infra -p "80:80" -p "443:443" -p "8080:8080" --name coolify-proxy -d ${defaultTraefikImage} --api.insecure=true --entrypoints.web.address=:80 --entrypoints.websecure.address=:443 --providers.docker=false --providers.docker.exposedbydefault=false --providers.http.endpoint=${coolifyEndpoint} --providers.http.pollTimeout=5s --log.level=error` + `DOCKER_HOST="${host}" docker run --restart always --label "traefik.http.middlewares.dashboard.basicauth.users=${proxyUser}:${proxyPassword}" --label "traefik.http.routers.dashboard.service=api@internal" --label "traefik.http.routers.dashboard.middlewares=auth" --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl -v /var/run/docker.sock:/var/run/docker.sock --network coolify-infra -p "80:80" -p "443:443" -p "8080:8080" --name coolify-proxy -d ${defaultTraefikImage} --api.dashboard=true --entrypoints.web.address=:80 --entrypoints.websecure.address=:443 --providers.docker=false --providers.docker.exposedbydefault=false --providers.http.endpoint=${coolifyEndpoint} --providers.http.pollTimeout=5s --log.level=error` ); await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } }); await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true }); From 1fa5c5e0213bd6b93f4b8248f8f8e580b5e35f02 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Sat, 14 May 2022 15:23:41 +0200 Subject: [PATCH 12/44] WIP: Traefik migration --- src/lib/haproxy/index.ts | 2 +- src/lib/queues/proxyTcpHttp.ts | 8 +++-- src/routes/settings/index.svelte | 2 +- src/routes/traefik.json.ts | 55 +++++++++++++++++++++++--------- 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index 77a61974e..8f7d59592 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -343,7 +343,7 @@ export async function startTraefikProxy(engine: string): Promise { ); const ip = JSON.parse(Config)[0].Gateway; await asyncExecShell( - `DOCKER_HOST="${host}" docker run --restart always --label "traefik.http.middlewares.dashboard.basicauth.users=${proxyUser}:${proxyPassword}" --label "traefik.http.routers.dashboard.service=api@internal" --label "traefik.http.routers.dashboard.middlewares=auth" --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl -v /var/run/docker.sock:/var/run/docker.sock --network coolify-infra -p "80:80" -p "443:443" -p "8080:8080" --name coolify-proxy -d ${defaultTraefikImage} --api.dashboard=true --entrypoints.web.address=:80 --entrypoints.websecure.address=:443 --providers.docker=false --providers.docker.exposedbydefault=false --providers.http.endpoint=${coolifyEndpoint} --providers.http.pollTimeout=5s --log.level=error` + `DOCKER_HOST="${host}" docker run --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl -v /var/run/docker.sock:/var/run/docker.sock --network coolify-infra -p "80:80" -p "443:443" -p "8080:8080" --name coolify-proxy -d ${defaultTraefikImage} --entrypoints.web.address=:80 --entrypoints.websecure.address=:443 --providers.docker=true --providers.docker.exposedbydefault=false --providers.http.endpoint=${coolifyEndpoint} --providers.http.pollTimeout=5s --log.level=error` ); await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } }); await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true }); diff --git a/src/lib/queues/proxyTcpHttp.ts b/src/lib/queues/proxyTcpHttp.ts index b23b1119f..b120c2f74 100644 --- a/src/lib/queues/proxyTcpHttp.ts +++ b/src/lib/queues/proxyTcpHttp.ts @@ -57,10 +57,14 @@ export default async function (): Promise
-
+
New Proxy Available!
{ const type = event.url.searchParams.get('type'); let traefik = {}; if (publicPort) { - traefik = { - [type]: { - routers: { - [id]: { - entrypoints: [type], - rule: `HostSNI(\`*\`)`, - service: id - } - }, - services: { - [id]: { - loadbalancer: { - servers: [] + if (type === 'tcp') { + traefik = { + [type]: { + routers: { + [id]: { + entrypoints: [type], + rule: `HostSNI(\`*\`)`, + service: id + } + }, + services: { + [id]: { + loadbalancer: { + servers: [] + } } } } + }; + } else if (type === 'http') { + const service = await db.prisma.service.findFirst({ where: { id } }); + if (service?.fqdn) { + const domain = getDomain(service.fqdn); + traefik = { + [type]: { + routers: { + [id]: { + entrypoints: [type], + rule: `Host(\`${domain}\`)`, + service: id + } + }, + services: { + [id]: { + loadbalancer: { + servers: [] + } + } + } + } + }; } - }; + } } if (type === 'tcp') { traefik[type].services[id].loadbalancer.servers.push({ address: `${id}:${privatePort}` }); - } else { + } else if (type === 'http') { traefik[type].services[id].loadbalancer.servers.push({ url: `http://${id}:${privatePort}` }); } return { From 4f4f5b1c01ae4ddb3e5b686341eed23e357c4194 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 16 May 2022 16:11:35 +0200 Subject: [PATCH 13/44] WIP: Traefik --- docker-compose.yaml | 2 + src/lib/haproxy/index.ts | 27 ++- src/lib/queues/proxyTcpHttp.ts | 18 +- src/lib/queues/ssl.ts | 6 +- src/lib/queues/sslrenewal.ts | 6 +- src/routes/traefik.json.ts | 304 ++++++++++++++++++++++----------- 6 files changed, 253 insertions(+), 110 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 6f3cd69f0..39866b5e2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -39,3 +39,5 @@ volumes: name: coolify-ssl-certs coolify-letsencrypt: name: coolify-letsencrypt + coolify-traefik-letsencrypt: + name: coolify-traefik-letsencrypt diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index 8f7d59592..c31b7740f 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -156,7 +156,7 @@ export async function startTraefikTCPProxy( `--entrypoints.tcp.address=:${publicPort}`, `--providers.http.endpoint=${coolifyEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp`, '--providers.http.pollTimeout=2s', - '--log.level=debug' + '--log.level=error' ], ports: [`${publicPort}:${publicPort}`], extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`], @@ -252,7 +252,7 @@ export async function startTraefikHTTPProxy( `--entrypoints.http.address=:${publicPort}`, `--providers.http.endpoint=${coolifyEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`, '--providers.http.pollTimeout=2s', - '--log.level=debug' + '--log.level=error' ], ports: [`${publicPort}:${publicPort}`], extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`], @@ -343,7 +343,28 @@ export async function startTraefikProxy(engine: string): Promise { ); const ip = JSON.parse(Config)[0].Gateway; await asyncExecShell( - `DOCKER_HOST="${host}" docker run --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl -v /var/run/docker.sock:/var/run/docker.sock --network coolify-infra -p "80:80" -p "443:443" -p "8080:8080" --name coolify-proxy -d ${defaultTraefikImage} --entrypoints.web.address=:80 --entrypoints.websecure.address=:443 --providers.docker=true --providers.docker.exposedbydefault=false --providers.http.endpoint=${coolifyEndpoint} --providers.http.pollTimeout=5s --log.level=error` + `DOCKER_HOST="${host}" docker run --restart always \ + --add-host 'host.docker.internal:host-gateway' \ + --add-host 'host.docker.internal:${ip}' \ + -v coolify-traefik-letsencrypt:/etc/traefik/acme \ + -v /var/run/docker.sock:/var/run/docker.sock \ + --network coolify-infra \ + -p "80:80" \ + -p "443:443" \ + -p "8080:8080" \ + --name coolify-proxy \ + -d ${defaultTraefikImage} \ + --api.insecure=true \ + --entrypoints.web.address=:80 \ + --entrypoints.websecure.address=:443 \ + --providers.docker=true \ + --providers.docker.exposedbydefault=false \ + --providers.http.endpoint=${coolifyEndpoint} \ + --providers.http.pollTimeout=5s \ + --certificatesresolvers.letsencrypt.acme.httpchallenge=true \ + --certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \ + --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \ + --log.level=debug` ); await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } }); await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true }); diff --git a/src/lib/queues/proxyTcpHttp.ts b/src/lib/queues/proxyTcpHttp.ts index b120c2f74..e4ed9fe16 100644 --- a/src/lib/queues/proxyTcpHttp.ts +++ b/src/lib/queues/proxyTcpHttp.ts @@ -1,12 +1,15 @@ import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database'; import { + checkContainer, startCoolifyProxy, startHttpProxy, startTcpProxy, startTraefikHTTPProxy, startTraefikProxy, startTraefikTCPProxy, - stopTcpHttpProxy + stopCoolifyProxy, + stopTcpHttpProxy, + stopTraefikProxy } from '$lib/haproxy'; export default async function (): Promise { try { - const settings = await prisma.setting.findFirst(); // Coolify Proxy + const engine = '/var/run/docker.sock'; + const settings = await prisma.setting.findFirst(); const localDocker = await prisma.destinationDocker.findFirst({ - where: { engine: '/var/run/docker.sock' } + where: { engine } }); if (localDocker && localDocker.isCoolifyProxyUsed) { if (settings.isTraefikUsed) { - await startTraefikProxy('/var/run/docker.sock'); + const found = await checkContainer(engine, 'coolify-haproxy'); + if (found) await stopCoolifyProxy(engine); + await startTraefikProxy(engine); } else { - await startCoolifyProxy('/var/run/docker.sock'); + const found = await checkContainer(engine, 'coolify-proxy'); + if (found) await stopTraefikProxy(engine); + await startCoolifyProxy(engine); } } diff --git a/src/lib/queues/ssl.ts b/src/lib/queues/ssl.ts index 84b608fae..2aaf50732 100644 --- a/src/lib/queues/ssl.ts +++ b/src/lib/queues/ssl.ts @@ -1,8 +1,12 @@ import { generateSSLCerts } from '$lib/letsencrypt'; +import { prisma } from '$lib/database'; export default async function (): Promise { try { - return await generateSSLCerts(); + const settings = await prisma.setting.findFirst(); + if (!settings.isTraefikUsed) { + return await generateSSLCerts(); + } } catch (error) { console.log(error); throw error; diff --git a/src/lib/queues/sslrenewal.ts b/src/lib/queues/sslrenewal.ts index 766b9c502..0c14b2dc9 100644 --- a/src/lib/queues/sslrenewal.ts +++ b/src/lib/queues/sslrenewal.ts @@ -1,8 +1,12 @@ import { renewSSLCerts } from '$lib/letsencrypt'; +import { prisma } from '$lib/database'; export default async function (): Promise { try { - return await renewSSLCerts(); + const settings = await prisma.setting.findFirst(); + if (!settings.isTraefikUsed) { + return await renewSSLCerts(); + } } catch (error) { console.log(error); throw error; diff --git a/src/routes/traefik.json.ts b/src/routes/traefik.json.ts index 5f7a5934f..54f242109 100644 --- a/src/routes/traefik.json.ts +++ b/src/routes/traefik.json.ts @@ -6,16 +6,49 @@ import { listServicesWithIncludes } from '$lib/database'; import { checkContainer } from '$lib/haproxy'; import type { RequestHandler } from '@sveltejs/kit'; +function generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik }) { + if (!isDualCerts) { + if (isWWW) { + if (isHttps) { + traefik.http.routers[id].middlewares?.length > 0 + ? traefik.http.routers[id].middlewares.push('https-redirect-non-www-to-www') + : (traefik.http.routers[id].middlewares = [ + 'https-redirect-non-www-to-www', + 'http-to-https' + ]); + } else { + traefik.http.routers[id].middlewares?.length > 0 + ? traefik.http.routers[id].middlewares.push('http-redirect-non-www-to-www') + : (traefik.http.routers[id].middlewares = [ + 'http-redirect-non-www-to-www', + 'https-to-http' + ]); + } + } else { + if (isHttps) { + traefik.http.routers[id].middlewares?.length > 0 + ? traefik.http.routers[id].middlewares.push('https-redirect-www-to-non-www') + : (traefik.http.routers[id].middlewares = [ + 'https-redirect-www-to-non-www', + 'http-to-https' + ]); + } else { + traefik.http.routers[id]?.middlewares?.length > 0 + ? traefik.http.routers[id].middlewares.push('http-redirect-www-to-non-www') + : (traefik.http.routers[id].middlewares = ['http-redirect-www-to-non-www']); + } + } + } +} export const get: RequestHandler = async (event) => { const id = event.url.searchParams.get('id'); if (id) { const privatePort = event.url.searchParams.get('privatePort'); const publicPort = event.url.searchParams.get('publicPort'); const type = event.url.searchParams.get('type'); - let traefik = {}; if (publicPort) { if (type === 'tcp') { - traefik = { + const traefik = { [type]: { routers: { [id]: { @@ -27,47 +60,64 @@ export const get: RequestHandler = async (event) => { services: { [id]: { loadbalancer: { - servers: [] + servers: [{ address: `${id}:${privatePort}` }] } } + }, + middlewares: { + ['global-compress']: { + compress: true + } } } }; + return { + status: 200, + body: { + ...traefik + } + }; } else if (type === 'http') { const service = await db.prisma.service.findFirst({ where: { id } }); if (service?.fqdn) { const domain = getDomain(service.fqdn); - traefik = { + const isWWW = domain.startsWith('www.'); + const traefik = { [type]: { routers: { [id]: { entrypoints: [type], - rule: `Host(\`${domain}\`)`, + rule: isWWW + ? `Host(\`${domain}\`) || Host(\`www.${domain}\`)` + : `Host(\`${domain}\`)`, service: id } }, services: { [id]: { loadbalancer: { - servers: [] + servers: [{ url: `http://${id}:${privatePort}` }] } } + }, + middlewares: { + ['global-compress']: { + compress: true + } } } }; + return { + status: 200, + body: { + ...traefik + } + }; } } } - if (type === 'tcp') { - traefik[type].services[id].loadbalancer.servers.push({ address: `${id}:${privatePort}` }); - } else if (type === 'http') { - traefik[type].services[id].loadbalancer.servers.push({ url: `http://${id}:${privatePort}` }); - } return { - status: 200, - body: { - ...traefik - } + status: 500 }; } else { const applications = await db.prisma.application.findMany({ @@ -85,27 +135,26 @@ export const get: RequestHandler = async (event) => { port, destinationDocker, destinationDockerId, - settings: { previews }, - updatedAt + settings: { previews, dualCerts } } = application; if (destinationDockerId) { const { engine, network } = destinationDocker; const isRunning = await checkContainer(engine, id); if (fqdn) { const domain = getDomain(fqdn); + const nakedDomain = domain.replace(/^www\./, ''); const isHttps = fqdn.startsWith('https://'); const isWWW = fqdn.includes('www.'); - const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`; if (isRunning) { data.applications.push({ id, port: port || 3000, domain, + nakedDomain, isRunning, isHttps, - redirectValue, - redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain, - updatedAt: updatedAt.getTime() + isWWW, + isDualCerts: dualCerts }); } if (previews) { @@ -127,9 +176,7 @@ export const get: RequestHandler = async (event) => { domain: previewDomain, isRunning, isHttps, - redirectValue, - redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain, - updatedAt: updatedAt.getTime() + isWWW }); } } @@ -144,9 +191,9 @@ export const get: RequestHandler = async (event) => { fqdn, id, type, + dualCerts, destinationDocker, destinationDockerId, - updatedAt, plausibleAnalytics } = service; if (destinationDockerId) { @@ -158,9 +205,9 @@ export const get: RequestHandler = async (event) => { const isRunning = await checkContainer(engine, id); if (fqdn) { const domain = getDomain(fqdn); + const nakedDomain = domain.replace(/^www\./, ''); const isHttps = fqdn.startsWith('https://'); const isWWW = fqdn.includes('www.'); - const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`; if (isRunning) { // Plausible Analytics custom script let scriptName = false; @@ -170,17 +217,16 @@ export const get: RequestHandler = async (event) => { ) { scriptName = plausibleAnalytics.scriptName; } - data.services.push({ id, port, publicPort, domain, + nakedDomain, isRunning, isHttps, - redirectValue, - redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain, - updatedAt: updatedAt.getTime(), + isWWW, + isDualCerts: dualCerts, scriptName }); } @@ -189,34 +235,115 @@ export const get: RequestHandler = async (event) => { } } - const { fqdn } = await db.prisma.setting.findFirst(); + const { fqdn, dualCerts } = await db.prisma.setting.findFirst(); if (fqdn) { const domain = getDomain(fqdn); + const nakedDomain = domain.replace(/^www\./, ''); const isHttps = fqdn.startsWith('https://'); const isWWW = fqdn.includes('www.'); - const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`; data.coolify.push({ id: dev ? 'host.docker.internal' : 'coolify', port: 3000, domain, + nakedDomain, isHttps, - redirectValue, - redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain + isWWW, + isDualCerts: dualCerts }); } const traefik = { http: { routers: {}, - services: {} + services: {}, + middlewares: { + ['global-compress']: { + compress: true + }, + ['https-redirect-non-www-to-www']: { + redirectregex: { + regex: '^https://(?:www\\.)?(.+)', + replacement: 'https://www.${1}', + permanent: dev ? false : true + } + }, + ['http-redirect-non-www-to-www']: { + redirectregex: { + regex: '^http://(?:www\\.)?(.+)', + replacement: 'http://www.${1}', + permanent: dev ? false : true + } + }, + ['https-redirect-www-to-non-www']: { + redirectregex: { + regex: '^https?://www\\.(.+)', + replacement: 'https://${1}', + permanent: dev ? false : true + } + }, + ['http-redirect-www-to-non-www']: { + redirectregex: { + regex: '^http?://www\\.(.+)', + replacement: 'http://${1}', + permanent: dev ? false : true + } + }, + ['http-to-https']: { + redirectregex: { + regex: '^http?://(.+)', + replacement: 'https://${1}', + permanent: dev ? false : true + } + }, + ['https-to-http']: { + redirectregex: { + regex: '^https?://(.+)', + replacement: 'http://${1}', + permanent: dev ? false : true + } + }, + ['https-http']: { + redirectscheme: { + scheme: 'http', + permanent: false + } + } + } } }; for (const application of data.applications) { - const { id, port, domain, isHttps, redirectValue, redirectTo, updatedAt } = application; - traefik.http.routers[id] = { - entrypoints: ['web'], - rule: `Host(\`${domain}\`)`, - service: id - }; + const { id, port, domain, nakedDomain, isHttps, isWWW, isDualCerts } = application; + if (isHttps) { + traefik.http.routers[id] = { + entrypoints: ['web'], + rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, + middlewares: ['http-to-https'], + service: id + }; + traefik.http.routers[`${id}-secure`] = { + entrypoints: ['websecure'], + rule: isWWW + ? isDualCerts + ? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)` + : `Host(\`${nakedDomain}\`)` + : `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, + service: id + }; + } else { + traefik.http.routers[id] = { + entrypoints: ['web'], + rule: isWWW + ? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)` + : `Host(\`${nakedDomain}\`)`, + service: id + }; + traefik.http.routers[`${id}-secure`] = { + entrypoints: ['websecure'], + rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, + middlewares: ['https-http'], + service: id + }; + } + traefik.http.services[id] = { loadbalancer: { servers: [ @@ -226,14 +353,23 @@ export const get: RequestHandler = async (event) => { ] } }; + if (isHttps && !dev) { + traefik.http.routers[id].tls = { + certresolver: 'letsencrypt' + }; + } + generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik }); } - for (const application of data.services) { - const { id, port, domain, isHttps, redirectValue, redirectTo, updatedAt, scriptName } = - application; + for (const service of data.services) { + const { id, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName } = service; traefik.http.routers[id] = { - entrypoints: ['web'], - rule: `Host(\`${domain}\`)`, + entrypoints: isHttps ? ['web', 'websecure'] : ['web'], + rule: isWWW + ? isDualCerts + ? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)` + : `Host(\`${nakedDomain}\`)` + : `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, service: id }; traefik.http.services[id] = { @@ -245,22 +381,33 @@ export const get: RequestHandler = async (event) => { ] } }; + if (isHttps && !dev) { + traefik.http.routers[id].tls = { + certresolver: 'letsencrypt' + }; + } if (scriptName) { if (!traefik.http.middlewares) traefik.http.middlewares = {}; traefik.http.middlewares[`${id}-redir`] = { replacepathregex: { regex: `/js/${scriptName}`, - replacement: '/js/plausible.js' + replacement: '/js/plausible.js', + permanent: false } }; traefik.http.routers[id].middlewares = [`${id}-redir`]; } + generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik }); } - for (const application of data.coolify) { - const { domain, id, port } = application; + for (const coolify of data.coolify) { + const { nakedDomain, domain, id, port, isHttps, isWWW, isDualCerts } = coolify; traefik.http.routers['coolify'] = { - entrypoints: ['web'], - rule: `Host(\`${domain}\`)`, + entrypoints: isHttps ? ['web', 'websecure'] : ['web'], + rule: isWWW + ? isDualCerts + ? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)` + : `Host(\`${nakedDomain}\`)` + : `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, service: id }; traefik.http.services[id] = { @@ -272,61 +419,18 @@ export const get: RequestHandler = async (event) => { ] } }; + if (isHttps && !dev) { + traefik.http.routers[id].tls = { + certresolver: 'letsencrypt' + }; + } + generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik }); } return { status: 200, body: { ...traefik - // "http": { - // "routers": { - // "coolify": { - // "entrypoints": [ - // "web" - // ], - // "middlewares": [ - // "coolify-hc" - // ], - // "rule": "Host(`staging.coolify.io`)", - // "service": "coolify" - // }, - // "static.example.coolify.io": { - // "entrypoints": [ - // "web" - // ], - // "rule": "Host(`static.example.coolify.io`)", - // "service": "static.example.coolify.io" - // } - // }, - // "services": { - // "coolify": { - // "loadbalancer": { - // "servers": [ - // { - // "url": "http://coolify:3000" - // } - // ] - // } - // }, - // "static.example.coolify.io": { - // "loadbalancer": { - // "servers": [ - // { - // "url": "http://cl32p06f58068518cs3thg6vbc7:80" - // } - // ] - // } - // } - // }, - // "middlewares": { - // "coolify-hc": { - // "replacepathregex": { - // "regex": "/dead.json", - // "replacement": "/undead.json" - // } - // } - // } - // } } }; } From 8516ac671a2a23e7aa6654342ae4bff378cf4999 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 16 May 2022 23:20:50 +0200 Subject: [PATCH 14/44] WIP: Traefik --- src/lib/haproxy/index.ts | 17 +- src/routes/traefik.json.ts | 437 ---------------------- src/routes/webhooks/traefik/main.json.ts | 283 ++++++++++++++ src/routes/webhooks/traefik/other.json.ts | 76 ++++ 4 files changed, 370 insertions(+), 443 deletions(-) delete mode 100644 src/routes/traefik.json.ts create mode 100644 src/routes/webhooks/traefik/main.json.ts create mode 100644 src/routes/webhooks/traefik/other.json.ts diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index c31b7740f..d0a6cb804 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -11,9 +11,14 @@ export const defaultProxyImage = `coolify-haproxy-alpine:latest`; export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`; export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`; export const defaultTraefikImage = `traefik:v2.6`; -const coolifyEndpoint = dev - ? 'http://host.docker.internal:3000/traefik.json' - : 'http://coolify:3000/traefik.json'; + +const mainTraefikEndpoint = dev + ? 'http://host.docker.internal:3000/webhooks/traefik/main.json' + : 'http://coolify:3000/webhooks/traefik/main.json'; + +const otherTraefikEndpoint = dev + ? 'http://host.docker.internal:3000/webhooks/traefik/other.json' + : 'http://coolify:3000/webhooks/traefik/other.json'; export async function haproxyInstance(): Promise { const { proxyPassword } = await db.listSettings(); @@ -154,7 +159,7 @@ export async function startTraefikTCPProxy( image: 'traefik:v2.6', command: [ `--entrypoints.tcp.address=:${publicPort}`, - `--providers.http.endpoint=${coolifyEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp`, + `--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp`, '--providers.http.pollTimeout=2s', '--log.level=error' ], @@ -250,7 +255,7 @@ export async function startTraefikHTTPProxy( image: 'traefik:v2.6', command: [ `--entrypoints.http.address=:${publicPort}`, - `--providers.http.endpoint=${coolifyEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`, + `--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`, '--providers.http.pollTimeout=2s', '--log.level=error' ], @@ -359,7 +364,7 @@ export async function startTraefikProxy(engine: string): Promise { --entrypoints.websecure.address=:443 \ --providers.docker=true \ --providers.docker.exposedbydefault=false \ - --providers.http.endpoint=${coolifyEndpoint} \ + --providers.http.endpoint=${mainTraefikEndpoint} \ --providers.http.pollTimeout=5s \ --certificatesresolvers.letsencrypt.acme.httpchallenge=true \ --certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \ diff --git a/src/routes/traefik.json.ts b/src/routes/traefik.json.ts deleted file mode 100644 index 54f242109..000000000 --- a/src/routes/traefik.json.ts +++ /dev/null @@ -1,437 +0,0 @@ -import { dev } from '$app/env'; -import { asyncExecShell, getDomain, getEngine } from '$lib/common'; -import { supportedServiceTypesAndVersions } from '$lib/components/common'; -import * as db from '$lib/database'; -import { listServicesWithIncludes } from '$lib/database'; -import { checkContainer } from '$lib/haproxy'; -import type { RequestHandler } from '@sveltejs/kit'; - -function generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik }) { - if (!isDualCerts) { - if (isWWW) { - if (isHttps) { - traefik.http.routers[id].middlewares?.length > 0 - ? traefik.http.routers[id].middlewares.push('https-redirect-non-www-to-www') - : (traefik.http.routers[id].middlewares = [ - 'https-redirect-non-www-to-www', - 'http-to-https' - ]); - } else { - traefik.http.routers[id].middlewares?.length > 0 - ? traefik.http.routers[id].middlewares.push('http-redirect-non-www-to-www') - : (traefik.http.routers[id].middlewares = [ - 'http-redirect-non-www-to-www', - 'https-to-http' - ]); - } - } else { - if (isHttps) { - traefik.http.routers[id].middlewares?.length > 0 - ? traefik.http.routers[id].middlewares.push('https-redirect-www-to-non-www') - : (traefik.http.routers[id].middlewares = [ - 'https-redirect-www-to-non-www', - 'http-to-https' - ]); - } else { - traefik.http.routers[id]?.middlewares?.length > 0 - ? traefik.http.routers[id].middlewares.push('http-redirect-www-to-non-www') - : (traefik.http.routers[id].middlewares = ['http-redirect-www-to-non-www']); - } - } - } -} -export const get: RequestHandler = async (event) => { - const id = event.url.searchParams.get('id'); - if (id) { - const privatePort = event.url.searchParams.get('privatePort'); - const publicPort = event.url.searchParams.get('publicPort'); - const type = event.url.searchParams.get('type'); - if (publicPort) { - if (type === 'tcp') { - const traefik = { - [type]: { - routers: { - [id]: { - entrypoints: [type], - rule: `HostSNI(\`*\`)`, - service: id - } - }, - services: { - [id]: { - loadbalancer: { - servers: [{ address: `${id}:${privatePort}` }] - } - } - }, - middlewares: { - ['global-compress']: { - compress: true - } - } - } - }; - return { - status: 200, - body: { - ...traefik - } - }; - } else if (type === 'http') { - const service = await db.prisma.service.findFirst({ where: { id } }); - if (service?.fqdn) { - const domain = getDomain(service.fqdn); - const isWWW = domain.startsWith('www.'); - const traefik = { - [type]: { - routers: { - [id]: { - entrypoints: [type], - rule: isWWW - ? `Host(\`${domain}\`) || Host(\`www.${domain}\`)` - : `Host(\`${domain}\`)`, - service: id - } - }, - services: { - [id]: { - loadbalancer: { - servers: [{ url: `http://${id}:${privatePort}` }] - } - } - }, - middlewares: { - ['global-compress']: { - compress: true - } - } - } - }; - return { - status: 200, - body: { - ...traefik - } - }; - } - } - } - return { - status: 500 - }; - } else { - const applications = await db.prisma.application.findMany({ - include: { destinationDocker: true, settings: true } - }); - const data = { - applications: [], - services: [], - coolify: [] - }; - for (const application of applications) { - const { - fqdn, - id, - port, - destinationDocker, - destinationDockerId, - settings: { previews, dualCerts } - } = application; - if (destinationDockerId) { - const { engine, network } = destinationDocker; - const isRunning = await checkContainer(engine, id); - if (fqdn) { - const domain = getDomain(fqdn); - const nakedDomain = domain.replace(/^www\./, ''); - const isHttps = fqdn.startsWith('https://'); - const isWWW = fqdn.includes('www.'); - if (isRunning) { - data.applications.push({ - id, - port: port || 3000, - domain, - nakedDomain, - isRunning, - isHttps, - isWWW, - isDualCerts: dualCerts - }); - } - if (previews) { - const host = getEngine(engine); - const { stdout } = await asyncExecShell( - `DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` - ); - const containers = stdout - .trim() - .split('\n') - .filter((a) => a) - .map((c) => c.replace(/"/g, '')); - if (containers.length > 0) { - for (const container of containers) { - const previewDomain = `${container.split('-')[1]}.${domain}`; - data.applications.push({ - id: container, - port: port || 3000, - domain: previewDomain, - isRunning, - isHttps, - isWWW - }); - } - } - } - } - } - } - const services = await listServicesWithIncludes(); - - for (const service of services) { - const { - fqdn, - id, - type, - dualCerts, - destinationDocker, - destinationDockerId, - plausibleAnalytics - } = service; - if (destinationDockerId) { - const { engine } = destinationDocker; - const found = supportedServiceTypesAndVersions.find((a) => a.name === type); - if (found) { - const port = found.ports.main; - const publicPort = service[type]?.publicPort; - const isRunning = await checkContainer(engine, id); - if (fqdn) { - const domain = getDomain(fqdn); - const nakedDomain = domain.replace(/^www\./, ''); - const isHttps = fqdn.startsWith('https://'); - const isWWW = fqdn.includes('www.'); - if (isRunning) { - // Plausible Analytics custom script - let scriptName = false; - if ( - type === 'plausibleanalytics' && - plausibleAnalytics.scriptName !== 'plausible.js' - ) { - scriptName = plausibleAnalytics.scriptName; - } - data.services.push({ - id, - port, - publicPort, - domain, - nakedDomain, - isRunning, - isHttps, - isWWW, - isDualCerts: dualCerts, - scriptName - }); - } - } - } - } - } - - const { fqdn, dualCerts } = await db.prisma.setting.findFirst(); - if (fqdn) { - const domain = getDomain(fqdn); - const nakedDomain = domain.replace(/^www\./, ''); - const isHttps = fqdn.startsWith('https://'); - const isWWW = fqdn.includes('www.'); - data.coolify.push({ - id: dev ? 'host.docker.internal' : 'coolify', - port: 3000, - domain, - nakedDomain, - isHttps, - isWWW, - isDualCerts: dualCerts - }); - } - const traefik = { - http: { - routers: {}, - services: {}, - middlewares: { - ['global-compress']: { - compress: true - }, - ['https-redirect-non-www-to-www']: { - redirectregex: { - regex: '^https://(?:www\\.)?(.+)', - replacement: 'https://www.${1}', - permanent: dev ? false : true - } - }, - ['http-redirect-non-www-to-www']: { - redirectregex: { - regex: '^http://(?:www\\.)?(.+)', - replacement: 'http://www.${1}', - permanent: dev ? false : true - } - }, - ['https-redirect-www-to-non-www']: { - redirectregex: { - regex: '^https?://www\\.(.+)', - replacement: 'https://${1}', - permanent: dev ? false : true - } - }, - ['http-redirect-www-to-non-www']: { - redirectregex: { - regex: '^http?://www\\.(.+)', - replacement: 'http://${1}', - permanent: dev ? false : true - } - }, - ['http-to-https']: { - redirectregex: { - regex: '^http?://(.+)', - replacement: 'https://${1}', - permanent: dev ? false : true - } - }, - ['https-to-http']: { - redirectregex: { - regex: '^https?://(.+)', - replacement: 'http://${1}', - permanent: dev ? false : true - } - }, - ['https-http']: { - redirectscheme: { - scheme: 'http', - permanent: false - } - } - } - } - }; - for (const application of data.applications) { - const { id, port, domain, nakedDomain, isHttps, isWWW, isDualCerts } = application; - if (isHttps) { - traefik.http.routers[id] = { - entrypoints: ['web'], - rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, - middlewares: ['http-to-https'], - service: id - }; - traefik.http.routers[`${id}-secure`] = { - entrypoints: ['websecure'], - rule: isWWW - ? isDualCerts - ? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)` - : `Host(\`${nakedDomain}\`)` - : `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, - service: id - }; - } else { - traefik.http.routers[id] = { - entrypoints: ['web'], - rule: isWWW - ? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)` - : `Host(\`${nakedDomain}\`)`, - service: id - }; - traefik.http.routers[`${id}-secure`] = { - entrypoints: ['websecure'], - rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, - middlewares: ['https-http'], - service: id - }; - } - - traefik.http.services[id] = { - loadbalancer: { - servers: [ - { - url: `http://${id}:${port}` - } - ] - } - }; - if (isHttps && !dev) { - traefik.http.routers[id].tls = { - certresolver: 'letsencrypt' - }; - } - generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik }); - } - for (const service of data.services) { - const { id, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName } = service; - - traefik.http.routers[id] = { - entrypoints: isHttps ? ['web', 'websecure'] : ['web'], - rule: isWWW - ? isDualCerts - ? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)` - : `Host(\`${nakedDomain}\`)` - : `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, - service: id - }; - traefik.http.services[id] = { - loadbalancer: { - servers: [ - { - url: `http://${id}:${port}` - } - ] - } - }; - if (isHttps && !dev) { - traefik.http.routers[id].tls = { - certresolver: 'letsencrypt' - }; - } - if (scriptName) { - if (!traefik.http.middlewares) traefik.http.middlewares = {}; - traefik.http.middlewares[`${id}-redir`] = { - replacepathregex: { - regex: `/js/${scriptName}`, - replacement: '/js/plausible.js', - permanent: false - } - }; - traefik.http.routers[id].middlewares = [`${id}-redir`]; - } - generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik }); - } - for (const coolify of data.coolify) { - const { nakedDomain, domain, id, port, isHttps, isWWW, isDualCerts } = coolify; - traefik.http.routers['coolify'] = { - entrypoints: isHttps ? ['web', 'websecure'] : ['web'], - rule: isWWW - ? isDualCerts - ? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)` - : `Host(\`${nakedDomain}\`)` - : `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, - service: id - }; - traefik.http.services[id] = { - loadbalancer: { - servers: [ - { - url: `http://${id}:${port}` - } - ] - } - }; - if (isHttps && !dev) { - traefik.http.routers[id].tls = { - certresolver: 'letsencrypt' - }; - } - generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik }); - } - - return { - status: 200, - body: { - ...traefik - } - }; - } -}; diff --git a/src/routes/webhooks/traefik/main.json.ts b/src/routes/webhooks/traefik/main.json.ts new file mode 100644 index 000000000..b0fee9cc7 --- /dev/null +++ b/src/routes/webhooks/traefik/main.json.ts @@ -0,0 +1,283 @@ +import { dev } from '$app/env'; +import { asyncExecShell, getDomain, getEngine } from '$lib/common'; +import { supportedServiceTypesAndVersions } from '$lib/components/common'; +import * as db from '$lib/database'; +import { listServicesWithIncludes } from '$lib/database'; +import { checkContainer } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +const traefik = { + http: { + routers: {}, + services: {}, + middlewares: { + 'redirect-to-https': { + redirectscheme: { + scheme: 'https' + } + }, + 'redirect-to-http': { + redirectscheme: { + scheme: 'http' + } + }, + 'redirect-to-non-www': { + redirectregex: { + regex: '^https?://www\\.(.+)', + replacement: 'http://${1}' + } + }, + 'redirect-to-www': { + redirectregex: { + regex: '^https?://(?:www\\.)?(.+)', + replacement: 'http://www.${1}' + } + } + } + } +}; + +function configureMiddleware({ id, port, nakedDomain, isHttps, isWWW, isDualCerts }) { + if (isHttps) { + traefik.http.routers[id] = { + entrypoints: ['web'], + rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, + service: `${id}`, + middlewares: ['redirect-to-https'] + }; + + traefik.http.routers[`${id}-secure`] = { + entrypoints: ['websecure'], + rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, + service: `${id}`, + tls: { + certresolver: 'letsencrypt' + }, + middlewares: [] + }; + + traefik.http.services[id] = { + loadbalancer: { + servers: [ + { + url: `http://${id}:${port}` + } + ] + } + }; + + if (!isDualCerts) { + if (isWWW) { + traefik.http.routers[`${id}`].middlewares.push('redirect-to-www'); + traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-www'); + } else { + traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www'); + traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-non-www'); + } + } + } else { + traefik.http.routers[id] = { + entrypoints: ['web'], + rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, + service: `${id}`, + middlewares: [] + }; + + traefik.http.routers[`${id}-secure`] = { + entrypoints: ['websecure'], + rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, + service: `${id}`, + tls: { + domains: { + main: `${nakedDomain}` + } + }, + middlewares: ['redirect-to-http'] + }; + + traefik.http.services[id] = { + loadbalancer: { + servers: [ + { + url: `http://${id}:${port}` + } + ] + } + }; + + if (!isDualCerts) { + if (isWWW) { + traefik.http.routers[`${id}`].middlewares.push('redirect-to-www'); + traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-www'); + } else { + traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www'); + traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-non-www'); + } + } + } +} +export const get: RequestHandler = async (event) => { + const applications = await db.prisma.application.findMany({ + include: { destinationDocker: true, settings: true } + }); + const data = { + applications: [], + services: [], + coolify: [] + }; + for (const application of applications) { + const { + fqdn, + id, + port, + destinationDocker, + destinationDockerId, + settings: { previews, dualCerts }, + updatedAt + } = application; + if (destinationDockerId) { + const { engine, network } = destinationDocker; + const isRunning = await checkContainer(engine, id); + if (fqdn) { + const domain = getDomain(fqdn); + const nakedDomain = domain.replace(/^www\./, ''); + const isHttps = fqdn.startsWith('https://'); + const isWWW = fqdn.includes('www.'); + if (isRunning) { + data.applications.push({ + id, + port: port || 3000, + domain, + nakedDomain, + isRunning, + isHttps, + isWWW, + isDualCerts: dualCerts + }); + } + if (previews) { + const host = getEngine(engine); + const { stdout } = await asyncExecShell( + `DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` + ); + const containers = stdout + .trim() + .split('\n') + .filter((a) => a) + .map((c) => c.replace(/"/g, '')); + if (containers.length > 0) { + for (const container of containers) { + const previewDomain = `${container.split('-')[1]}.${domain}`; + data.applications.push({ + id: container, + port: port || 3000, + domain: previewDomain, + isRunning, + isHttps, + redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain, + updatedAt: updatedAt.getTime() + }); + } + } + } + } + } + } + const services = await listServicesWithIncludes(); + + for (const service of services) { + const { + fqdn, + id, + type, + destinationDocker, + destinationDockerId, + updatedAt, + dualCerts, + plausibleAnalytics + } = service; + if (destinationDockerId) { + const { engine } = destinationDocker; + const found = supportedServiceTypesAndVersions.find((a) => a.name === type); + if (found) { + const port = found.ports.main; + const publicPort = service[type]?.publicPort; + const isRunning = await checkContainer(engine, id); + if (fqdn) { + const domain = getDomain(fqdn); + const nakedDomain = domain.replace(/^www\./, ''); + const isHttps = fqdn.startsWith('https://'); + const isWWW = fqdn.includes('www.'); + if (isRunning) { + // Plausible Analytics custom script + let scriptName = false; + if (type === 'plausibleanalytics' && plausibleAnalytics.scriptName !== 'plausible.js') { + scriptName = plausibleAnalytics.scriptName; + } + + data.services.push({ + id, + port, + publicPort, + domain, + nakedDomain, + isRunning, + isHttps, + isWWW, + isDualCerts: dualCerts, + scriptName + }); + } + } + } + } + } + + const { fqdn, dualCerts } = await db.prisma.setting.findFirst(); + if (fqdn) { + const domain = getDomain(fqdn); + const nakedDomain = domain.replace(/^www\./, ''); + const isHttps = fqdn.startsWith('https://'); + const isWWW = fqdn.includes('www.'); + data.coolify.push({ + id: dev ? 'host.docker.internal' : 'coolify', + port: 3000, + domain, + nakedDomain, + isHttps, + isWWW, + isDualCerts: dualCerts + }); + } + for (const application of data.applications) { + configureMiddleware(application); + } + for (const service of data.services) { + const { id, scriptName } = service; + configureMiddleware(service); + + if (scriptName) { + traefik.http.middlewares[`${id}-redir`] = { + replacepathregex: { + regex: `/js/${scriptName}`, + replacement: '/js/plausible.js' + } + }; + if (traefik.http.routers[id].middlewares.length > 0) { + traefik.http.routers[id].middlewares.push(`${id}-redir`); + } else { + traefik.http.routers[id].middlewares = [`${id}-redir`]; + } + } + } + for (const coolify of data.coolify) { + configureMiddleware(coolify); + } + + return { + status: 200, + body: { + ...traefik + } + }; +}; diff --git a/src/routes/webhooks/traefik/other.json.ts b/src/routes/webhooks/traefik/other.json.ts new file mode 100644 index 000000000..f2a0a113b --- /dev/null +++ b/src/routes/webhooks/traefik/other.json.ts @@ -0,0 +1,76 @@ +import { dev } from '$app/env'; +import { asyncExecShell, getDomain, getEngine } from '$lib/common'; +import { supportedServiceTypesAndVersions } from '$lib/components/common'; +import * as db from '$lib/database'; +import { listServicesWithIncludes } from '$lib/database'; +import { checkContainer } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const id = event.url.searchParams.get('id'); + if (id) { + const privatePort = event.url.searchParams.get('privatePort'); + const publicPort = event.url.searchParams.get('publicPort'); + const type = event.url.searchParams.get('type'); + let traefik = {}; + if (publicPort) { + if (type === 'tcp') { + traefik = { + [type]: { + routers: { + [id]: { + entrypoints: [type], + rule: `HostSNI(\`*\`)`, + service: id + } + }, + services: { + [id]: { + loadbalancer: { + servers: [] + } + } + } + } + }; + } else if (type === 'http') { + const service = await db.prisma.service.findFirst({ where: { id } }); + if (service?.fqdn) { + const domain = getDomain(service.fqdn); + traefik = { + [type]: { + routers: { + [id]: { + entrypoints: [type], + rule: `Host(\`${domain}\`)`, + service: id + } + }, + services: { + [id]: { + loadbalancer: { + servers: [] + } + } + } + } + }; + } + } + } + if (type === 'tcp') { + traefik[type].services[id].loadbalancer.servers.push({ address: `${id}:${privatePort}` }); + } else if (type === 'http') { + traefik[type].services[id].loadbalancer.servers.push({ url: `http://${id}:${privatePort}` }); + } + return { + status: 200, + body: { + ...traefik + } + }; + } + return { + status: 500 + }; +}; From 1ec620be4bc90c7498b49c0cb8c00f07f909bdc5 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 16 May 2022 23:56:54 +0200 Subject: [PATCH 15/44] WIP: Traefik --- package.json | 2 +- src/app.html | 1 - src/lib/haproxy/index.ts | 2 +- src/routes/__layout.svelte | 2 + src/routes/webhooks/traefik/main.json.ts | 66 +++++++++++++++++++----- 5 files changed, 56 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 782e43f09..96d9a64a4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "2.8.2", + "version": "2.9.0", "license": "AGPL-3.0", "scripts": { "dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0", diff --git a/src/app.html b/src/app.html index a0336e87d..ab9a75726 100644 --- a/src/app.html +++ b/src/app.html @@ -3,7 +3,6 @@ - Coolify %svelte.head% diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index d0a6cb804..d2424589c 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -369,7 +369,7 @@ export async function startTraefikProxy(engine: string): Promise { --certificatesresolvers.letsencrypt.acme.httpchallenge=true \ --certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \ --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \ - --log.level=debug` + --log.level=error` ); await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } }); await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true }); diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index 0f0843f8f..0dceeb9af 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -133,6 +133,8 @@ Coolify {#if !$session.whiteLabeled} + {:else if $session.whiteLabelDetails.icon} + {/if} diff --git a/src/routes/webhooks/traefik/main.json.ts b/src/routes/webhooks/traefik/main.json.ts index b0fee9cc7..715c50314 100644 --- a/src/routes/webhooks/traefik/main.json.ts +++ b/src/routes/webhooks/traefik/main.json.ts @@ -37,7 +37,7 @@ const traefik = { } }; -function configureMiddleware({ id, port, nakedDomain, isHttps, isWWW, isDualCerts }) { +function configureMiddleware({ id, port, domain, nakedDomain, isHttps, isWWW, isDualCerts }) { if (isHttps) { traefik.http.routers[id] = { entrypoints: ['web'], @@ -46,16 +46,6 @@ function configureMiddleware({ id, port, nakedDomain, isHttps, isWWW, isDualCert middlewares: ['redirect-to-https'] }; - traefik.http.routers[`${id}-secure`] = { - entrypoints: ['websecure'], - rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, - service: `${id}`, - tls: { - certresolver: 'letsencrypt' - }, - middlewares: [] - }; - traefik.http.services[id] = { loadbalancer: { servers: [ @@ -66,13 +56,61 @@ function configureMiddleware({ id, port, nakedDomain, isHttps, isWWW, isDualCert } }; - if (!isDualCerts) { + if (isDualCerts) { + traefik.http.routers[`${id}-secure`] = { + entrypoints: ['websecure'], + rule: `Host(\`${domain}\`) || Host(\`www.${nakedDomain}\`)`, + service: `${id}`, + tls: { + certresolver: 'letsencrypt' + }, + middlewares: [] + }; + } else { if (isWWW) { + traefik.http.routers[`${id}-secure-www`] = { + entrypoints: ['websecure'], + rule: `Host(\`www.${nakedDomain}\`)`, + service: `${id}`, + tls: { + certresolver: 'letsencrypt' + }, + middlewares: [] + }; + traefik.http.routers[`${id}-secure`] = { + entrypoints: ['websecure'], + rule: `Host(\`${nakedDomain}\`)`, + service: `${id}`, + tls: { + domains: { + main: `${domain}` + } + }, + middlewares: ['redirect-to-www'] + }; traefik.http.routers[`${id}`].middlewares.push('redirect-to-www'); - traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-www'); } else { + traefik.http.routers[`${id}-secure-www`] = { + entrypoints: ['websecure'], + rule: `Host(\`www.${nakedDomain}\`)`, + service: `${id}`, + tls: { + domains: { + main: `${domain}` + } + }, + middlewares: ['redirect-to-non-www'] + }; + traefik.http.routers[`${id}-secure`] = { + entrypoints: ['websecure'], + rule: `Host(\`${domain}\`)`, + service: `${id}`, + tls: { + certresolver: 'letsencrypt' + }, + middlewares: [] + }; traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www'); - traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-non-www'); } } } else { From 35b31dce2b55afce0474fc4e6d949f8321874542 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 17 May 2022 10:14:06 +0200 Subject: [PATCH 16/44] WIP: Notifications and application usage --- package.json | 12 +- pnpm-lock.yaml | 103 +++++++++------- .../migration.sql | 2 +- .../migration.sql | 10 ++ prisma/schema.prisma | 12 +- src/lib/haproxy/index.ts | 15 +++ src/lib/store.ts | 9 ++ src/routes/__layout.svelte | 3 + src/routes/applications/[id]/__layout.svelte | 110 +++++++++++------- src/routes/applications/[id]/index.json.ts | 13 +-- src/routes/applications/[id]/index.svelte | 82 ++++++++++--- src/routes/applications/[id]/status.json.ts | 36 ++++++ src/routes/applications/[id]/usage.json.ts | 31 +++++ src/routes/notifications.json.ts | 30 +++++ src/routes/webhooks/traefik/other.json.ts | 9 +- 15 files changed, 349 insertions(+), 128 deletions(-) rename prisma/migrations/{20220513154929_traefik => 20220517081328_traefik}/migration.sql (95%) create mode 100644 prisma/migrations/20220517081338_notifications/migration.sql create mode 100644 src/routes/applications/[id]/status.json.ts create mode 100644 src/routes/applications/[id]/usage.json.ts create mode 100644 src/routes/notifications.json.ts diff --git a/package.json b/package.json index 96d9a64a4..1f766bd18 100644 --- a/package.json +++ b/package.json @@ -30,11 +30,11 @@ }, "devDependencies": { "@sveltejs/adapter-node": "1.0.0-next.73", - "@sveltejs/adapter-static": "1.0.0-next.29", - "@sveltejs/kit": "1.0.0-next.326", + "@sveltejs/adapter-static": "1.0.0-next.31", + "@sveltejs/kit": "1.0.0-next.334", "@types/js-cookie": "3.0.2", "@types/js-yaml": "4.0.5", - "@types/node": "17.0.31", + "@types/node": "17.0.34", "@types/node-forge": "1.0.2", "@typescript-eslint/eslint-plugin": "4.31.1", "@typescript-eslint/parser": "4.31.1", @@ -50,10 +50,10 @@ "postcss": "8.4.13", "prettier": "2.6.2", "prettier-plugin-svelte": "2.7.0", - "prettier-plugin-tailwindcss": "0.1.10", + "prettier-plugin-tailwindcss": "0.1.11", "prisma": "3.11.1", "svelte": "3.48.0", - "svelte-check": "2.7.0", + "svelte-check": "2.7.1", "svelte-preprocess": "4.10.6", "svelte-select": "4.4.7", "sveltekit-i18n": "2.2.1", @@ -68,7 +68,7 @@ "@prisma/client": "3.11.1", "@sentry/node": "6.19.7", "bcryptjs": "2.4.3", - "bullmq": "1.81.4", + "bullmq": "1.82.2", "compare-versions": "4.1.3", "cookie": "0.5.0", "cuid": "2.1.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28b0ea786..b626b4fbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,18 +5,18 @@ specifiers: '@prisma/client': 3.11.1 '@sentry/node': 6.19.7 '@sveltejs/adapter-node': 1.0.0-next.73 - '@sveltejs/adapter-static': 1.0.0-next.29 - '@sveltejs/kit': 1.0.0-next.326 + '@sveltejs/adapter-static': 1.0.0-next.31 + '@sveltejs/kit': 1.0.0-next.334 '@types/js-cookie': 3.0.2 '@types/js-yaml': 4.0.5 - '@types/node': 17.0.31 + '@types/node': 17.0.34 '@types/node-forge': 1.0.2 '@typescript-eslint/eslint-plugin': 4.31.1 '@typescript-eslint/parser': 4.31.1 '@zerodevx/svelte-toast': 0.7.1 autoprefixer: 10.4.7 bcryptjs: 2.4.3 - bullmq: 1.81.4 + bullmq: 1.82.2 compare-versions: 4.1.3 cookie: 0.5.0 cross-env: 7.0.3 @@ -44,10 +44,10 @@ specifiers: postcss: 8.4.13 prettier: 2.6.2 prettier-plugin-svelte: 2.7.0 - prettier-plugin-tailwindcss: 0.1.10 + prettier-plugin-tailwindcss: 0.1.11 prisma: 3.11.1 svelte: 3.48.0 - svelte-check: 2.7.0 + svelte-check: 2.7.1 svelte-kit-cookie-session: 2.1.4 svelte-preprocess: 4.10.6 svelte-select: 4.4.7 @@ -64,7 +64,7 @@ dependencies: '@prisma/client': 3.11.1_prisma@3.11.1 '@sentry/node': 6.19.7 bcryptjs: 2.4.3 - bullmq: 1.81.4 + bullmq: 1.82.2 compare-versions: 4.1.3 cookie: 0.5.0 cuid: 2.1.8 @@ -88,11 +88,11 @@ dependencies: devDependencies: '@sveltejs/adapter-node': 1.0.0-next.73 - '@sveltejs/adapter-static': 1.0.0-next.29 - '@sveltejs/kit': 1.0.0-next.326_svelte@3.48.0 + '@sveltejs/adapter-static': 1.0.0-next.31 + '@sveltejs/kit': 1.0.0-next.334_svelte@3.48.0 '@types/js-cookie': 3.0.2 '@types/js-yaml': 4.0.5 - '@types/node': 17.0.31 + '@types/node': 17.0.34 '@types/node-forge': 1.0.2 '@typescript-eslint/eslint-plugin': 4.31.1_lii63oz3usekbu5ehvrcuwn5jy '@typescript-eslint/parser': 4.31.1_e4zyhrvfnqudwdx5bevnvkluy4 @@ -108,15 +108,15 @@ devDependencies: postcss: 8.4.13 prettier: 2.6.2 prettier-plugin-svelte: 2.7.0_kkjbqzpydplecjtkxrgomroeru - prettier-plugin-tailwindcss: 0.1.10_prettier@2.6.2 + prettier-plugin-tailwindcss: 0.1.11_prettier@2.6.2 prisma: 3.11.1 svelte: 3.48.0 - svelte-check: 2.7.0_f2ke6qjyzu5axsjd6yk3u4tn7a + svelte-check: 2.7.1_f2ke6qjyzu5axsjd6yk3u4tn7a svelte-preprocess: 4.10.6_nq4dx2skq5drra53vttuo4lltu svelte-select: 4.4.7 sveltekit-i18n: 2.2.1_svelte@3.48.0 tailwindcss: 3.0.24_ts-node@10.7.0 - ts-node: 10.7.0_l47be6km5p57gglrggidw5gsgm + ts-node: 10.7.0_3smuweqyuzdazdnyhhezld6mfa tslib: 2.4.0 typescript: 4.6.4 @@ -216,6 +216,31 @@ packages: } dev: false + /@jridgewell/resolve-uri/3.0.7: + resolution: + { + integrity: sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== + } + engines: { node: '>=6.0.0' } + dev: true + + /@jridgewell/sourcemap-codec/1.4.13: + resolution: + { + integrity: sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== + } + dev: true + + /@jridgewell/trace-mapping/0.3.13: + resolution: + { + integrity: sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== + } + dependencies: + '@jridgewell/resolve-uri': 3.0.7 + '@jridgewell/sourcemap-codec': 1.4.13 + dev: true + /@nodelib/fs.scandir/2.1.5: resolution: { @@ -382,21 +407,21 @@ packages: tiny-glob: 0.2.9 dev: true - /@sveltejs/adapter-static/1.0.0-next.29: + /@sveltejs/adapter-static/1.0.0-next.31: resolution: { - integrity: sha512-0hjGnfT3BRyoHnzJ2w0/xL+xICRpKneDTm45ZzggiRrc0r71WJfF6toGeg8N4QUQnj8EJ3Itm453gsS1kt7VUQ== + integrity: sha512-d9RNA/de5ljb+gN8mKA3YfmfJoTbYFdH96NYDD8u4Lu9O/ZnseUxOAcAmD4/LKbLXOY/oYhRpt029xT2owyI3Q== } dependencies: tiny-glob: 0.2.9 dev: true - /@sveltejs/kit/1.0.0-next.326_svelte@3.48.0: + /@sveltejs/kit/1.0.0-next.334_svelte@3.48.0: resolution: { - integrity: sha512-prJqmXZ2H1wmFfnMw7wDujfbkcA8vuubuqUkpVVmXhfh2+SEzQscPTNwxoE5EJxb5sywtLWEvYx3hv1gPS4Lvg== + integrity: sha512-HPMF1oYBfyOG6wfU0Y6F4SID8jphue9yF+PXJqVTDBL5Z2WBG2ogum6MavE8aWhq+g2H6w5y0jNT8+8DO2KTCA== } - engines: { node: '>=14.13' } + engines: { node: '>=16' } hasBin: true peerDependencies: svelte: ^3.44.0 @@ -506,7 +531,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.3 - '@types/node': 17.0.31 + '@types/node': 17.0.34 '@types/responselike': 1.0.0 dev: false @@ -544,7 +569,7 @@ packages: integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg== } dependencies: - '@types/node': 17.0.31 + '@types/node': 17.0.34 dev: false /@types/node-forge/1.0.2: @@ -553,13 +578,13 @@ packages: integrity: sha512-J1OkeZGaW0y9Y7xD49Ja8O82B9l5nZDeoYuGWqIOYPAf9LR+xF23k9ILdzv8dH+2H033fx3D5oiA0GlmicI+sg== } dependencies: - '@types/node': 17.0.31 + '@types/node': 17.0.34 dev: true - /@types/node/17.0.31: + /@types/node/17.0.34: resolution: { - integrity: sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q== + integrity: sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA== } /@types/pug/2.0.5: @@ -575,7 +600,7 @@ packages: integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== } dependencies: - '@types/node': 17.0.31 + '@types/node': 17.0.34 dev: false /@types/sass/1.16.1: @@ -584,7 +609,7 @@ packages: integrity: sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ== } dependencies: - '@types/node': 17.0.31 + '@types/node': 17.0.34 dev: true /@typescript-eslint/eslint-plugin/4.31.1_lii63oz3usekbu5ehvrcuwn5jy: @@ -1700,10 +1725,10 @@ packages: ieee754: 1.2.1 dev: false - /bullmq/1.81.4: + /bullmq/1.82.2: resolution: { - integrity: sha512-sUEWOMKZnWlh1/XNqYAoSwXW6P8nZN7uJiHKZ8XlZCiIxWlEGjFtlugkkiCZ0lsTI2nNRHdxfpn78x9K3L1utQ== + integrity: sha512-pDmMl6HmL/7B41ldBK4lnmGUcobkI/n/a0T3d/volMWC0ULxsaZ6R6fDePk23LwH9Fxu4o9Ny+zurCL3vG7lbg== } dependencies: cron-parser: 4.2.1 @@ -4161,7 +4186,7 @@ packages: dependencies: lilconfig: 2.0.5 postcss: 8.4.13 - ts-node: 10.7.0_l47be6km5p57gglrggidw5gsgm + ts-node: 10.7.0_3smuweqyuzdazdnyhhezld6mfa yaml: 1.10.2 dev: true @@ -4229,10 +4254,10 @@ packages: svelte: 3.48.0 dev: true - /prettier-plugin-tailwindcss/0.1.10_prettier@2.6.2: + /prettier-plugin-tailwindcss/0.1.11_prettier@2.6.2: resolution: { - integrity: sha512-ooDGNuXUjgCXfShliVYQ6+0iXqUFXn+zdNInPe0WZN9qINt9srbLGFGY5jeVL4MXtY20/4S8JaBcd8l6N6NfCQ== + integrity: sha512-a28+1jvpIZQdZ/W97wOXb6VqI762MKE/TxMMuibMEHhyYsSxQA8Ek30KObd5kJI2HF1ldtSYprFayXJXi3pz8Q== } engines: { node: '>=12.17.0' } peerDependencies: @@ -4719,14 +4744,6 @@ packages: engines: { node: '>=0.10.0' } dev: true - /source-map/0.7.3: - resolution: - { - integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - } - engines: { node: '>= 8' } - dev: true - /sourcemap-codec/1.4.8: resolution: { @@ -4899,21 +4916,21 @@ packages: engines: { node: '>= 0.4' } dev: true - /svelte-check/2.7.0_f2ke6qjyzu5axsjd6yk3u4tn7a: + /svelte-check/2.7.1_f2ke6qjyzu5axsjd6yk3u4tn7a: resolution: { - integrity: sha512-GrvG24j0+i8AOm0k0KyJ6Dqc+TAR2yzB7rtS4nljHStunVxCTr/1KYlv4EsOeoqtHLzeWMOd5D2O6nDdP/yw4A== + integrity: sha512-vHVu2+SQ6ibt77iTQaq2oiOjBgGL48qqcg0ZdEOsP5pPOjgeyR9QbnaEdzdBs9nsVYBc/42haKtzb2uFqS8GVw== } hasBin: true peerDependencies: svelte: ^3.24.0 dependencies: + '@jridgewell/trace-mapping': 0.3.13 chokidar: 3.5.3 fast-glob: 3.2.11 import-fresh: 3.3.0 picocolors: 1.0.0 sade: 1.7.4 - source-map: 0.7.3 svelte: 3.48.0 svelte-preprocess: 4.10.6_nq4dx2skq5drra53vttuo4lltu typescript: 4.6.4 @@ -5154,7 +5171,7 @@ packages: engines: { node: '>=0.10.0' } dev: true - /ts-node/10.7.0_l47be6km5p57gglrggidw5gsgm: + /ts-node/10.7.0_3smuweqyuzdazdnyhhezld6mfa: resolution: { integrity: sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== @@ -5176,7 +5193,7 @@ packages: '@tsconfig/node12': 1.0.9 '@tsconfig/node14': 1.0.1 '@tsconfig/node16': 1.0.2 - '@types/node': 17.0.31 + '@types/node': 17.0.34 acorn: 8.5.0 acorn-walk: 8.2.0 arg: 4.1.3 diff --git a/prisma/migrations/20220513154929_traefik/migration.sql b/prisma/migrations/20220517081328_traefik/migration.sql similarity index 95% rename from prisma/migrations/20220513154929_traefik/migration.sql rename to prisma/migrations/20220517081328_traefik/migration.sql index 164bed387..a83281fa4 100644 --- a/prisma/migrations/20220513154929_traefik/migration.sql +++ b/prisma/migrations/20220517081328_traefik/migration.sql @@ -12,7 +12,7 @@ CREATE TABLE "new_Setting" ( "proxyHash" TEXT, "isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false, "isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true, - "isTraefikUsed" BOOLEAN NOT NULL DEFAULT false, + "isTraefikUsed" BOOLEAN NOT NULL DEFAULT true, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" DATETIME NOT NULL ); diff --git a/prisma/migrations/20220517081338_notifications/migration.sql b/prisma/migrations/20220517081338_notifications/migration.sql new file mode 100644 index 000000000..069f938d5 --- /dev/null +++ b/prisma/migrations/20220517081338_notifications/migration.sql @@ -0,0 +1,10 @@ +-- CreateTable +CREATE TABLE "Notification" ( + "id" TEXT NOT NULL PRIMARY KEY, + "type" TEXT NOT NULL, + "message" TEXT NOT NULL, + "isRead" BOOLEAN NOT NULL DEFAULT false, + "showAtVersion" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a20b3455c..97d092331 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,11 +20,21 @@ model Setting { proxyHash String? isAutoUpdateEnabled Boolean @default(false) isDNSCheckEnabled Boolean @default(true) - isTraefikUsed Boolean @default(false) + isTraefikUsed Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } +model Notification { + id String @id @default(cuid()) + type String + message String + isRead Boolean @default(false) + showAtVersion String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + model User { id String @id @unique @default(cuid()) email String @unique diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index d2424589c..5af082efe 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -421,6 +421,21 @@ export async function checkContainer( return containerFound; } +export async function getContainerUsage(engine: string, container: string): Promise { + const host = getEngine(engine); + try { + const { stdout } = await asyncExecShell( + `DOCKER_HOST="${host}" docker container stats ${container} --no-stream --no-trunc --format "{{json .}}"` + ); + return JSON.parse(stdout); + } catch (err) { + return { + MemUsage: 0, + CPUPerc: 0, + NetIO: 0 + }; + } +} export async function stopCoolifyProxy( engine: string ): Promise<{ stdout: string; stderr: string } | Error> { diff --git a/src/lib/store.ts b/src/lib/store.ts index 75b943b46..9366d70b2 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -14,3 +14,12 @@ export const features: Readable<{ latestVersion: string; beta: boolean }> = read }); export const isTraefikUsed: Writable = writable(false); + +export const status: Writable = writable({ + application: { + isRunning: false, + isExited: false, + loading: false, + initialLoading: true + } +}); diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index 0dceeb9af..95c80565c 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -80,6 +80,9 @@ } finally { } } + try { + const data = await get(`/notifications.json`); + } catch (error) {} } }); diff --git a/src/routes/applications/[id]/__layout.svelte b/src/routes/applications/[id]/__layout.svelte index cc8c19c19..db30336fa 100644 --- a/src/routes/applications/[id]/__layout.svelte +++ b/src/routes/applications/[id]/__layout.svelte @@ -17,7 +17,7 @@ const endpoint = `/applications/${params.id}.json`; const res = await fetch(endpoint); if (res.ok) { - let { application, isRunning, isExited, appId, githubToken, gitlabToken } = await res.json(); + let { application, appId, githubToken, gitlabToken } = await res.json(); if (!application || Object.entries(application).length === 0) { return { status: 302, @@ -45,13 +45,10 @@ return { props: { application, - isRunning, - isExited, githubToken, gitlabToken }, stuff: { - isRunning, application, appId } @@ -67,8 +64,6 @@ @@ -153,16 +154,16 @@ {#if loading} {:else} - {#if isExited} + {#if $status.application.isExited} {/if} - {#if isRunning} + {#if $status.application.initialLoading} + + {:else if $status.application.isRunning}
+
+
Application Usage
+
+
+
+
Used Memory / Memory Limit
+
+ {usage?.MemUsage} +
+
+ +
+
Used CPU
+
+ {usage?.CPUPerc} +
+
+ +
+
Network IO
+
+ {usage?.NetIO} +
+
+
+
+
@@ -365,10 +413,10 @@ >
!isRunning && changeSettings('dualCerts')} + on:click={() => !$status.application.isRunning && changeSettings('dualCerts')} />
{#if application.buildPack === 'python'} @@ -527,8 +575,8 @@
{ + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + let isRunning = false; + let isExited = false; + try { + const application = await db.getApplication({ id, teamId }); + if (application.destinationDockerId) { + [isRunning, isExited] = await Promise.all([ + checkContainer(application.destinationDocker.engine, id), + isContainerExited(application.destinationDocker.engine, id) + ]); + } + return { + status: 200, + body: { + isRunning, + isExited + }, + headers: {} + }; + } catch (error) { + console.log(error); + return ErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/usage.json.ts b/src/routes/applications/[id]/usage.json.ts new file mode 100644 index 000000000..2ff28e6ae --- /dev/null +++ b/src/routes/applications/[id]/usage.json.ts @@ -0,0 +1,31 @@ +import { asyncExecShell, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { ErrorHandler } from '$lib/database'; +import { checkContainer, getContainerUsage, isContainerExited } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; +import { setDefaultConfiguration } from '$lib/buildPacks/common'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + let usage = {}; + try { + const application = await db.getApplication({ id, teamId }); + if (application.destinationDockerId) { + [usage] = await Promise.all([getContainerUsage(application.destinationDocker.engine, id)]); + } + return { + status: 200, + body: { + usage + }, + headers: {} + }; + } catch (error) { + console.log(error); + return ErrorHandler(error); + } +}; diff --git a/src/routes/notifications.json.ts b/src/routes/notifications.json.ts new file mode 100644 index 000000000..840fe5118 --- /dev/null +++ b/src/routes/notifications.json.ts @@ -0,0 +1,30 @@ +import { ErrorHandler } from '$lib/database'; +import { version } from '$lib/common'; +import * as db from '$lib/database'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (request) => { + try { + const unreadNotifications = await db.prisma.notification.findMany({ + where: { isRead: false, showAtVersion: version } + }); + return { + status: 200, + body: { + ...unreadNotifications + } + }; + } catch (error) { + console.log(error); + return ErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { type, latestVersion } = await event.request.json(); + const settings = await db.prisma.setting.findFirst(); + + return { + status: 500 + }; +}; diff --git a/src/routes/webhooks/traefik/other.json.ts b/src/routes/webhooks/traefik/other.json.ts index f2a0a113b..ea99e770e 100644 --- a/src/routes/webhooks/traefik/other.json.ts +++ b/src/routes/webhooks/traefik/other.json.ts @@ -27,7 +27,7 @@ export const get: RequestHandler = async (event) => { services: { [id]: { loadbalancer: { - servers: [] + servers: [{ address: `${id}:${privatePort}` }] } } } @@ -49,7 +49,7 @@ export const get: RequestHandler = async (event) => { services: { [id]: { loadbalancer: { - servers: [] + servers: [{ url: `http://${id}:${privatePort}` }] } } } @@ -58,11 +58,6 @@ export const get: RequestHandler = async (event) => { } } } - if (type === 'tcp') { - traefik[type].services[id].loadbalancer.servers.push({ address: `${id}:${privatePort}` }); - } else if (type === 'http') { - traefik[type].services[id].loadbalancer.servers.push({ url: `http://${id}:${privatePort}` }); - } return { status: 200, body: { From dbf910ff38567db8aa5c6e26bd824f2769a1b576 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 17 May 2022 11:16:58 +0200 Subject: [PATCH 17/44] feat: PageLoader --- src/lib/components/PageLoader.svelte | 35 ++++++++++++++++++++ src/routes/__layout.svelte | 8 +++++ src/routes/applications/[id]/__layout.svelte | 1 + 3 files changed, 44 insertions(+) create mode 100644 src/lib/components/PageLoader.svelte diff --git a/src/lib/components/PageLoader.svelte b/src/lib/components/PageLoader.svelte new file mode 100644 index 000000000..edbbcd80c --- /dev/null +++ b/src/lib/components/PageLoader.svelte @@ -0,0 +1,35 @@ + + +
+
+
+ + diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index 95c80565c..39303e0ea 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -38,12 +38,15 @@ import '../tailwind.css'; import { SvelteToast, toast } from '@zerodevx/svelte-toast'; import { page, session } from '$app/stores'; + import { fade } from 'svelte/transition'; import { onMount } from 'svelte'; import { errorNotification } from '$lib/form'; import { asyncSleep } from '$lib/components/common'; import { del, get, post } from '$lib/api'; import { dev } from '$app/env'; import { features, isTraefikUsed } from '$lib/store'; + import { navigating } from '$app/stores'; + import PageLoader from '$lib/components/PageLoader.svelte'; $isTraefikUsed = settings?.isTraefikUsed || false; @@ -141,6 +144,11 @@ {/if} +{#if $navigating} +
+ +
+{/if} {#if $session.userId}
@@ -413,8 +412,8 @@ >
Exposed Port Date: Tue, 17 May 2022 15:54:11 +0200 Subject: [PATCH 20/44] fixes --- src/lib/haproxy/index.ts | 12 ++++++++++-- src/routes/update.json.ts | 7 +------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index 5af082efe..dcd2d054d 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -166,13 +166,17 @@ export async function startTraefikTCPProxy( ports: [`${publicPort}:${publicPort}`], extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`], volumes: ['/var/run/docker.sock:/var/run/docker.sock'], - networks: [network] + networks: ['coolify-infra', network] } }, networks: { [network]: { external: false, name: network + }, + 'coolify-infra': { + external: false, + name: 'coolify-infra' } } }; @@ -262,13 +266,17 @@ export async function startTraefikHTTPProxy( ports: [`${publicPort}:${publicPort}`], extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`], volumes: ['/var/run/docker.sock:/var/run/docker.sock'], - networks: [network] + networks: ['coolify-infra', network] } }, networks: { [network]: { external: false, name: network + }, + 'coolify-infra': { + external: false, + name: 'coolify-infra' } } }; diff --git a/src/routes/update.json.ts b/src/routes/update.json.ts index 429030db1..ef5f0920b 100644 --- a/src/routes/update.json.ts +++ b/src/routes/update.json.ts @@ -6,12 +6,7 @@ import * as db from '$lib/database'; import type { RequestHandler } from '@sveltejs/kit'; import compare from 'compare-versions'; import got from 'got'; -import { - checkContainer, - configureNetworkTraefikProxy, - startCoolifyProxy, - startTraefikProxy -} from '$lib/haproxy'; +import { checkContainer, startCoolifyProxy, startTraefikProxy } from '$lib/haproxy'; export const get: RequestHandler = async (request) => { try { From dc3add495c2adf91e57f6d187d966baa637a08f6 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 18 May 2022 12:32:53 +0200 Subject: [PATCH 21/44] wip(fix): traefik --- src/lib/haproxy/index.ts | 21 ++--- src/lib/queues/proxyTcpHttp.ts | 13 ++-- src/routes/services/[id]/minio/start.json.ts | 2 - src/routes/webhooks/traefik/main.json.ts | 80 +++++++++++--------- src/routes/webhooks/traefik/other.json.ts | 6 +- 5 files changed, 64 insertions(+), 58 deletions(-) diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index dcd2d054d..2c21460c3 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -108,6 +108,7 @@ export async function checkHAProxy(haproxy?: Got): Promise { } export async function stopTcpHttpProxy( + id: string, destinationDocker: DestinationDocker, publicPort: number, forceName: string = null @@ -115,7 +116,7 @@ export async function stopTcpHttpProxy( const { engine } = destinationDocker; const host = getEngine(engine); const settings = await db.listSettings(); - let containerName = `proxy-for-${publicPort}`; + let containerName = `${id}-${publicPort}`; if (!settings.isTraefikUsed) { containerName = `haproxy-for-${publicPort}`; } @@ -141,7 +142,7 @@ export async function startTraefikTCPProxy( const { network, engine } = destinationDocker; const host = getEngine(engine); - const containerName = `proxy-for-${publicPort}`; + const containerName = `${id}-${publicPort}`; const found = await checkContainer(engine, containerName, true); const foundDependentContainer = await checkContainer(engine, id, true); @@ -154,8 +155,8 @@ export async function startTraefikTCPProxy( const tcpProxy = { version: '3.5', services: { - [id]: { - container_name: `proxy-for-${publicPort}`, + [`${id}-${publicPort}`]: { + container_name: containerName, image: 'traefik:v2.6', command: [ `--entrypoints.tcp.address=:${publicPort}`, @@ -241,7 +242,7 @@ export async function startTraefikHTTPProxy( const { network, engine } = destinationDocker; const host = getEngine(engine); - const containerName = `proxy-for-${publicPort}`; + const containerName = `${id}-${publicPort}`; const found = await checkContainer(engine, containerName, true); const foundDependentContainer = await checkContainer(engine, id, true); @@ -251,21 +252,21 @@ export async function startTraefikHTTPProxy( `DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'` ); const ip = JSON.parse(Config)[0].Gateway; + console.log({ privatePort, publicPort }); const tcpProxy = { version: '3.5', services: { - [id]: { - container_name: `proxy-for-${publicPort}`, + [`${id}-${publicPort}`]: { + container_name: containerName, image: 'traefik:v2.6', command: [ `--entrypoints.http.address=:${publicPort}`, `--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`, '--providers.http.pollTimeout=2s', - '--log.level=error' + '--log.level=debug' ], ports: [`${publicPort}:${publicPort}`], extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`], - volumes: ['/var/run/docker.sock:/var/run/docker.sock'], networks: ['coolify-infra', network] } }, @@ -377,7 +378,7 @@ export async function startTraefikProxy(engine: string): Promise { --certificatesresolvers.letsencrypt.acme.httpchallenge=true \ --certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \ --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \ - --log.level=error` + --log.level=debug` ); await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } }); await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true }); diff --git a/src/lib/queues/proxyTcpHttp.ts b/src/lib/queues/proxyTcpHttp.ts index e4ed9fe16..136798777 100644 --- a/src/lib/queues/proxyTcpHttp.ts +++ b/src/lib/queues/proxyTcpHttp.ts @@ -21,7 +21,7 @@ export default async function (): Promise { await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await db.updateMinioService({ id, publicPort }); - await startHttpProxy(destinationDocker, id, publicPort, apiPort); return { status: 200 }; diff --git a/src/routes/webhooks/traefik/main.json.ts b/src/routes/webhooks/traefik/main.json.ts index a5f1b9a55..e887a06d9 100644 --- a/src/routes/webhooks/traefik/main.json.ts +++ b/src/routes/webhooks/traefik/main.json.ts @@ -6,38 +6,10 @@ import { listServicesWithIncludes } from '$lib/database'; import { checkContainer } from '$lib/haproxy'; import type { RequestHandler } from '@sveltejs/kit'; -const traefik = { - http: { - routers: {}, - services: {}, - middlewares: { - 'redirect-to-https': { - redirectscheme: { - scheme: 'https' - } - }, - 'redirect-to-http': { - redirectscheme: { - scheme: 'http' - } - }, - 'redirect-to-non-www': { - redirectregex: { - regex: '^https?://www\\.(.+)', - replacement: 'http://${1}' - } - }, - 'redirect-to-www': { - redirectregex: { - regex: '^https?://(?:www\\.)?(.+)', - replacement: 'http://www.${1}' - } - } - } - } -}; - -function configureMiddleware({ id, port, domain, nakedDomain, isHttps, isWWW, isDualCerts }) { +function configureMiddleware( + { id, port, domain, nakedDomain, isHttps, isWWW, isDualCerts }, + traefik +) { if (isHttps) { traefik.http.routers[id] = { entrypoints: ['web'], @@ -155,6 +127,36 @@ function configureMiddleware({ id, port, domain, nakedDomain, isHttps, isWWW, is } } export const get: RequestHandler = async (event) => { + const traefik = { + http: { + routers: {}, + services: {}, + middlewares: { + 'redirect-to-https': { + redirectscheme: { + scheme: 'https' + } + }, + 'redirect-to-http': { + redirectscheme: { + scheme: 'http' + } + }, + 'redirect-to-non-www': { + redirectregex: { + regex: '^https?://www\\.(.+)', + replacement: 'http://${1}' + } + }, + 'redirect-to-www': { + redirectregex: { + regex: '^https?://(?:www\\.)?(.+)', + replacement: 'http://www.${1}' + } + } + } + } + }; const applications = await db.prisma.application.findMany({ include: { destinationDocker: true, settings: true } }); @@ -230,7 +232,6 @@ export const get: RequestHandler = async (event) => { type, destinationDocker, destinationDockerId, - updatedAt, dualCerts, plausibleAnalytics } = service; @@ -288,11 +289,11 @@ export const get: RequestHandler = async (event) => { }); } for (const application of data.applications) { - configureMiddleware(application); + configureMiddleware(application, traefik); } for (const service of data.services) { const { id, scriptName } = service; - configureMiddleware(service); + configureMiddleware(service, traefik); if (scriptName) { traefik.http.middlewares[`${id}-redir`] = { @@ -309,9 +310,14 @@ export const get: RequestHandler = async (event) => { } } for (const coolify of data.coolify) { - configureMiddleware(coolify); + configureMiddleware(coolify, traefik); + } + if (Object.keys(traefik.http.routers).length === 0) { + traefik.http.routers = null; + } + if (Object.keys(traefik.http.services).length === 0) { + traefik.http.services = null; } - return { status: 200, body: { diff --git a/src/routes/webhooks/traefik/other.json.ts b/src/routes/webhooks/traefik/other.json.ts index ea99e770e..ab0aa7c9e 100644 --- a/src/routes/webhooks/traefik/other.json.ts +++ b/src/routes/webhooks/traefik/other.json.ts @@ -40,14 +40,14 @@ export const get: RequestHandler = async (event) => { traefik = { [type]: { routers: { - [id]: { + [`${id}-${publicPort}`]: { entrypoints: [type], rule: `Host(\`${domain}\`)`, - service: id + service: `${id}-${publicPort}` } }, services: { - [id]: { + [`${id}-${publicPort}`]: { loadbalancer: { servers: [{ url: `http://${id}:${privatePort}` }] } From b006fe8f681e04a52ef835860eb38ab6681f3e47 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 18 May 2022 12:33:24 +0200 Subject: [PATCH 22/44] fix: remove debug things --- src/lib/haproxy/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index 2c21460c3..f44050047 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -252,7 +252,6 @@ export async function startTraefikHTTPProxy( `DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'` ); const ip = JSON.parse(Config)[0].Gateway; - console.log({ privatePort, publicPort }); const tcpProxy = { version: '3.5', services: { @@ -263,7 +262,7 @@ export async function startTraefikHTTPProxy( `--entrypoints.http.address=:${publicPort}`, `--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`, '--providers.http.pollTimeout=2s', - '--log.level=debug' + '--log.level=error' ], ports: [`${publicPort}:${publicPort}`], extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`], @@ -378,7 +377,7 @@ export async function startTraefikProxy(engine: string): Promise { --certificatesresolvers.letsencrypt.acme.httpchallenge=true \ --certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \ --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \ - --log.level=debug` + --log.level=error` ); await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } }); await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true }); From eb62888c39ffdafd391bbaa6daa1254bb4b95db2 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 18 May 2022 16:54:04 +0200 Subject: [PATCH 23/44] fix: WIP Traefik --- src/lib/haproxy/index.ts | 10 +- src/lib/queues/proxyTcpHttp.ts | 2 +- .../services/[id]/wordpress/ftp.json.ts | 109 ++++++++---------- src/routes/settings/index.svelte | 2 +- src/routes/update.json.ts | 24 ++-- src/routes/webhooks/traefik/other.json.ts | 6 +- 6 files changed, 73 insertions(+), 80 deletions(-) diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index f44050047..510186287 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -122,6 +122,7 @@ export async function stopTcpHttpProxy( } if (forceName) containerName = forceName; const found = await checkContainer(engine, containerName); + try { if (found) { return await asyncExecShell( @@ -137,15 +138,15 @@ export async function startTraefikTCPProxy( id: string, publicPort: number, privatePort: number, - volume?: string + type?: string ): Promise<{ stdout: string; stderr: string } | Error> { const { network, engine } = destinationDocker; const host = getEngine(engine); - const containerName = `${id}-${publicPort}`; const found = await checkContainer(engine, containerName, true); - const foundDependentContainer = await checkContainer(engine, id, true); - + let dependentId = id; + if (type === 'wordpressftp') dependentId = `${id}-ftp`; + const foundDependentContainer = await checkContainer(engine, dependentId, true); try { if (foundDependentContainer && !found) { const { stdout: Config } = await asyncExecShell( @@ -210,7 +211,6 @@ export async function startTcpProxy( const containerName = `haproxy-for-${publicPort}`; const found = await checkContainer(engine, containerName, true); const foundDependentContainer = await checkContainer(engine, id, true); - try { if (foundDependentContainer && !found) { const { stdout: Config } = await asyncExecShell( diff --git a/src/lib/queues/proxyTcpHttp.ts b/src/lib/queues/proxyTcpHttp.ts index 136798777..c70339bad 100644 --- a/src/lib/queues/proxyTcpHttp.ts +++ b/src/lib/queues/proxyTcpHttp.ts @@ -71,7 +71,7 @@ export default async function (): Promise { }); const { service: { destinationDockerId, destinationDocker }, - ftpPublicPort: oldPublicPort, + ftpPublicPort, ftpUser: user, ftpPassword: savedPassword, ftpHostKey, ftpHostKeyPrivate } = data; - if (user) ftpUser = user; - if (savedPassword) ftpPassword = decrypt(savedPassword); + const { network, engine } = destinationDocker; + const settings = await db.prisma.setting.findFirst(); + const host = getEngine(engine); + if (ftpEnabled) { + if (user) ftpUser = user; + if (savedPassword) ftpPassword = decrypt(savedPassword); + + const { stdout: password } = await asyncExecShell( + `echo ${ftpPassword} | openssl passwd -1 -stdin` + ); + if (destinationDockerId) { + try { + await fs.stat(hostkeyDir); + } catch (error) { + await asyncExecShell(`mkdir -p ${hostkeyDir}`); + } + if (!ftpHostKey) { + await asyncExecShell( + `ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519` + ); + const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`); + await db.prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKey: encrypt(ftpHostKey) } + }); + } else { + await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`); + } + if (!ftpHostKeyPrivate) { + await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`); + const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`); + await db.prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) } + }); + } else { + await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`); + } - const { stdout: password } = await asyncExecShell( - `echo ${ftpPassword} | openssl passwd -1 -stdin` - ); - if (destinationDockerId) { - try { - await fs.stat(hostkeyDir); - } catch (error) { - await asyncExecShell(`mkdir -p ${hostkeyDir}`); - } - if (!ftpHostKey) { - await asyncExecShell( - `ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519` - ); - const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`); - await db.prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpHostKey: encrypt(ftpHostKey) } - }); - } else { - await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`); - } - if (!ftpHostKeyPrivate) { - await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`); - const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`); - await db.prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) } - }); - } else { - await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`); - } - const { network, engine } = destinationDocker; - const host = getEngine(engine); - if (ftpEnabled) { await db.prisma.wordpress.update({ where: { serviceId: id }, data: { @@ -147,28 +149,7 @@ export const post: RequestHandler = async (event) => { await asyncExecShell( `DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` ); - const settings = await db.prisma.setting.findFirst(); - if (settings.isTraefikUsed) { - await startTraefikTCPProxy(destinationDocker, `${id}-ftp`, publicPort, 22); - } else { - await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22); - } - } else { - await db.prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpPublicPort: null } - }); - try { - await asyncExecShell( - `DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` - ); - } catch (error) { - // - } - await stopTcpHttpProxy(destinationDocker, oldPublicPort); } - } - if (ftpEnabled) { return { status: 201, body: { @@ -178,6 +159,18 @@ export const post: RequestHandler = async (event) => { } }; } else { + await db.prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpPublicPort: null } + }); + try { + await asyncExecShell( + `DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` + ); + } catch (error) { + // + } + await stopTcpHttpProxy(id, destinationDocker, ftpPublicPort); return { status: 200, body: {} diff --git a/src/routes/settings/index.svelte b/src/routes/settings/index.svelte index 8c975d2a9..6eeeb2bdf 100644 --- a/src/routes/settings/index.svelte +++ b/src/routes/settings/index.svelte @@ -166,7 +166,7 @@ await post(`/update.json`, { type: to }); const data = await get(`/settings.json`); $isTraefikUsed = data.settings.isTraefikUsed; - return toast.push('Proxy migration completed.'); + return toast.push('Proxy migration started, it takes a few seconds.'); } catch ({ error }) { return errorNotification(error); } finally { diff --git a/src/routes/update.json.ts b/src/routes/update.json.ts index ef5f0920b..8d697f32e 100644 --- a/src/routes/update.json.ts +++ b/src/routes/update.json.ts @@ -64,12 +64,12 @@ export const post: RequestHandler = async (event) => { } } else if (type === 'traefik') { try { - const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy'); - if (found) { - await asyncExecShell(`docker stop -t 0 coolify-haproxy`); - await asyncExecShell(`docker rm coolify-haproxy`); - } - await startTraefikProxy('/var/run/docker.sock'); + // const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy'); + // if (found) { + // await asyncExecShell(`docker stop -t 0 coolify-haproxy`); + // await asyncExecShell(`docker rm coolify-haproxy`); + // } + // await startTraefikProxy('/var/run/docker.sock'); await db.prisma.setting.update({ where: { id: settings.id }, data: { isTraefikUsed: true } @@ -83,12 +83,12 @@ export const post: RequestHandler = async (event) => { } } else if (type === 'haproxy') { try { - const found = await checkContainer('/var/run/docker.sock', 'coolify-proxy'); - if (found) { - await asyncExecShell(`docker stop -t 0 coolify-proxy`); - await asyncExecShell(`docker rm coolify-proxy`); - } - await startCoolifyProxy('/var/run/docker.sock'); + // const found = await checkContainer('/var/run/docker.sock', 'coolify-proxy'); + // if (found) { + // await asyncExecShell(`docker stop -t 0 coolify-proxy`); + // await asyncExecShell(`docker rm coolify-proxy`); + // } + // await startCoolifyProxy('/var/run/docker.sock'); await db.prisma.setting.update({ where: { id: settings.id }, data: { isTraefikUsed: false } diff --git a/src/routes/webhooks/traefik/other.json.ts b/src/routes/webhooks/traefik/other.json.ts index ab0aa7c9e..ea99e770e 100644 --- a/src/routes/webhooks/traefik/other.json.ts +++ b/src/routes/webhooks/traefik/other.json.ts @@ -40,14 +40,14 @@ export const get: RequestHandler = async (event) => { traefik = { [type]: { routers: { - [`${id}-${publicPort}`]: { + [id]: { entrypoints: [type], rule: `Host(\`${domain}\`)`, - service: `${id}-${publicPort}` + service: id } }, services: { - [`${id}-${publicPort}`]: { + [id]: { loadbalancer: { servers: [{ url: `http://${id}:${privatePort}` }] } From c0d711170bc22948e7757857c77ddade09bc0bf5 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 10:20:43 +0200 Subject: [PATCH 24/44] fix: Proxy for http --- src/lib/database/common.ts | 8 +++++++- src/lib/haproxy/index.ts | 16 ++++++++++------ src/lib/queues/proxyTcpHttp.ts | 2 ++ src/routes/services/[id]/minio/stop.json.ts | 12 +----------- src/routes/webhooks/traefik/other.json.ts | 14 ++++++++++++++ 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/lib/database/common.ts b/src/lib/database/common.ts index 53551882b..3196a40ee 100644 --- a/src/lib/database/common.ts +++ b/src/lib/database/common.ts @@ -305,6 +305,12 @@ export async function getFreePort() { select: { mysqlPublicPort: true } }) ).map((a) => a.mysqlPublicPort); - const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed]; + const minioUSed = await ( + await prisma.minio.findMany({ + where: { publicPort: { not: null } }, + select: { publicPort: true } + }) + ).map((a) => a.publicPort); + const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUSed]; return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts }); } diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index 510186287..b9a8e9581 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -202,8 +202,7 @@ export async function startTcpProxy( destinationDocker: DestinationDocker, id: string, publicPort: number, - privatePort: number, - volume?: string + privatePort: number ): Promise<{ stdout: string; stderr: string } | Error> { const { network, engine } = destinationDocker; const host = getEngine(engine); @@ -218,9 +217,7 @@ export async function startTcpProxy( ); const ip = JSON.parse(Config)[0].Gateway; return await asyncExecShell( - `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} ${ - volume ? `-v ${volume}` : '' - } -d coollabsio/${defaultProxyImageTcp}` + `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/${defaultProxyImageTcp}` ); } if (!foundDependentContainer && found) { @@ -262,11 +259,15 @@ export async function startTraefikHTTPProxy( `--entrypoints.http.address=:${publicPort}`, `--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`, '--providers.http.pollTimeout=2s', + '--certificatesresolvers.letsencrypt.acme.httpchallenge=true', + '--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json', + '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http', '--log.level=error' ], ports: [`${publicPort}:${publicPort}`], extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`], - networks: ['coolify-infra', network] + networks: ['coolify-infra', network], + volumes: ['coolify-traefik-letsencrypt:/etc/traefik/acme'] } }, networks: { @@ -278,6 +279,9 @@ export async function startTraefikHTTPProxy( external: false, name: 'coolify-infra' } + }, + volumes: { + 'coolify-traefik-letsencrypt': {} } }; await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy)); diff --git a/src/lib/queues/proxyTcpHttp.ts b/src/lib/queues/proxyTcpHttp.ts index c70339bad..4d5f3ec29 100644 --- a/src/lib/queues/proxyTcpHttp.ts +++ b/src/lib/queues/proxyTcpHttp.ts @@ -85,6 +85,7 @@ export default async function (): Promise { try { const service = await db.getService({ id, teamId }); - const { - destinationDockerId, - destinationDocker, - fqdn, - minio: { publicPort } - } = service; + const { destinationDockerId, destinationDocker } = service; await db.updateMinioService({ id, publicPort: null }); if (destinationDockerId) { const engine = destinationDocker.engine; @@ -30,11 +25,6 @@ export const post: RequestHandler = async (event) => { } catch (error) { console.error(error); } - try { - await stopTcpHttpProxy(destinationDocker, publicPort); - } catch (error) { - console.log(error); - } } return { diff --git a/src/routes/webhooks/traefik/other.json.ts b/src/routes/webhooks/traefik/other.json.ts index ea99e770e..428c9e217 100644 --- a/src/routes/webhooks/traefik/other.json.ts +++ b/src/routes/webhooks/traefik/other.json.ts @@ -37,6 +37,7 @@ export const get: RequestHandler = async (event) => { const service = await db.prisma.service.findFirst({ where: { id } }); if (service?.fqdn) { const domain = getDomain(service.fqdn); + const isHttps = service.fqdn.startsWith('https://'); traefik = { [type]: { routers: { @@ -55,6 +56,19 @@ export const get: RequestHandler = async (event) => { } } }; + if (isHttps) { + if (dev) { + traefik[type].routers[id].tls = { + domains: { + main: `${domain}` + } + }; + } else { + traefik[type].routers[id].tls = { + certresolver: 'letsencrypt' + }; + } + } } } } From ebdd3601b35735dd74fb7ed0cd7cacf88b0622a4 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 10:21:18 +0200 Subject: [PATCH 25/44] remove console.logs --- src/lib/queues/proxyTcpHttp.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/queues/proxyTcpHttp.ts b/src/lib/queues/proxyTcpHttp.ts index 4d5f3ec29..c70339bad 100644 --- a/src/lib/queues/proxyTcpHttp.ts +++ b/src/lib/queues/proxyTcpHttp.ts @@ -85,7 +85,6 @@ export default async function (): Promise Date: Thu, 19 May 2022 13:44:45 +0200 Subject: [PATCH 26/44] fix: PR deployments view --- src/routes/applications/[id]/previews/index.json.ts | 8 ++------ src/routes/applications/[id]/previews/index.svelte | 10 +++++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/routes/applications/[id]/previews/index.json.ts b/src/routes/applications/[id]/previews/index.json.ts index ab10a06b8..d92690671 100644 --- a/src/routes/applications/[id]/previews/index.json.ts +++ b/src/routes/applications/[id]/previews/index.json.ts @@ -17,7 +17,7 @@ export const get: RequestHandler = async (event) => { const destinationDocker = await db.getDestinationByApplicationId({ id, teamId }); const docker = dockerInstance({ destinationDocker }); const listContainers = await docker.engine.listContainers({ - filters: { network: [destinationDocker.network] } + filters: { network: [destinationDocker.network], name: [id] } }); const containers = listContainers.filter((container) => { return ( @@ -30,11 +30,7 @@ export const get: RequestHandler = async (event) => { JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString()) ) .filter((container) => { - return ( - container.type !== 'manual' && - container.type !== 'webhook_commit' && - container.applicationId === id - ); + return container.pullmergeRequestId && container.applicationId === id; }); return { body: { diff --git a/src/routes/applications/[id]/previews/index.svelte b/src/routes/applications/[id]/previews/index.svelte index 152aa00d2..f343c17df 100644 --- a/src/routes/applications/[id]/previews/index.svelte +++ b/src/routes/applications/[id]/previews/index.svelte @@ -31,6 +31,7 @@ import { errorNotification } from '$lib/form'; import { toast } from '@zerodevx/svelte-toast'; import { t } from '$lib/translations'; + import { goto } from '$app/navigation'; const { id } = $page.params; async function refreshSecrets() { @@ -39,11 +40,18 @@ } async function redeploy(container) { try { - await post(`/applications/${id}/deploy.json`, { + const { buildId } = await post(`/applications/${id}/deploy.json`, { pullmergeRequestId: container.pullmergeRequestId, branch: container.branch }); toast.push('Application redeployed queued.'); + if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) { + return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`); + } else { + return await goto(`/applications/${id}/logs/build?buildId=${buildId}`, { + replaceState: true + }); + } } catch ({ error }) { return errorNotification(error); } From 6fb6a514ac7522d177e314dd5b724d9d08297b0e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 13:45:17 +0200 Subject: [PATCH 27/44] fix: Minio urls + domain checks --- .../migration.sql | 2 + prisma/schema.prisma | 1 + src/lib/database/checks.ts | 11 +- src/lib/database/common.ts | 4 +- src/lib/database/services.ts | 21 +++- src/lib/queues/proxyTcpHttp.ts | 23 ++-- .../services/[id]/_Services/_MinIO.svelte | 24 ++-- .../services/[id]/_Services/_Services.svelte | 84 ++++++++++--- src/routes/services/[id]/check.json.ts | 20 ++- src/routes/services/[id]/minio/index.json.ts | 11 +- src/routes/services/[id]/minio/start.json.ts | 3 +- src/routes/webhooks/traefik/main.json.ts | 47 +++++-- src/routes/webhooks/traefik/other.json.ts | 115 +++++++++++++----- 13 files changed, 270 insertions(+), 96 deletions(-) create mode 100644 prisma/migrations/20220519095648_minio_apifqdn/migration.sql diff --git a/prisma/migrations/20220519095648_minio_apifqdn/migration.sql b/prisma/migrations/20220519095648_minio_apifqdn/migration.sql new file mode 100644 index 000000000..a44712864 --- /dev/null +++ b/prisma/migrations/20220519095648_minio_apifqdn/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Minio" ADD COLUMN "apiFqdn" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 97d092331..4629a1ba1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -345,6 +345,7 @@ model Minio { rootUser String rootUserPassword String publicPort Int? + apiFqdn String? serviceId String @unique service Service @relation(fields: [serviceId], references: [id]) createdAt DateTime @default(now()) diff --git a/src/lib/database/checks.ts b/src/lib/database/checks.ts index bbe773ae5..2c1c25cd8 100644 --- a/src/lib/database/checks.ts +++ b/src/lib/database/checks.ts @@ -51,10 +51,12 @@ export async function isSecretExists({ export async function isDomainConfigured({ id, - fqdn + fqdn, + checkOwn = false }: { id: string; fqdn: string; + checkOwn?: boolean; }): Promise { const domain = getDomain(fqdn); const nakedDomain = domain.replace('www.', ''); @@ -72,12 +74,15 @@ export async function isDomainConfigured({ where: { OR: [ { fqdn: { endsWith: `//${nakedDomain}` } }, - { fqdn: { endsWith: `//www.${nakedDomain}` } } + { fqdn: { endsWith: `//www.${nakedDomain}` } }, + { minio: { apiFqdn: { endsWith: `//${nakedDomain}` } } }, + { minio: { apiFqdn: { endsWith: `//www.${nakedDomain}` } } } ], - id: { not: id } + id: { not: checkOwn ? undefined : id } }, select: { fqdn: true } }); + const coolifyFqdn = await prisma.setting.findFirst({ where: { OR: [ diff --git a/src/lib/database/common.ts b/src/lib/database/common.ts index 3196a40ee..c26796ea4 100644 --- a/src/lib/database/common.ts +++ b/src/lib/database/common.ts @@ -305,12 +305,12 @@ export async function getFreePort() { select: { mysqlPublicPort: true } }) ).map((a) => a.mysqlPublicPort); - const minioUSed = await ( + const minioUsed = await ( await prisma.minio.findMany({ where: { publicPort: { not: null } }, select: { publicPort: true } }) ).map((a) => a.publicPort); - const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUSed]; + const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed]; return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts }); } diff --git a/src/lib/database/services.ts b/src/lib/database/services.ts index d2a7bb899..453153c0c 100644 --- a/src/lib/database/services.ts +++ b/src/lib/database/services.ts @@ -360,7 +360,24 @@ export async function updateService({ }): Promise { return await prisma.service.update({ where: { id }, data: { fqdn, name, exposePort } }); } - +export async function updateMinioService({ + id, + fqdn, + apiFqdn, + exposePort, + name +}: { + id: string; + fqdn: string; + apiFqdn: string; + exposePort?: number; + name: string; +}): Promise { + return await prisma.service.update({ + where: { id }, + data: { fqdn, name, exposePort, minio: { update: { apiFqdn } } } + }); +} export async function updateFiderService({ id, fqdn, @@ -459,7 +476,7 @@ export async function updateWordpress({ }); } -export async function updateMinioService({ +export async function updateMinioServicePort({ id, publicPort }: { diff --git a/src/lib/queues/proxyTcpHttp.ts b/src/lib/queues/proxyTcpHttp.ts index c70339bad..9ce239b66 100644 --- a/src/lib/queues/proxyTcpHttp.ts +++ b/src/lib/queues/proxyTcpHttp.ts @@ -81,19 +81,16 @@ export default async function (): Promise
-
- - -
+{#if !service.minio.apiFqdn} +
+ + +
+{/if} diff --git a/src/routes/services/[id]/_Services/_Services.svelte b/src/routes/services/[id]/_Services/_Services.svelte index 00ef5d305..03a94d311 100644 --- a/src/routes/services/[id]/_Services/_Services.svelte +++ b/src/routes/services/[id]/_Services/_Services.svelte @@ -33,6 +33,7 @@ try { await post(`/services/${id}/check.json`, { fqdn: service.fqdn, + otherFqdns: [service.minio.apiFqdn], exposePort: service.exposePort }); await post(`/services/${id}/${service.type}.json`, { ...service }); @@ -89,6 +90,14 @@
+ {#if service.type === 'minio' && !service.minio.apiFqdn && isRunning} +
+ IMPORTANT! There was a small modification with + Minio in the latest version of Coolify. Now you can separate the Console URL from the API URL, + so you could use both through SSL. But this proccess cannot be done automatically, so you have + to stop your Minio instance, configure the new domain and start it back. Sorry for any inconvenience. +
+ {/if}
@@ -134,25 +143,62 @@ {/if}
-
-
- - -
+ {#if service.type === 'minio'} +
+
+ +
+ + +
+
+
+ + +
+ + +
+ {:else} +
+
+ + +
+ + +
+ {/if} - -
!isRunning && changeSettings('dualCerts')} />
-
+
{ if (status === 401) return { status, body }; const { id } = event.params; - let { fqdn, exposePort } = await event.request.json(); + let { fqdn, exposePort, otherFqdns } = await event.request.json(); if (fqdn) fqdn = fqdn.toLowerCase(); + if (otherFqdns) otherFqdns = otherFqdns.map((fqdn) => fqdn.toLowerCase()); + if (exposePort) exposePort = Number(exposePort); try { - const found = await db.isDomainConfigured({ id, fqdn }); + let found = await db.isDomainConfigured({ id, fqdn }); if (found) { throw { message: t.get('application.domain_already_in_use', { @@ -23,6 +25,20 @@ export const post: RequestHandler = async (event) => { }) }; } + if (otherFqdns) { + for (const ofqdn of otherFqdns) { + const domain = getDomain(ofqdn); + const nakedDomain = domain.replace('www.', ''); + found = await db.isDomainConfigured({ id, fqdn: ofqdn, checkOwn: true }); + if (found) { + throw { + message: t.get('application.domain_already_in_use', { + domain: nakedDomain + }) + }; + } + } + } if (exposePort) { exposePort = Number(exposePort); diff --git a/src/routes/services/[id]/minio/index.json.ts b/src/routes/services/[id]/minio/index.json.ts index ff98ede6d..32056218d 100644 --- a/src/routes/services/[id]/minio/index.json.ts +++ b/src/routes/services/[id]/minio/index.json.ts @@ -9,12 +9,17 @@ export const post: RequestHandler = async (event) => { const { id } = event.params; - let { name, fqdn, exposePort } = await event.request.json(); + let { + name, + fqdn, + exposePort, + minio: { apiFqdn } + } = await event.request.json(); if (fqdn) fqdn = fqdn.toLowerCase(); if (exposePort) exposePort = Number(exposePort); - + if (apiFqdn) apiFqdn = apiFqdn.toLowerCase(); try { - await db.updateService({ id, fqdn, name, exposePort }); + await db.updateMinioService({ id, fqdn, apiFqdn, name, exposePort }); return { status: 201 }; } catch (error) { return ErrorHandler(error); diff --git a/src/routes/services/[id]/minio/start.json.ts b/src/routes/services/[id]/minio/start.json.ts index dee17c1af..9dd75bdd9 100644 --- a/src/routes/services/[id]/minio/start.json.ts +++ b/src/routes/services/[id]/minio/start.json.ts @@ -34,7 +34,6 @@ export const post: RequestHandler = async (event) => { const publicPort = await getFreePort(); const consolePort = 9001; - const apiPort = 9000; const { workdir } = await createDirectories({ repository: type, buildId: id }); const image = getServiceImage(type); @@ -93,7 +92,7 @@ export const post: RequestHandler = async (event) => { try { await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); - await db.updateMinioService({ id, publicPort }); + await db.updateMinioServicePort({ id, publicPort }); return { status: 200 }; diff --git a/src/routes/webhooks/traefik/main.json.ts b/src/routes/webhooks/traefik/main.json.ts index e887a06d9..e39e41f83 100644 --- a/src/routes/webhooks/traefik/main.json.ts +++ b/src/routes/webhooks/traefik/main.json.ts @@ -7,7 +7,7 @@ import { checkContainer } from '$lib/haproxy'; import type { RequestHandler } from '@sveltejs/kit'; function configureMiddleware( - { id, port, domain, nakedDomain, isHttps, isWWW, isDualCerts }, + { id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts }, traefik ) { if (isHttps) { @@ -22,7 +22,7 @@ function configureMiddleware( loadbalancer: { servers: [ { - url: `http://${id}:${port}` + url: `http://${container}:${port}` } ] } @@ -109,7 +109,7 @@ function configureMiddleware( loadbalancer: { servers: [ { - url: `http://${id}:${port}` + url: `http://${container}:${port}` } ] } @@ -172,8 +172,7 @@ export const get: RequestHandler = async (event) => { port, destinationDocker, destinationDockerId, - settings: { previews, dualCerts }, - updatedAt + settings: { previews, dualCerts } } = application; if (destinationDockerId) { const { engine, network } = destinationDocker; @@ -186,6 +185,7 @@ export const get: RequestHandler = async (event) => { if (isRunning) { data.applications.push({ id, + container: id, port: port || 3000, domain, nakedDomain, @@ -208,14 +208,17 @@ export const get: RequestHandler = async (event) => { if (containers.length > 0) { for (const container of containers) { const previewDomain = `${container.split('-')[1]}.${domain}`; + const nakedDomain = previewDomain.replace(/^www\./, ''); data.applications.push({ id: container, + container, port: port || 3000, domain: previewDomain, isRunning, + nakedDomain, isHttps, - redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain, - updatedAt: updatedAt.getTime() + isWWW, + isDualCerts: dualCerts }); } } @@ -254,8 +257,26 @@ export const get: RequestHandler = async (event) => { scriptName = plausibleAnalytics.scriptName; } + let container = id; + let otherDomain = null; + let otherNakedDomain = null; + let otherIsHttps = null; + let otherIsWWW = null; + + if (type === 'minio') { + otherDomain = getDomain(service.minio.apiFqdn); + otherNakedDomain = otherDomain.replace(/^www\./, ''); + otherIsHttps = service.minio.apiFqdn.startsWith('https://'); + otherIsWWW = service.minio.apiFqdn.includes('www.'); + } data.services.push({ id, + container, + type, + otherDomain, + otherNakedDomain, + otherIsHttps, + otherIsWWW, port, publicPort, domain, @@ -280,6 +301,7 @@ export const get: RequestHandler = async (event) => { const isWWW = fqdn.includes('www.'); data.coolify.push({ id: dev ? 'host.docker.internal' : 'coolify', + container: dev ? 'host.docker.internal' : 'coolify', port: 3000, domain, nakedDomain, @@ -293,7 +315,18 @@ export const get: RequestHandler = async (event) => { } for (const service of data.services) { const { id, scriptName } = service; + configureMiddleware(service, traefik); + if (service.type === 'minio') { + service.id = id + '-minio'; + service.container = id; + service.domain = service.otherDomain; + service.nakedDomain = service.otherNakedDomain; + service.isHttps = service.otherIsHttps; + service.isWWW = service.otherIsWWW; + service.port = 9000; + configureMiddleware(service, traefik); + } if (scriptName) { traefik.http.middlewares[`${id}-redir`] = { diff --git a/src/routes/webhooks/traefik/other.json.ts b/src/routes/webhooks/traefik/other.json.ts index 428c9e217..a1c357878 100644 --- a/src/routes/webhooks/traefik/other.json.ts +++ b/src/routes/webhooks/traefik/other.json.ts @@ -1,9 +1,6 @@ import { dev } from '$app/env'; -import { asyncExecShell, getDomain, getEngine } from '$lib/common'; -import { supportedServiceTypesAndVersions } from '$lib/components/common'; +import { getDomain } from '$lib/common'; import * as db from '$lib/database'; -import { listServicesWithIncludes } from '$lib/database'; -import { checkContainer } from '$lib/haproxy'; import type { RequestHandler } from '@sveltejs/kit'; export const get: RequestHandler = async (event) => { @@ -13,7 +10,7 @@ export const get: RequestHandler = async (event) => { const publicPort = event.url.searchParams.get('publicPort'); const type = event.url.searchParams.get('type'); let traefik = {}; - if (publicPort) { + if (publicPort && type && privatePort) { if (type === 'tcp') { traefik = { [type]: { @@ -34,43 +31,97 @@ export const get: RequestHandler = async (event) => { } }; } else if (type === 'http') { - const service = await db.prisma.service.findFirst({ where: { id } }); - if (service?.fqdn) { - const domain = getDomain(service.fqdn); - const isHttps = service.fqdn.startsWith('https://'); - traefik = { - [type]: { - routers: { - [id]: { - entrypoints: [type], - rule: `Host(\`${domain}\`)`, - service: id - } - }, - services: { - [id]: { - loadbalancer: { - servers: [{ url: `http://${id}:${privatePort}` }] + const service = await db.prisma.service.findFirst({ + where: { id }, + include: { minio: true } + }); + if (service) { + if (service.type === 'minio') { + if (service?.minio?.apiFqdn) { + const { + minio: { apiFqdn } + } = service; + const domain = getDomain(apiFqdn); + const isHttps = apiFqdn.startsWith('https://'); + traefik = { + [type]: { + routers: { + [id]: { + entrypoints: [type], + rule: `Host(\`${domain}\`)`, + service: id + } + }, + services: { + [id]: { + loadbalancer: { + servers: [{ url: `http://${id}:${privatePort}` }] + } + } } } + }; + if (isHttps) { + if (dev) { + traefik[type].routers[id].tls = { + domains: { + main: `${domain}` + } + }; + } else { + traefik[type].routers[id].tls = { + certresolver: 'letsencrypt' + }; + } } } - }; - if (isHttps) { - if (dev) { - traefik[type].routers[id].tls = { - domains: { - main: `${domain}` + } else { + if (service?.fqdn) { + const domain = getDomain(service.fqdn); + const isHttps = service.fqdn.startsWith('https://'); + traefik = { + [type]: { + routers: { + [id]: { + entrypoints: [type], + rule: `Host(\`${domain}:${privatePort}\`)`, + service: id + } + }, + services: { + [id]: { + loadbalancer: { + servers: [{ url: `http://${id}:${privatePort}` }] + } + } + } } }; - } else { - traefik[type].routers[id].tls = { - certresolver: 'letsencrypt' - }; + if (isHttps) { + if (dev) { + traefik[type].routers[id].tls = { + domains: { + main: `${domain}` + } + }; + } else { + traefik[type].routers[id].tls = { + certresolver: 'letsencrypt' + }; + } + } } } + } else { + return { + status: 500 + }; } } + } else { + return { + status: 500 + }; } return { status: 200, From 56ab8312f17f2c19928f0b0741319b8ee3228d46 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 14:32:10 +0200 Subject: [PATCH 28/44] remove notifications for now --- .../20220517081338_notifications/migration.sql | 10 ---------- prisma/schema.prisma | 10 ---------- 2 files changed, 20 deletions(-) delete mode 100644 prisma/migrations/20220517081338_notifications/migration.sql diff --git a/prisma/migrations/20220517081338_notifications/migration.sql b/prisma/migrations/20220517081338_notifications/migration.sql deleted file mode 100644 index 069f938d5..000000000 --- a/prisma/migrations/20220517081338_notifications/migration.sql +++ /dev/null @@ -1,10 +0,0 @@ --- CreateTable -CREATE TABLE "Notification" ( - "id" TEXT NOT NULL PRIMARY KEY, - "type" TEXT NOT NULL, - "message" TEXT NOT NULL, - "isRead" BOOLEAN NOT NULL DEFAULT false, - "showAtVersion" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4629a1ba1..7cef6adda 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -25,16 +25,6 @@ model Setting { updatedAt DateTime @updatedAt } -model Notification { - id String @id @default(cuid()) - type String - message String - isRead Boolean @default(false) - showAtVersion String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -} - model User { id String @id @unique @default(cuid()) email String @unique From 70a3fc247e96a59d9d54618806325624db99e518 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 14:32:31 +0200 Subject: [PATCH 29/44] remove notifications for now --- src/routes/__layout.svelte | 12 ------------ src/routes/notifications.json.ts | 30 ------------------------------ 2 files changed, 42 deletions(-) delete mode 100644 src/routes/notifications.json.ts diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index 39303e0ea..492673a28 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -83,9 +83,6 @@ } finally { } } - try { - const data = await get(`/notifications.json`); - } catch (error) {} } }); @@ -531,15 +528,6 @@ >Powered by Coolify {/if} - {#if !$isTraefikUsed} - - - - {/if} {/if}
diff --git a/src/routes/notifications.json.ts b/src/routes/notifications.json.ts deleted file mode 100644 index 840fe5118..000000000 --- a/src/routes/notifications.json.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ErrorHandler } from '$lib/database'; -import { version } from '$lib/common'; -import * as db from '$lib/database'; -import type { RequestHandler } from '@sveltejs/kit'; - -export const get: RequestHandler = async (request) => { - try { - const unreadNotifications = await db.prisma.notification.findMany({ - where: { isRead: false, showAtVersion: version } - }); - return { - status: 200, - body: { - ...unreadNotifications - } - }; - } catch (error) { - console.log(error); - return ErrorHandler(error); - } -}; - -export const post: RequestHandler = async (event) => { - const { type, latestVersion } = await event.request.json(); - const settings = await db.prisma.setting.findFirst(); - - return { - status: 500 - }; -}; From 7fc43ef2bbcbf221c13af1b4ce8298dae119c03b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 14:32:47 +0200 Subject: [PATCH 30/44] fix: Remove gh token on git source changes --- src/routes/applications/[id]/configuration/repository.svelte | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/routes/applications/[id]/configuration/repository.svelte b/src/routes/applications/[id]/configuration/repository.svelte index 822c0f74f..5b2809751 100644 --- a/src/routes/applications/[id]/configuration/repository.svelte +++ b/src/routes/applications/[id]/configuration/repository.svelte @@ -19,10 +19,13 @@ From 6a59b8d27cd01479263d179157732488cba83772 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 14:33:02 +0200 Subject: [PATCH 31/44] fix: do not fetch app state in case of missconfiguration --- src/routes/applications/[id]/__layout.svelte | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/routes/applications/[id]/__layout.svelte b/src/routes/applications/[id]/__layout.svelte index 877d3620a..3662f289a 100644 --- a/src/routes/applications/[id]/__layout.svelte +++ b/src/routes/applications/[id]/__layout.svelte @@ -143,10 +143,18 @@ clearInterval(statusInterval); }); onMount(async () => { - await getStatus(); - statusInterval = setInterval(async () => { + if (!application.gitSourceId || !application.destinationDockerId || !application.fqdn) { + $status.application.initialLoading = false; + $status.application.isRunning = false; + $status.application.isExited = false; + $status.application.loading = false; + return; + } else { await getStatus(); - }, 1000); + statusInterval = setInterval(async () => { + await getStatus(); + }, 1000); + } }); From e2bf02841f4d7128be28c2ffa51431e149dcd5e7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 14:36:49 +0200 Subject: [PATCH 32/44] fix: Demo instance save domain instantly --- src/routes/applications/[id]/index.svelte | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/routes/applications/[id]/index.svelte b/src/routes/applications/[id]/index.svelte index 6b3d09dd7..a00f08d2f 100644 --- a/src/routes/applications/[id]/index.svelte +++ b/src/routes/applications/[id]/index.svelte @@ -88,9 +88,7 @@ function containerClass() { return 'text-white border border-dashed border-coolgray-300 bg-transparent font-thin px-0'; } - if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) { - application.fqdn = `http://${cuid()}.demo.coolify.io`; - } + async function getUsage() { if (usageLoading) return; usageLoading = true; @@ -102,6 +100,10 @@ clearInterval(usageInterval); }); onMount(async () => { + if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) { + application.fqdn = `http://${cuid()}.demo.coolify.io`; + await handleSubmit(); + } domainEl.focus(); await getUsage(); usageInterval = setInterval(async () => { @@ -149,6 +151,7 @@ } } async function handleSubmit() { + if (loading) return; loading = true; try { nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, ''); From 26eacfc2c05f78bc09d28acc6b1a2f0a9fe10389 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 14:38:38 +0200 Subject: [PATCH 33/44] fix: Instant save on demo instance --- src/routes/services/[id]/_Services/_Services.svelte | 11 +++++++++++ src/routes/services/[id]/index.svelte | 6 ------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/routes/services/[id]/_Services/_Services.svelte b/src/routes/services/[id]/_Services/_Services.svelte index 03a94d311..826a3bdc8 100644 --- a/src/routes/services/[id]/_Services/_Services.svelte +++ b/src/routes/services/[id]/_Services/_Services.svelte @@ -1,4 +1,6 @@
diff --git a/src/routes/services/[id]/index.svelte b/src/routes/services/[id]/index.svelte index a16591266..2a3bc0d26 100644 --- a/src/routes/services/[id]/index.svelte +++ b/src/routes/services/[id]/index.svelte @@ -30,8 +30,6 @@
From 54f7142b2b5704de1275dab1faecf9367bd70757 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 15:05:56 +0200 Subject: [PATCH 34/44] disable appwrite for now --- src/lib/components/common.ts | 23 ++++++++++--------- .../{appwrite => appwrite-wip}/index.json.ts | 0 .../{appwrite => appwrite-wip}/start.json.ts | 0 .../{appwrite => appwrite-wip}/stop.json.ts | 0 src/routes/webhooks/traefik/main.json.ts | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) rename src/routes/services/[id]/{appwrite => appwrite-wip}/index.json.ts (100%) rename src/routes/services/[id]/{appwrite => appwrite-wip}/start.json.ts (100%) rename src/routes/services/[id]/{appwrite => appwrite-wip}/stop.json.ts (100%) diff --git a/src/lib/components/common.ts b/src/lib/components/common.ts index 3ded5d12b..20b66a9cd 100644 --- a/src/lib/components/common.ts +++ b/src/lib/components/common.ts @@ -219,17 +219,18 @@ export const supportedServiceTypesAndVersions = [ ports: { main: 3000 } - }, - { - name: 'appwrite', - fancyName: 'AppWrite', - baseImage: 'appwrite/appwrite', - images: ['appwrite/influxdb', 'appwrite/telegraf', 'mariadb:10.7', 'redis:6.0-alpine3.12'], - versions: ['latest', '0.13.0'], - recommendedVersion: '0.13.0', - ports: { - main: 3000 - } + // }, + // { + // name: 'appwrite', + // fancyName: 'AppWrite', + // baseImage: 'appwrite/appwrite', + // images: ['appwrite/influxdb', 'appwrite/telegraf', 'mariadb:10.7', 'redis:6.0-alpine3.12'], + // versions: ['latest', '0.13.0'], + // recommendedVersion: '0.13.0', + // ports: { + // main: 3000 + // } + // } } ]; diff --git a/src/routes/services/[id]/appwrite/index.json.ts b/src/routes/services/[id]/appwrite-wip/index.json.ts similarity index 100% rename from src/routes/services/[id]/appwrite/index.json.ts rename to src/routes/services/[id]/appwrite-wip/index.json.ts diff --git a/src/routes/services/[id]/appwrite/start.json.ts b/src/routes/services/[id]/appwrite-wip/start.json.ts similarity index 100% rename from src/routes/services/[id]/appwrite/start.json.ts rename to src/routes/services/[id]/appwrite-wip/start.json.ts diff --git a/src/routes/services/[id]/appwrite/stop.json.ts b/src/routes/services/[id]/appwrite-wip/stop.json.ts similarity index 100% rename from src/routes/services/[id]/appwrite/stop.json.ts rename to src/routes/services/[id]/appwrite-wip/stop.json.ts diff --git a/src/routes/webhooks/traefik/main.json.ts b/src/routes/webhooks/traefik/main.json.ts index e39e41f83..467dcddf7 100644 --- a/src/routes/webhooks/traefik/main.json.ts +++ b/src/routes/webhooks/traefik/main.json.ts @@ -263,7 +263,7 @@ export const get: RequestHandler = async (event) => { let otherIsHttps = null; let otherIsWWW = null; - if (type === 'minio') { + if (type === 'minio' && service.minio.apiFqdn) { otherDomain = getDomain(service.minio.apiFqdn); otherNakedDomain = otherDomain.replace(/^www\./, ''); otherIsHttps = service.minio.apiFqdn.startsWith('https://'); From 795f99bb4799ca9bd14eb11288e3aec911ddb427 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 15:18:19 +0200 Subject: [PATCH 35/44] fix: new source canceled view --- src/routes/sources/[id]/_Github.svelte | 10 ++++------ src/routes/sources/[id]/index.svelte | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/routes/sources/[id]/_Github.svelte b/src/routes/sources/[id]/_Github.svelte index b03fe8abd..6d91f2699 100644 --- a/src/routes/sources/[id]/_Github.svelte +++ b/src/routes/sources/[id]/_Github.svelte @@ -80,10 +80,13 @@
- {#if !source.githubAppId} + {#if !source.githubApp?.installationId}
General
+ {#if source.apiUrl && source.htmlUrl && source.name} + + {/if}
@@ -117,11 +120,6 @@ />
- {#if source.apiUrl && source.htmlUrl && source.name} -
- -
- {/if} {:else if source.githubApp?.installationId}
diff --git a/src/routes/sources/[id]/index.svelte b/src/routes/sources/[id]/index.svelte index 4b12fe1c6..ffdd7d6a1 100644 --- a/src/routes/sources/[id]/index.svelte +++ b/src/routes/sources/[id]/index.svelte @@ -113,7 +113,7 @@
- {#if !source.gitlabAppId && !source.githubAppId} + {#if !source.gitlabAppId && (!source.githubAppId || !source.githubApp?.installationId)}
Select a provider
From 972f77c790418357acd7d924f5be09f3744544ef Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 16:19:20 +0200 Subject: [PATCH 36/44] ui: css --- src/tailwind.css | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/tailwind.css b/src/tailwind.css index 8996a07c8..b8f1e647b 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -49,6 +49,9 @@ #svelte .custom-select-wrapper .selectContainer { @apply h-12 w-96 rounded border-none bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm; } +#svelte .custom-select-wrapper .spinner { + @apply text-coollabs-100; +} #svelte .listContainer { @apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-green-500 scrollbar-track-coolgray-200; } @@ -66,26 +69,6 @@ #svelte .item.active { select { @apply h-12 w-96 rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm; } -.svelte-select { - --background: rgb(32 32 32); - --inputColor: white; - --multiItemPadding: 0; - --multiSelectPadding: 0 0.5rem 0 0.5rem; - --border: none; - --placeholderColor: rgb(87 83 78); - --listBackground: rgb(32 32 32); - --itemColor: white; - --itemHoverBG: rgb(107 22 237); - --multiItemBG: rgb(32 32 32); - --multiClearHoverBG: transparent; - --multiClearHoverFill: rgb(239 68 68); - --multiItemActiveBG: transparent; - --multiClearBG: transparent; - --clearSelectFocusColor: white; - --clearSelectHoverColor: rgb(239 68 68); - --multiItemBorderRadius: 0.25rem; - --listShadow: none; -} label { @apply inline-block w-64 text-xs tracking-tight md:text-sm; From 1f25bc411fd37cd8460dd559c869f1da372036bc Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 16:43:17 +0200 Subject: [PATCH 37/44] feat: database + service usage --- src/routes/applications/[id]/index.svelte | 2 +- src/routes/databases/[id]/index.svelte | 57 +++++++++++++++++++ .../[id]/usage.json.ts | 11 ++-- .../services/[id]/_Services/_Services.svelte | 2 +- src/routes/services/[id]/index.svelte | 57 +++++++++++++++++++ src/routes/services/[id]/usage.json.ts | 30 ++++++++++ 6 files changed, 151 insertions(+), 8 deletions(-) rename src/routes/{applications => databases}/[id]/usage.json.ts (55%) create mode 100644 src/routes/services/[id]/usage.json.ts diff --git a/src/routes/applications/[id]/index.svelte b/src/routes/applications/[id]/index.svelte index a00f08d2f..78fa72dff 100644 --- a/src/routes/applications/[id]/index.svelte +++ b/src/routes/applications/[id]/index.svelte @@ -102,7 +102,7 @@ onMount(async () => { if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) { application.fqdn = `http://${cuid()}.demo.coolify.io`; - await handleSubmit(); + await post(`/applications/${id}.json`, { ...application }); } domainEl.focus(); await getUsage(); diff --git a/src/routes/databases/[id]/index.svelte b/src/routes/databases/[id]/index.svelte index 3ead53529..c9eb7a4ff 100644 --- a/src/routes/databases/[id]/index.svelte +++ b/src/routes/databases/[id]/index.svelte @@ -33,10 +33,40 @@
@@ -49,4 +79,31 @@
+
+
Database Usage
+
+
+
+
Used Memory / Memory Limit
+
+ {usage?.MemUsage} +
+
+ +
+
Used CPU
+
+ {usage?.CPUPerc} +
+
+ +
+
Network IO
+
+ {usage?.NetIO} +
+
+
+
+
diff --git a/src/routes/applications/[id]/usage.json.ts b/src/routes/databases/[id]/usage.json.ts similarity index 55% rename from src/routes/applications/[id]/usage.json.ts rename to src/routes/databases/[id]/usage.json.ts index 2ff28e6ae..9ec987310 100644 --- a/src/routes/applications/[id]/usage.json.ts +++ b/src/routes/databases/[id]/usage.json.ts @@ -1,9 +1,8 @@ -import { asyncExecShell, getUserDetails } from '$lib/common'; +import { getUserDetails } from '$lib/common'; import * as db from '$lib/database'; import { ErrorHandler } from '$lib/database'; -import { checkContainer, getContainerUsage, isContainerExited } from '$lib/haproxy'; +import { getContainerUsage } from '$lib/haproxy'; import type { RequestHandler } from '@sveltejs/kit'; -import { setDefaultConfiguration } from '$lib/buildPacks/common'; export const get: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -13,9 +12,9 @@ export const get: RequestHandler = async (event) => { let usage = {}; try { - const application = await db.getApplication({ id, teamId }); - if (application.destinationDockerId) { - [usage] = await Promise.all([getContainerUsage(application.destinationDocker.engine, id)]); + const database = await db.getDatabase({ id, teamId }); + if (database.destinationDockerId) { + [usage] = await Promise.all([getContainerUsage(database.destinationDocker.engine, id)]); } return { status: 200, diff --git a/src/routes/services/[id]/_Services/_Services.svelte b/src/routes/services/[id]/_Services/_Services.svelte index 826a3bdc8..d1d638bb4 100644 --- a/src/routes/services/[id]/_Services/_Services.svelte +++ b/src/routes/services/[id]/_Services/_Services.svelte @@ -74,7 +74,7 @@ onMount(async () => { if (browser && window.location.hostname === 'demo.coolify.io' && !service.fqdn) { service.fqdn = `http://${cuid()}.demo.coolify.io`; - await handleSubmit(); + await post(`/services/${id}/${service.type}.json`, { ...service }); } }); diff --git a/src/routes/services/[id]/index.svelte b/src/routes/services/[id]/index.svelte index 2a3bc0d26..4d231a8fb 100644 --- a/src/routes/services/[id]/index.svelte +++ b/src/routes/services/[id]/index.svelte @@ -32,11 +32,41 @@
@@ -46,6 +76,7 @@
{service.name}
+ {#if service.fqdn}
+
+
Service Usage
+
+
+
+
Used Memory / Memory Limit
+
+ {usage?.MemUsage} +
+
+
+
Used CPU
+
+ {usage?.CPUPerc} +
+
+ +
+
Network IO
+
+ {usage?.NetIO} +
+
+
+
+
diff --git a/src/routes/services/[id]/usage.json.ts b/src/routes/services/[id]/usage.json.ts new file mode 100644 index 000000000..24e27b1c4 --- /dev/null +++ b/src/routes/services/[id]/usage.json.ts @@ -0,0 +1,30 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { ErrorHandler } from '$lib/database'; +import { getContainerUsage } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + let usage = {}; + try { + const service = await db.getService({ id, teamId }); + if (service.destinationDockerId) { + [usage] = await Promise.all([getContainerUsage(service.destinationDocker.engine, id)]); + } + return { + status: 200, + body: { + usage + }, + headers: {} + }; + } catch (error) { + console.log(error); + return ErrorHandler(error); + } +}; From cfdc8db54333901b1e1ad77f17ec665d5ae055ba Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 16:47:45 +0200 Subject: [PATCH 38/44] Deleted a file, oops --- src/routes/applications/[id]/usage.json.ts | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/routes/applications/[id]/usage.json.ts diff --git a/src/routes/applications/[id]/usage.json.ts b/src/routes/applications/[id]/usage.json.ts new file mode 100644 index 000000000..ab161d81e --- /dev/null +++ b/src/routes/applications/[id]/usage.json.ts @@ -0,0 +1,30 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { ErrorHandler } from '$lib/database'; +import { getContainerUsage } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + + let usage = {}; + try { + const application = await db.getApplication({ id, teamId }); + if (application.destinationDockerId) { + [usage] = await Promise.all([getContainerUsage(application.destinationDocker.engine, id)]); + } + return { + status: 200, + body: { + usage + }, + headers: {} + }; + } catch (error) { + console.log(error); + return ErrorHandler(error); + } +}; From 59c62923bea64591338ad0e4d14d601bf383f970 Mon Sep 17 00:00:00 2001 From: Arpit Vasani Date: Mon, 23 May 2022 14:30:28 +0530 Subject: [PATCH 39/44] gradually redirecting users to fider --- .github/ISSUE_TEMPLATE/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 49618c8e7..326d434b3 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,3 +3,6 @@ contact_links: - name: 🤔 Questions and Help url: https://discord.com/invite/6rDM4fkymF about: Reach out to us on discord or our github discussions page. + - name: 🙋‍♂️ service request + url: https://feedback.coolify.io/ + about: want to request a new service? for e.g wordpress, hasura, appwrite etc... From 750ef8077705995128b2d058d43c7c92dbc878c6 Mon Sep 17 00:00:00 2001 From: Arpit Vasani Date: Sat, 28 May 2022 13:08:52 +0000 Subject: [PATCH 40/44] adding support for github codespaces. --- .devcontainer/Dockerfile | 16 ++++++++++++++++ .devcontainer/devcontainer.json | 31 +++++++++++++++++++++++++++++++ CONTRIBUTING.md | 2 +- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..0505d67e3 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,16 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node/.devcontainer/base.Dockerfile + +# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster +ARG VARIANT="16-bullseye" +FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment if you want to install an additional version of node using nvm +# ARG EXTRA_NODE_VERSION=10 +# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" + +# [Optional] Uncomment if you want to install more global node modules +RUN su node -c "npm install -g pnpm" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..124e3b01b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node +{ + "name": "Node.js", + "build": { + "dockerfile": "Dockerfile", + // Update 'VARIANT' to pick a Node version: 18, 16, 14. + // Append -bullseye or -buster to pin to an OS version. + // Use -bullseye variants on local arm64/Apple Silicon. + "args": { "VARIANT": "16-bullseye" } + }, + + // Set *default* container specific settings.json values on container create. + "settings": {}, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": ["dbaeumer.vscode-eslint"], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "yarn install", + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "node", + "features": { + "docker-in-docker": "20.10", + "github-cli": "latest" + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2be3c4ff5..c69b1b717 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ ## 🙋 Want to help? ## 👋 Introduction -🔴 At the moment, Coolify **doesn't support Windows**. You must use Linux or MacOS. +> 🔴 At the moment, Coolify **doesn't support Windows**. You must use Linux or MacOS. 💡 Although windows users can use github codespaces for development #### Recommended Pull Request Guideline From 4b0370ac088b9db8faa85f2e49254fd3a9d11d5a Mon Sep 17 00:00:00 2001 From: Arpit Vasani Date: Sat, 28 May 2022 14:58:52 +0000 Subject: [PATCH 41/44] Let's see if this works --- .devcontainer/devcontainer.json | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 124e3b01b..952b30b58 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,25 +7,27 @@ // Update 'VARIANT' to pick a Node version: 18, 16, 14. // Append -bullseye or -buster to pin to an OS version. // Use -bullseye variants on local arm64/Apple Silicon. - "args": { "VARIANT": "16-bullseye" } + "args": { + "VARIANT": "16-bullseye" + } }, - // Set *default* container specific settings.json values on container create. "settings": {}, - // Add the IDs of extensions you want installed when the container is created. - "extensions": ["dbaeumer.vscode-eslint"], - + "extensions": [ + "dbaeumer.vscode-eslint" + ], // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "yarn install", - + "postCreateCommand": [ + "cp .env.template .env ", + "pnpm install" + ], // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "node", "features": { "docker-in-docker": "20.10", "github-cli": "latest" } -} +} \ No newline at end of file From d18b2b6a1f7e8a33d4f432a4f408edfbd369ced3 Mon Sep 17 00:00:00 2001 From: Arpit Vasani Date: Sat, 28 May 2022 20:33:47 +0530 Subject: [PATCH 42/44] Update devcontainer.json --- .devcontainer/devcontainer.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 952b30b58..4765fcea1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,14 +20,11 @@ // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": [ - "cp .env.template .env ", - "pnpm install" - ], + "postCreateCommand": "cp .env.template .env && pnpm install", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "node", "features": { "docker-in-docker": "20.10", "github-cli": "latest" } -} \ No newline at end of file +} From 6e22fecc98c70de6dff0342a451d52f01e34cab2 Mon Sep 17 00:00:00 2001 From: Arpit Vasani Date: Sat, 28 May 2022 15:19:49 +0000 Subject: [PATCH 43/44] adding more commands --- .devcontainer/devcontainer.json | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4765fcea1..f898e8827 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,13 +14,11 @@ // Set *default* container specific settings.json values on container create. "settings": {}, // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "dbaeumer.vscode-eslint" - ], + "extensions": ["dbaeumer.vscode-eslint", "svelte.svelte-vscode"], // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], + "forwardPorts": [3000], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "cp .env.template .env && pnpm install", + "postCreateCommand": "cp .env.template .env && pnpm install && pnpm db:push && pnpm db:seed", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "node", "features": { From 69f5601b3e05ecb2305448ce05927c0a386d7f8c Mon Sep 17 00:00:00 2001 From: Arpit Vasani Date: Sat, 28 May 2022 16:50:45 +0000 Subject: [PATCH 44/44] Updating docs and removing redundant information --- CONTRIBUTING.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c69b1b717..62f919153 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,6 +15,12 @@ ## 🙋 Want to help? ## 👋 Introduction +### Setup with github codespaces + +If you have github codespaces enabled then you can just create a codespace and run `pnpm dev` to run your the dev environment. All the required dependencies and packages has been configured for you already. + +### Setup locally in your machine + > 🔴 At the moment, Coolify **doesn't support Windows**. You must use Linux or MacOS. 💡 Although windows users can use github codespaces for development #### Recommended Pull Request Guideline @@ -35,20 +41,16 @@ # How to start after you set up your local fork? You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally. -#### Setup a local development environment +#### Steps for local setup -- Copy `.env.template` to `.env` and set the `COOLIFY_APP_ID` environment variable to something cool. -- Install dependencies with `pnpm install`. -- Need to create a local SQlite database with `pnpm db:push`. - - This will apply all migrations at `db/dev.db`. -- Seed the database with base entities with `pnpm db:seed` -- You can start coding after starting `pnpm dev`. +1. Copy `.env.template` to `.env` and set the `COOLIFY_APP_ID` environment variable to something cool. +2. Install dependencies with `pnpm install`. +3. Need to create a local SQlite database with `pnpm db:push`. -#### How to start after you set up your local fork? + This will apply all migrations at `db/dev.db`. -This repository works better with [pnpm](https://pnpm.io) due to the lock file. I recommend you to give it a try and use `pnpm` as well because it is cool and efficient! - -You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally. +4. Seed the database with base entities with `pnpm db:seed` +5. You can start coding after starting `pnpm dev`. ## 🧑‍💻 Developer contribution