import { dev } from '$app/env'; import { asyncExecShell, getEngine, getUserDetails } from '$lib/common'; import { decrypt, encrypt } from '$lib/crypto'; import * as db from '$lib/database'; import { ErrorHandler, generatePassword, getFreePort } from '$lib/database'; 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'; import fs from 'fs/promises'; import yaml from 'js-yaml'; export const post: RequestHandler = async (event) => { const { status, body, teamId } = await getUserDetails(event); if (status === 401) return { status, body }; const { id } = event.params; const { ftpEnabled } = await event.request.json(); const publicPort = await getFreePort(); let ftpUser = cuid(); let ftpPassword = generatePassword(); const hostkeyDir = dev ? '/tmp/hostkeys' : '/app/ssl/hostkeys'; try { const data = await db.prisma.wordpress.update({ where: { serviceId: id }, data: { ftpEnabled }, include: { service: { include: { destinationDocker: true } } } }); const { service: { destinationDockerId, destinationDocker }, ftpPublicPort, ftpUser: user, ftpPassword: savedPassword, ftpHostKey, ftpHostKeyPrivate } = data; 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`); } await db.prisma.wordpress.update({ where: { serviceId: id }, data: { ftpPublicPort: publicPort, ftpUser: user ? undefined : ftpUser, ftpPassword: savedPassword ? undefined : encrypt(ftpPassword) } }); try { const isRunning = await checkContainer(engine, `${id}-ftp`); if (isRunning) { await asyncExecShell( `DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` ); } } catch (error) { console.log(error); // } const volumes = [ `${id}-wordpress-data:/home/${ftpUser}`, `${ dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' }/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`, `${ dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' }/${id}.rsa:/etc/ssh/ssh_host_rsa_key`, `${ dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' }/${id}.sh:/etc/sftp.d/chmod.sh` ]; const compose: ComposeFile = { version: '3.8', services: { [`${id}-ftp`]: { image: `atmoz/sftp:alpine`, command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`, extra_hosts: ['host.docker.internal:host-gateway'], container_name: `${id}-ftp`, volumes, networks: [network], depends_on: [], restart: 'always' } }, networks: { [network]: { external: true } }, volumes: { [`${id}-wordpress-data`]: { external: true, name: `${id}-wordpress-data` } } }; await fs.writeFile( `${hostkeyDir}/${id}.sh`, `#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key` ); await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`); await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose)); await asyncExecShell( `DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` ); } return { status: 201, body: { publicPort, ftpUser, ftpPassword } }; } 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: {} }; } } catch (error) { console.log(error); return ErrorHandler(error); } finally { await asyncExecShell( `rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh` ); } };