diff --git a/package.json b/package.json index db46808ff..10db9ccc2 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "2.0.4", + "version": "2.0.5", "license": "AGPL-3.0", "scripts": { - "dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0", - "dev:stop": "docker-compose -f docker-compose-dev.yaml down", - "dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10", + "dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0", + "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", "build": "svelte-kit build", diff --git a/src/lib/components/svg/services/VaultWarden.svelte b/src/lib/components/svg/services/VaultWarden.svelte new file mode 100644 index 000000000..3e4f6b8b5 --- /dev/null +++ b/src/lib/components/svg/services/VaultWarden.svelte @@ -0,0 +1,35 @@ + + + + + + + diff --git a/src/lib/database/common.ts b/src/lib/database/common.ts index c79e9d101..c94dadd28 100644 --- a/src/lib/database/common.ts +++ b/src/lib/database/common.ts @@ -116,6 +116,12 @@ export const supportedServiceTypesAndVersions = [ fancyName: 'Wordpress', baseImage: 'wordpress', versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'] + }, + { + name: 'vaultwarden', + fancyName: 'Vaultwarden', + baseImage: 'vaultwarden/server', + versions: ['latest'] } ]; diff --git a/src/lib/database/services.ts b/src/lib/database/services.ts index 6b7ac2f3f..61cf70316 100644 --- a/src/lib/database/services.ts +++ b/src/lib/database/services.ts @@ -99,6 +99,13 @@ export async function configureServiceType({ id, type }) { wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } } } }); + } else if (type === 'vaultwarden') { + await prisma.service.update({ + where: { id }, + data: { + type + } + }); } } export async function setService({ id, version }) { @@ -115,6 +122,9 @@ export async function updatePlausibleAnalyticsService({ id, fqdn, email, usernam export async function updateNocoDbOrMinioService({ id, fqdn, name }) { return await prisma.service.update({ where: { id }, data: { fqdn, name } }); } +export async function updateVaultWardenService({ id, fqdn, name }) { + return await prisma.service.update({ where: { id }, data: { fqdn, name } }); +} export async function updateVsCodeServer({ id, fqdn, name }) { return await prisma.service.update({ where: { id }, data: { fqdn, name } }); } diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index 853bc38b6..7679fe28f 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -602,11 +602,7 @@ export async function configureNetworkCoolifyProxy(engine) { export async function configureSimpleServiceProxyOn({ id, domain, port }) { const haproxy = await haproxyInstance(); - try { - await checkHAProxy(haproxy); - } catch (error) { - return; - } + await checkHAProxy(haproxy); try { await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json(); return; diff --git a/src/routes/services/[id]/configuration/type.svelte b/src/routes/services/[id]/configuration/type.svelte index 5fb0a0559..398f6a4d5 100644 --- a/src/routes/services/[id]/configuration/type.svelte +++ b/src/routes/services/[id]/configuration/type.svelte @@ -36,6 +36,7 @@ import Wordpress from '$lib/components/svg/services/Wordpress.svelte'; import { goto } from '$app/navigation'; import { post } from '$lib/api'; + import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte'; const { id } = $page.params; const from = $page.url.searchParams.get('from'); @@ -71,6 +72,8 @@ {:else if type.name === 'wordpress'} + {:else if type.name === 'vaultwarden'} + {/if}{type.fancyName} diff --git a/src/routes/services/[id]/index.svelte b/src/routes/services/[id]/index.svelte index 06dace107..1682e5b25 100644 --- a/src/routes/services/[id]/index.svelte +++ b/src/routes/services/[id]/index.svelte @@ -36,6 +36,7 @@ import Wordpress from '$lib/components/svg/services/Wordpress.svelte'; import Services from './_Services/_Services.svelte'; import { getDomain } from '$lib/components/common'; + import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte'; export let service; export let isRunning; @@ -94,6 +95,10 @@ + {:else if service.type === 'vaultwarden'} + + + {/if} diff --git a/src/routes/services/[id]/vaultwarden/index.json.ts b/src/routes/services/[id]/vaultwarden/index.json.ts new file mode 100644 index 000000000..49d89b207 --- /dev/null +++ b/src/routes/services/[id]/vaultwarden/index.json.ts @@ -0,0 +1,20 @@ +import { getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } 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 } = await event.request.json(); + if (fqdn) fqdn = fqdn.toLowerCase(); + + try { + await db.updateVaultWardenService({ id, fqdn, name }); + return { status: 201 }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/vaultwarden/start.json.ts b/src/routes/services/[id]/vaultwarden/start.json.ts new file mode 100644 index 000000000..a84366092 --- /dev/null +++ b/src/routes/services/[id]/vaultwarden/start.json.ts @@ -0,0 +1,83 @@ +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 { letsEncrypt } from '$lib/letsencrypt'; +import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy'; +import { getDomain } from '$lib/components/common'; +import { getServiceImage, PrismaErrorHandler } from '$lib/database'; + +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, fqdn, destinationDockerId, destinationDocker } = service; + + const domain = getDomain(fqdn); + const isHttps = fqdn.startsWith('https://'); + + const network = destinationDockerId && destinationDocker.network; + const host = getEngine(destinationDocker.engine); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + const baseImage = getServiceImage(type); + + const config = { + image: `${baseImage}:${version}`, + volume: `${id}-vaultwarden-data:/data/` + }; + + const composeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image: config.image, + networks: [network], + volumes: [config.volume], + restart: 'always' + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [config.volume.split(':')[0]]: { + external: true + } + } + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + try { + await asyncExecShell( + `DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}` + ); + } catch (error) { + console.log(error); + } + try { + await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + await configureSimpleServiceProxyOn({ id, domain, port: 80 }); + + if (isHttps) { + await letsEncrypt({ domain, id }); + } + await reloadHaproxy(destinationDocker.engine); + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/[id]/vaultwarden/stop.json.ts b/src/routes/services/[id]/vaultwarden/stop.json.ts new file mode 100644 index 000000000..64a3141b3 --- /dev/null +++ b/src/routes/services/[id]/vaultwarden/stop.json.ts @@ -0,0 +1,39 @@ +import { getUserDetails, removeDestinationDocker } from '$lib/common'; +import { getDomain } from '$lib/components/common'; +import * as db from '$lib/database'; +import { PrismaErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import { checkContainer, configureSimpleServiceProxyOff } 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; + const domain = getDomain(fqdn); + if (destinationDockerId) { + const engine = destinationDocker.engine; + + try { + const found = await checkContainer(engine, id); + if (found) { + await removeDestinationDocker({ id, engine }); + } + } catch (error) { + console.error(error); + } + await configureSimpleServiceProxyOff({ domain }); + } + + return { + status: 200 + }; + } catch (error) { + return PrismaErrorHandler(error); + } +}; diff --git a/src/routes/services/index.svelte b/src/routes/services/index.svelte index 1800c5ed1..997c8d313 100644 --- a/src/routes/services/index.svelte +++ b/src/routes/services/index.svelte @@ -25,6 +25,7 @@ import MinIo from '$lib/components/svg/services/MinIO.svelte'; import VsCodeServer from '$lib/components/svg/services/VSCodeServer.svelte'; import Wordpress from '$lib/components/svg/services/Wordpress.svelte'; + import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte'; export let services; @@ -67,6 +68,8 @@ {:else if service.type === 'wordpress'} + {:else if service.type === 'vaultwarden'} + {/if}
{service.name}