2022-08-23 11:29:25 +02:00
import { exec } from 'node:child_process'
2022-07-06 11:02:36 +02:00
import util from 'util' ;
import fs from 'fs/promises' ;
import yaml from 'js-yaml' ;
import forge from 'node-forge' ;
import { uniqueNamesGenerator , adjectives , colors , animals } from 'unique-names-generator' ;
import type { Config } from 'unique-names-generator' ;
import generator from 'generate-password' ;
import crypto from 'crypto' ;
import { promises as dns } from 'dns' ;
import { PrismaClient } from '@prisma/client' ;
import cuid from 'cuid' ;
2022-07-20 13:35:26 +00:00
import os from 'os' ;
import sshConfig from 'ssh-config'
2022-07-06 11:02:36 +02:00
2022-07-25 12:42:10 +00:00
import { checkContainer , removeContainer } from './docker' ;
2022-07-06 11:02:36 +02:00
import { day } from './dayjs' ;
2022-08-29 15:29:00 +02:00
import * as serviceFields from './services/serviceFields'
2022-08-23 11:29:25 +02:00
import { saveBuildLog } from './buildPacks/common' ;
2022-08-24 09:39:24 +02:00
import { scheduler } from './scheduler' ;
2022-08-29 15:29:00 +02:00
import { supportedServiceTypesAndVersions } from './services/supportedVersions' ;
import { includeServices } from './services/common' ;
2022-07-06 11:02:36 +02:00
2022-09-07 10:23:08 +02:00
export const version = '3.10.0' ;
2022-07-08 13:38:19 +02:00
export const isDev = process . env . NODE_ENV === 'development' ;
2022-07-06 11:02:36 +02:00
const algorithm = 'aes-256-ctr' ;
const customConfig : Config = {
dictionaries : [ adjectives , colors , animals ] ,
style : 'capital' ,
separator : ' ' ,
length : 3
} ;
export const defaultProxyImage = ` coolify-haproxy-alpine:latest ` ;
export const defaultProxyImageTcp = ` coolify-haproxy-tcp-alpine:latest ` ;
export const defaultProxyImageHttp = ` coolify-haproxy-http-alpine:latest ` ;
2022-07-22 12:01:07 +00:00
export const defaultTraefikImage = ` traefik:v2.8 ` ;
2022-07-12 13:08:47 +00:00
export function getAPIUrl() {
if ( process . env . GITPOD_WORKSPACE_URL ) {
const { href } = new URL ( process . env . GITPOD_WORKSPACE_URL )
const newURL = href . replace ( 'https://' , 'https://3001-' ) . replace ( /\/$/ , '' )
return newURL
}
2022-08-12 09:38:02 +02:00
if ( process . env . CODESANDBOX_HOST ) {
return ` https:// ${ process . env . CODESANDBOX_HOST . replace ( /\$PORT/ , '3001' ) } `
2022-08-11 08:18:17 +00:00
}
2022-07-12 13:08:47 +00:00
return isDev ? 'http://localhost:3001' : 'http://localhost:3000' ;
}
2022-08-11 08:18:17 +00:00
2022-07-12 13:08:47 +00:00
export function getUIUrl() {
if ( process . env . GITPOD_WORKSPACE_URL ) {
const { href } = new URL ( process . env . GITPOD_WORKSPACE_URL )
const newURL = href . replace ( 'https://' , 'https://3000-' ) . replace ( /\/$/ , '' )
return newURL
}
2022-08-12 09:38:02 +02:00
if ( process . env . CODESANDBOX_HOST ) {
return ` https:// ${ process . env . CODESANDBOX_HOST . replace ( /\$PORT/ , '3000' ) } `
2022-08-11 08:18:17 +00:00
}
2022-07-12 13:08:47 +00:00
return 'http://localhost:3000' ;
}
2022-07-06 11:02:36 +02:00
const mainTraefikEndpoint = isDev
2022-07-12 13:08:47 +00:00
? ` ${ getAPIUrl ( ) } /webhooks/traefik/main.json `
2022-07-06 11:02:36 +02:00
: 'http://coolify:3000/webhooks/traefik/main.json' ;
const otherTraefikEndpoint = isDev
2022-07-12 13:08:47 +00:00
? ` ${ getAPIUrl ( ) } /webhooks/traefik/other.json `
2022-07-06 11:02:36 +02:00
: 'http://coolify:3000/webhooks/traefik/other.json' ;
export const uniqueName = ( ) : string = > uniqueNamesGenerator ( customConfig ) ;
2022-08-23 11:29:25 +02:00
export const asyncExecShell = util . promisify ( exec ) ;
export const asyncExecShellStream = async ( { debug , buildId , applicationId , command , engine } : { debug : boolean , buildId : string , applicationId : string , command : string , engine : string } ) = > {
return await new Promise ( async ( resolve , reject ) = > {
const { execaCommand } = await import ( 'execa' )
const subprocess = execaCommand ( command , { env : { DOCKER_BUILDKIT : "1" , DOCKER_HOST : engine } } )
if ( debug ) {
subprocess . stdout . on ( 'data' , async ( data ) = > {
const stdout = data . toString ( ) ;
const array = stdout . split ( '\n' )
for ( const line of array ) {
if ( line !== '\n' && line !== '' ) {
await saveBuildLog ( {
line : ` ${ line . replace ( '\n' , '' ) } ` ,
buildId ,
applicationId
} ) ;
}
}
} )
subprocess . stderr . on ( 'data' , async ( data ) = > {
const stderr = data . toString ( ) ;
const array = stderr . split ( '\n' )
for ( const line of array ) {
if ( line !== '\n' && line !== '' ) {
await saveBuildLog ( {
line : ` ${ line . replace ( '\n' , '' ) } ` ,
buildId ,
applicationId
} ) ;
}
}
} )
}
subprocess . on ( 'exit' , async ( code ) = > {
await asyncSleep ( 1000 ) ;
if ( code === 0 ) {
resolve ( code )
} else {
reject ( code )
}
} )
} )
}
2022-07-06 11:02:36 +02:00
export const asyncSleep = ( delay : number ) : Promise < unknown > = >
new Promise ( ( resolve ) = > setTimeout ( resolve , delay ) ) ;
export const prisma = new PrismaClient ( {
2022-08-25 12:49:09 +00:00
errorFormat : 'minimal' ,
2022-08-26 09:49:17 +02:00
// log: [
// {
// emit: 'event',
// level: 'query',
// },
// {
// emit: 'stdout',
// level: 'error',
// },
// {
// emit: 'stdout',
// level: 'info',
// },
// {
// emit: 'stdout',
// level: 'warn',
// },
// ],
2022-07-06 11:02:36 +02:00
} ) ;
2022-08-26 09:49:17 +02:00
// prisma.$on('query', (e) => {
2022-09-01 14:01:31 +02:00
// console.log({e})
// console.log('Query: ' + e.query)
// console.log('Params: ' + e.params)
// console.log('Duration: ' + e.duration + 'ms')
2022-08-26 09:49:17 +02:00
// })
2022-07-06 11:02:36 +02:00
export const base64Encode = ( text : string ) : string = > {
return Buffer . from ( text ) . toString ( 'base64' ) ;
} ;
export const base64Decode = ( text : string ) : string = > {
return Buffer . from ( text , 'base64' ) . toString ( 'ascii' ) ;
} ;
export const decrypt = ( hashString : string ) = > {
if ( hashString ) {
2022-08-12 19:39:03 +00:00
try {
const hash = JSON . parse ( hashString ) ;
const decipher = crypto . createDecipheriv (
algorithm ,
process . env [ 'COOLIFY_SECRET_KEY' ] ,
Buffer . from ( hash . iv , 'hex' )
) ;
const decrpyted = Buffer . concat ( [
decipher . update ( Buffer . from ( hash . content , 'hex' ) ) ,
decipher . final ( )
] ) ;
return decrpyted . toString ( ) ;
} catch ( error ) {
console . log ( { decryptionError : error.message } )
return hashString
}
2022-07-06 11:02:36 +02:00
}
} ;
export const encrypt = ( text : string ) = > {
if ( text ) {
const iv = crypto . randomBytes ( 16 ) ;
const cipher = crypto . createCipheriv ( algorithm , process . env [ 'COOLIFY_SECRET_KEY' ] , iv ) ;
const encrypted = Buffer . concat ( [ cipher . update ( text ) , cipher . final ( ) ] ) ;
return JSON . stringify ( {
iv : iv.toString ( 'hex' ) ,
content : encrypted.toString ( 'hex' )
} ) ;
}
} ;
2022-07-15 15:40:26 +02:00
2022-08-29 15:29:00 +02:00
2022-07-15 15:40:26 +02:00
2022-07-06 11:02:36 +02:00
export async function checkDoubleBranch ( branch : string , projectId : number ) : Promise < boolean > {
const applications = await prisma . application . findMany ( { where : { branch , projectId } } ) ;
return applications . length > 1 ;
}
export async function isDNSValid ( hostname : any , domain : string ) : Promise < any > {
const { isIP } = await import ( 'is-ip' ) ;
2022-08-17 10:43:57 +02:00
const { DNSServers } = await listSettings ( ) ;
if ( DNSServers ) {
2022-09-07 20:14:49 +00:00
dns . setServers ( [ . . . DNSServers . split ( ',' ) ] ) ;
2022-08-17 10:43:57 +02:00
}
2022-07-06 11:02:36 +02:00
let resolves = [ ] ;
try {
if ( isIP ( hostname ) ) {
resolves = [ hostname ] ;
} else {
resolves = await dns . resolve4 ( hostname ) ;
}
} catch ( error ) {
throw 'Invalid DNS.'
}
try {
let ipDomainFound = false ;
const dnsResolve = await dns . resolve4 ( domain ) ;
if ( dnsResolve . length > 0 ) {
for ( const ip of dnsResolve ) {
if ( resolves . includes ( ip ) ) {
ipDomainFound = true ;
}
}
}
if ( ! ipDomainFound ) throw false ;
} catch ( error ) {
throw 'DNS not set'
}
}
export function getDomain ( domain : string ) : string {
return domain ? . replace ( 'https://' , '' ) . replace ( 'http://' , '' ) ;
}
export async function isDomainConfigured ( {
id ,
fqdn ,
2022-07-25 10:16:25 +00:00
checkOwn = false ,
2022-08-06 09:21:16 +00:00
remoteIpAddress = undefined
2022-07-06 11:02:36 +02:00
} : {
id : string ;
fqdn : string ;
checkOwn? : boolean ;
2022-08-06 09:21:16 +00:00
remoteIpAddress? : string ;
2022-07-06 11:02:36 +02:00
} ) : Promise < boolean > {
const domain = getDomain ( fqdn ) ;
const nakedDomain = domain . replace ( 'www.' , '' ) ;
const foundApp = await prisma . application . findFirst ( {
where : {
OR : [
{ fqdn : { endsWith : ` // ${ nakedDomain } ` } } ,
{ fqdn : { endsWith : ` //www. ${ nakedDomain } ` } }
] ,
2022-07-25 10:16:25 +00:00
id : { not : id } ,
destinationDocker : {
2022-08-06 09:21:16 +00:00
remoteIpAddress ,
2022-07-25 10:16:25 +00:00
}
2022-07-06 11:02:36 +02:00
} ,
select : { fqdn : true }
} ) ;
const foundService = await prisma . service . findFirst ( {
where : {
OR : [
{ fqdn : { endsWith : ` // ${ nakedDomain } ` } } ,
{ fqdn : { endsWith : ` //www. ${ nakedDomain } ` } } ,
{ minio : { apiFqdn : { endsWith : ` // ${ nakedDomain } ` } } } ,
{ minio : { apiFqdn : { endsWith : ` //www. ${ nakedDomain } ` } } }
] ,
2022-07-25 10:16:25 +00:00
id : { not : checkOwn ? undefined : id } ,
destinationDocker : {
2022-08-06 09:21:16 +00:00
remoteIpAddress
2022-07-25 10:16:25 +00:00
}
2022-07-06 11:02:36 +02:00
} ,
select : { fqdn : true }
} ) ;
const coolifyFqdn = await prisma . setting . findFirst ( {
where : {
OR : [
{ fqdn : { endsWith : ` // ${ nakedDomain } ` } } ,
{ fqdn : { endsWith : ` //www. ${ nakedDomain } ` } }
] ,
id : { not : id }
} ,
select : { fqdn : true }
} ) ;
return ! ! ( foundApp || foundService || coolifyFqdn ) ;
}
2022-07-20 13:35:26 +00:00
export async function getContainerUsage ( dockerId : string , container : string ) : Promise < any > {
2022-07-06 11:02:36 +02:00
try {
2022-07-20 13:35:26 +00:00
const { stdout } = await executeDockerCmd ( { dockerId , command : ` docker container stats ${ container } --no-stream --no-trunc --format "{{json .}}" ` } )
2022-07-06 11:02:36 +02:00
return JSON . parse ( stdout ) ;
} catch ( err ) {
return {
MemUsage : 0 ,
CPUPerc : 0 ,
NetIO : 0
} ;
}
}
export async function checkDomainsIsValidInDNS ( { hostname , fqdn , dualCerts } ) : Promise < any > {
const { isIP } = await import ( 'is-ip' ) ;
const domain = getDomain ( fqdn ) ;
const domainDualCert = domain . includes ( 'www.' ) ? domain . replace ( 'www.' , '' ) : ` www. ${ domain } ` ;
2022-08-17 10:43:57 +02:00
const { DNSServers } = await listSettings ( ) ;
if ( DNSServers ) {
2022-09-07 20:14:49 +00:00
dns . setServers ( [ . . . DNSServers . split ( ',' ) ] ) ;
2022-08-17 10:43:57 +02:00
}
2022-07-06 11:02:36 +02:00
let resolves = [ ] ;
try {
if ( isIP ( hostname ) ) {
resolves = [ hostname ] ;
} else {
resolves = await dns . resolve4 ( hostname ) ;
}
} catch ( error ) {
2022-07-06 19:34:16 +02:00
throw { status : 500 , message : ` Could not determine IP address for ${ hostname } . ` }
2022-07-06 11:02:36 +02:00
}
if ( dualCerts ) {
try {
const ipDomain = await dns . resolve4 ( domain ) ;
const ipDomainDualCert = await dns . resolve4 ( domainDualCert ) ;
let ipDomainFound = false ;
let ipDomainDualCertFound = false ;
for ( const ip of ipDomain ) {
if ( resolves . includes ( ip ) ) {
ipDomainFound = true ;
}
}
for ( const ip of ipDomainDualCert ) {
if ( resolves . includes ( ip ) ) {
ipDomainDualCertFound = true ;
}
}
if ( ipDomainFound && ipDomainDualCertFound ) return { status : 200 } ;
2022-07-06 19:04:54 +02:00
throw { status : 500 , message : ` DNS not set correctly or propogated.<br>Please check your DNS settings. ` }
2022-07-06 11:02:36 +02:00
} catch ( error ) {
2022-07-06 19:04:54 +02:00
throw { status : 500 , message : ` DNS not set correctly or propogated.<br>Please check your DNS settings. ` }
2022-07-06 11:02:36 +02:00
}
} else {
try {
const ipDomain = await dns . resolve4 ( domain ) ;
let ipDomainFound = false ;
for ( const ip of ipDomain ) {
if ( resolves . includes ( ip ) ) {
ipDomainFound = true ;
}
}
if ( ipDomainFound ) return { status : 200 } ;
2022-07-06 19:04:54 +02:00
throw { status : 500 , message : ` DNS not set correctly or propogated.<br>Please check your DNS settings. ` }
2022-07-06 11:02:36 +02:00
} catch ( error ) {
2022-07-06 19:04:54 +02:00
throw { status : 500 , message : ` DNS not set correctly or propogated.<br>Please check your DNS settings. ` }
2022-07-06 11:02:36 +02:00
}
}
}
export function generateTimestamp ( ) : string {
return ` ${ day ( ) . format ( 'HH:mm:ss.SSS' ) } ` ;
}
export async function listServicesWithIncludes ( ) : Promise < any > {
return await prisma . service . findMany ( {
2022-08-29 15:29:00 +02:00
include : includeServices ,
2022-07-06 11:02:36 +02:00
orderBy : { createdAt : 'desc' }
} ) ;
}
export const supportedDatabaseTypesAndVersions = [
{
name : 'mongodb' ,
fancyName : 'MongoDB' ,
baseImage : 'bitnami/mongodb' ,
2022-08-12 12:13:52 +02:00
baseImageARM : 'mongo' ,
versions : [ '5.0' , '4.4' , '4.2' ] ,
versionsARM : [ '5.0' , '4.4' , '4.2' ]
2022-07-06 11:02:36 +02:00
} ,
2022-08-12 11:48:38 +02:00
{
name : 'mysql' ,
fancyName : 'MySQL' ,
baseImage : 'bitnami/mysql' ,
baseImageARM : 'mysql' ,
versions : [ '8.0' , '5.7' ] ,
versionsARM : [ '8.0' , '5.7' ]
2022-07-06 11:02:36 +02:00
} ,
{
name : 'mariadb' ,
fancyName : 'MariaDB' ,
baseImage : 'bitnami/mariadb' ,
2022-08-12 11:48:38 +02:00
baseImageARM : 'mariadb' ,
versions : [ '10.8' , '10.7' , '10.6' , '10.5' , '10.4' , '10.3' , '10.2' ] ,
versionsARM : [ '10.8' , '10.7' , '10.6' , '10.5' , '10.4' , '10.3' , '10.2' ]
2022-07-06 11:02:36 +02:00
} ,
{
name : 'postgresql' ,
fancyName : 'PostgreSQL' ,
baseImage : 'bitnami/postgresql' ,
2022-08-12 11:48:38 +02:00
baseImageARM : 'postgres' ,
versions : [ '14.5.0' , '13.8.0' , '12.12.0' , '11.17.0' , '10.22.0' ] ,
versionsARM : [ '14.5' , '13.8' , '12.12' , '11.17' , '10.22' ]
2022-07-06 11:02:36 +02:00
} ,
{
name : 'redis' ,
fancyName : 'Redis' ,
baseImage : 'bitnami/redis' ,
2022-08-12 11:48:38 +02:00
baseImageARM : 'redis' ,
versions : [ '7.0' , '6.2' , '6.0' , '5.0' ] ,
versionsARM : [ '7.0' , '6.2' , '6.0' , '5.0' ]
2022-07-06 11:02:36 +02:00
} ,
2022-07-31 19:50:25 +02:00
{
2022-08-12 11:48:38 +02:00
name : 'couchdb' ,
fancyName : 'CouchDB' ,
baseImage : 'bitnami/couchdb' ,
baseImageARM : 'couchdb' ,
versions : [ '3.2.2' , '3.1.2' , '2.3.1' ] ,
versionsARM : [ '3.2.2' , '3.1.2' , '2.3.1' ]
2022-09-07 10:20:40 +02:00
} ,
2022-09-07 11:02:13 +02:00
{
2022-07-31 19:50:25 +02:00
name : 'edgedb' ,
fancyName : 'EdgeDB' ,
baseImage : 'edgedb/edgedb' ,
2022-09-07 11:02:13 +02:00
versions : [ 'latest' , '2.1' , '2.0' , '1.4' ]
}
2022-07-06 11:02:36 +02:00
] ;
2022-08-07 12:42:20 +00:00
2022-08-22 19:30:09 +00:00
export async function getFreeSSHLocalPort ( id : string ) : Promise < number | boolean > {
const { default : isReachable } = await import ( 'is-port-reachable' ) ;
2022-08-07 12:42:20 +00:00
const { remoteIpAddress , sshLocalPort } = await prisma . destinationDocker . findUnique ( { where : { id } } )
if ( sshLocalPort ) {
return Number ( sshLocalPort )
}
2022-08-22 19:30:09 +00:00
const data = await prisma . setting . findFirst ( ) ;
const { minPort , maxPort } = data ;
2022-08-07 12:42:20 +00:00
const ports = await prisma . destinationDocker . findMany ( { where : { sshLocalPort : { not : null } , remoteIpAddress : { not : remoteIpAddress } } } )
2022-08-22 19:30:09 +00:00
const alreadyConfigured = await prisma . destinationDocker . findFirst ( {
where : {
remoteIpAddress , id : { not : id } , sshLocalPort : { not : null }
}
} )
2022-08-07 12:42:20 +00:00
if ( alreadyConfigured ? . sshLocalPort ) {
await prisma . destinationDocker . update ( { where : { id } , data : { sshLocalPort : alreadyConfigured.sshLocalPort } } )
return Number ( alreadyConfigured . sshLocalPort )
}
2022-08-22 19:30:09 +00:00
const range = generateRangeArray ( minPort , maxPort )
const availablePorts = range . filter ( port = > ! ports . map ( p = > p . sshLocalPort ) . includes ( port ) )
for ( const port of availablePorts ) {
const found = await isReachable ( port , { host : 'localhost' } )
if ( ! found ) {
await prisma . destinationDocker . update ( { where : { id } , data : { sshLocalPort : Number ( port ) } } )
return Number ( port )
}
}
return false
2022-08-07 12:42:20 +00:00
}
2022-07-20 13:35:26 +00:00
export async function createRemoteEngineConfiguration ( id : string ) {
const homedir = os . homedir ( ) ;
const sshKeyFile = ` /tmp/id_rsa- ${ id } `
2022-08-07 12:42:20 +00:00
const localPort = await getFreeSSHLocalPort ( id ) ;
2022-07-20 13:35:26 +00:00
const { sshKey : { privateKey } , remoteIpAddress , remotePort , remoteUser } = await prisma . destinationDocker . findFirst ( { where : { id } , include : { sshKey : true } } )
await fs . writeFile ( sshKeyFile , decrypt ( privateKey ) + '\n' , { encoding : 'utf8' , mode : 400 } )
2022-07-21 12:43:53 +00:00
// Needed for remote docker compose
2022-09-05 08:14:50 +02:00
const { stdout : numberOfSSHAgentsRunning } = await asyncExecShell ( ` ps ax | grep [s]sh-agent | grep coolify-ssh-agent.pid | grep -v grep | wc -l ` )
2022-07-27 13:11:46 +00:00
if ( numberOfSSHAgentsRunning !== '' && Number ( numberOfSSHAgentsRunning . trim ( ) ) == 0 ) {
2022-09-05 12:59:06 +02:00
try {
await fs . stat ( ` /tmp/coolify-ssh-agent.pid ` )
await fs . rm ( ` /tmp/coolify-ssh-agent.pid ` )
} catch ( error ) { }
2022-09-05 08:37:54 +02:00
await asyncExecShell ( ` eval $ (ssh-agent -sa /tmp/coolify-ssh-agent.pid) ` )
2022-07-27 13:11:46 +00:00
}
2022-09-05 08:14:50 +02:00
await asyncExecShell ( ` SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh-add -q ${ sshKeyFile } ` )
2022-07-06 11:02:36 +02:00
2022-08-07 12:42:20 +00:00
const { stdout : numberOfSSHTunnelsRunning } = await asyncExecShell ( ` ps ax | grep 'ssh -F /dev/null -o StrictHostKeyChecking no -fNL ${ localPort } :localhost: ${ remotePort } ' | grep -v grep | wc -l ` )
2022-07-27 13:57:42 +00:00
if ( numberOfSSHTunnelsRunning !== '' && Number ( numberOfSSHTunnelsRunning . trim ( ) ) == 0 ) {
try {
2022-09-05 08:14:50 +02:00
await asyncExecShell ( ` SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh -F /dev/null -o "StrictHostKeyChecking no" -fNL ${ localPort } :localhost: ${ remotePort } ${ remoteUser } @ ${ remoteIpAddress } ` )
2022-09-06 08:01:04 +02:00
} catch ( error ) { }
2022-07-27 13:57:42 +00:00
}
2022-07-20 13:35:26 +00:00
const config = sshConfig . parse ( '' )
const found = config . find ( { Host : remoteIpAddress } )
2022-07-06 11:02:36 +02:00
if ( ! found ) {
2022-07-20 13:35:26 +00:00
config . append ( {
Host : remoteIpAddress ,
2022-07-27 13:57:42 +00:00
Hostname : 'localhost' ,
2022-08-23 10:11:38 +02:00
Port : localPort.toString ( ) ,
2022-07-20 13:35:26 +00:00
User : remoteUser ,
IdentityFile : sshKeyFile ,
StrictHostKeyChecking : 'no'
} )
}
try {
await fs . stat ( ` ${ homedir } /.ssh/ ` )
} catch ( error ) {
await fs . mkdir ( ` ${ homedir } /.ssh/ ` )
}
2022-08-07 12:42:20 +00:00
return await fs . writeFile ( ` ${ homedir } /.ssh/config ` , sshConfig . stringify ( config ) )
2022-07-20 13:35:26 +00:00
}
2022-08-23 11:29:25 +02:00
export async function executeDockerCmd ( { debug , buildId , applicationId , dockerId , command } : { debug? : boolean , buildId? : string , applicationId? : string , dockerId : string , command : string } ) : Promise < any > {
2022-09-08 09:32:50 +02:00
const { execaCommand } = await import ( 'execa' )
2022-07-27 13:57:42 +00:00
let { remoteEngine , remoteIpAddress , engine } = await prisma . destinationDocker . findUnique ( { where : { id : dockerId } } )
2022-07-25 12:42:10 +00:00
if ( remoteEngine ) {
2022-07-20 13:35:26 +00:00
await createRemoteEngineConfiguration ( dockerId )
2022-07-27 13:57:42 +00:00
engine = ` ssh:// ${ remoteIpAddress } `
2022-07-25 12:42:10 +00:00
} else {
engine = 'unix:///var/run/docker.sock'
2022-07-20 13:35:26 +00:00
}
2022-08-15 09:27:54 +00:00
if ( process . env . CODESANDBOX_HOST ) {
if ( command . startsWith ( 'docker compose' ) ) {
command = command . replace ( /docker compose/gi , 'docker-compose' )
}
}
2022-08-23 11:29:25 +02:00
if ( command . startsWith ( ` docker build --progress plain ` ) ) {
return await asyncExecShellStream ( { debug , buildId , applicationId , command , engine } ) ;
}
2022-09-08 09:32:50 +02:00
return await execaCommand ( command , { env : { DOCKER_BUILDKIT : "1" , DOCKER_HOST : engine } , shell : true } )
2022-07-20 13:35:26 +00:00
}
export async function startTraefikProxy ( id : string ) : Promise < void > {
2022-07-22 20:23:16 +00:00
const { engine , network , remoteEngine , remoteIpAddress } = await prisma . destinationDocker . findUnique ( { where : { id } } )
2022-07-20 13:35:26 +00:00
const found = await checkContainer ( { dockerId : id , container : 'coolify-proxy' , remove : true } ) ;
2022-07-22 21:14:12 +00:00
const { id : settingsId , ipv4 , ipv6 } = await listSettings ( ) ;
2022-07-06 11:02:36 +02:00
if ( ! found ) {
2022-08-15 10:28:57 +00:00
const { stdout : coolifyNetwork } = await executeDockerCmd ( { dockerId : id , command : ` docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}" ` } )
2022-08-15 14:58:10 +00:00
2022-08-15 10:28:57 +00:00
if ( ! coolifyNetwork ) {
await executeDockerCmd ( { dockerId : id , command : ` docker network create --attachable coolify-infra ` } )
}
2022-07-20 13:35:26 +00:00
const { stdout : Config } = await executeDockerCmd ( { dockerId : id , command : ` docker network inspect ${ network } --format '{{json .IPAM.Config }}' ` } )
2022-07-06 11:02:36 +02:00
const ip = JSON . parse ( Config ) [ 0 ] . Gateway ;
2022-07-22 20:23:16 +00:00
let traefikUrl = mainTraefikEndpoint
if ( remoteEngine ) {
let ip = null
if ( isDev ) {
ip = getAPIUrl ( )
} else {
2022-07-22 21:28:01 +00:00
ip = ` http:// ${ ipv4 || ipv6 } :3000 `
2022-07-22 20:23:16 +00:00
}
traefikUrl = ` ${ ip } /webhooks/traefik/remote/ ${ id } `
}
2022-07-20 13:35:26 +00:00
await executeDockerCmd ( {
dockerId : id ,
command : ` docker run --restart always \
2022-07-06 11:02:36 +02:00
-- add - host 'host.docker.internal:host-gateway' \
2022-07-20 13:35:26 +00:00
$ { ip ? ` --add-host 'host.docker.internal: ${ ip } ' ` : '' } \
2022-07-06 11:02:36 +02:00
- v coolify - traefik - letsencrypt : / e t c / t r a e f i k / a c m e \
- v / var / r u n / d o c k e r . s o c k : / v a r / r u n / d o c k e r . s o c k \
-- network coolify - infra \
- p "80:80" \
- p "443:443" \
-- name coolify - proxy \
- d $ { defaultTraefikImage } \
-- entrypoints . web . address = : 80 \
-- entrypoints . web . forwardedHeaders . insecure = true \
-- entrypoints . websecure . address = : 443 \
-- entrypoints . websecure . forwardedHeaders . insecure = true \
-- providers . docker = true \
-- providers . docker . exposedbydefault = false \
2022-07-22 20:23:16 +00:00
-- providers . http . endpoint = $ { traefikUrl } \
2022-07-06 11:02:36 +02:00
-- providers . http . pollTimeout = 5 s \
-- certificatesresolvers . letsencrypt . acme . httpchallenge = true \
-- certificatesresolvers . letsencrypt . acme . storage = / e t c / t r a e f i k / a c m e / a c m e . j s o n \
-- certificatesresolvers . letsencrypt . acme . httpchallenge . entrypoint = web \
-- log . level = error `
2022-07-20 13:35:26 +00:00
} )
await prisma . setting . update ( { where : { id : settingsId } , data : { proxyHash : null } } ) ;
await prisma . destinationDocker . update ( {
where : { id } ,
2022-07-06 11:02:36 +02:00
data : { isCoolifyProxyUsed : true }
} ) ;
}
2022-07-22 20:23:16 +00:00
// Configure networks for local docker engine
if ( engine ) {
const destinations = await prisma . destinationDocker . findMany ( { where : { engine } } ) ;
for ( const destination of destinations ) {
await configureNetworkTraefikProxy ( destination ) ;
}
}
// Configure networks for remote docker engine
if ( remoteEngine ) {
const destinations = await prisma . destinationDocker . findMany ( { where : { remoteIpAddress } } ) ;
for ( const destination of destinations ) {
await configureNetworkTraefikProxy ( destination ) ;
}
}
2022-07-06 11:02:36 +02:00
}
2022-07-22 20:23:16 +00:00
export async function configureNetworkTraefikProxy ( destination : any ) : Promise < void > {
const { id } = destination
2022-07-20 13:35:26 +00:00
const { stdout : networks } = await executeDockerCmd ( {
2022-07-21 12:43:53 +00:00
dockerId : id ,
2022-07-20 13:35:26 +00:00
command :
` docker ps -a --filter name=coolify-proxy --format '{{json .Networks}}' `
} ) ;
2022-07-06 11:02:36 +02:00
const configuredNetworks = networks . replace ( /"/g , '' ) . replace ( '\n' , '' ) . split ( ',' ) ;
2022-07-22 20:23:16 +00:00
if ( ! configuredNetworks . includes ( destination . network ) ) {
await executeDockerCmd ( { dockerId : destination.id , command : ` docker network connect ${ destination . network } coolify-proxy ` } )
2022-07-06 11:02:36 +02:00
}
}
export async function stopTraefikProxy (
2022-07-20 13:35:26 +00:00
id : string
2022-07-06 11:02:36 +02:00
) : Promise < { stdout : string ; stderr : string } | Error > {
2022-07-20 13:35:26 +00:00
const found = await checkContainer ( { dockerId : id , container : 'coolify-proxy' } ) ;
await prisma . destinationDocker . update ( {
where : { id } ,
2022-07-06 11:02:36 +02:00
data : { isCoolifyProxyUsed : false }
} ) ;
2022-07-20 13:35:26 +00:00
const { id : settingsId } = await prisma . setting . findFirst ( { } ) ;
await prisma . setting . update ( { where : { id : settingsId } , data : { proxyHash : null } } ) ;
2022-07-06 11:02:36 +02:00
try {
if ( found ) {
2022-07-20 13:35:26 +00:00
await executeDockerCmd ( {
dockerId : id ,
command :
` docker stop -t 0 coolify-proxy && docker rm coolify-proxy `
} ) ;
2022-07-06 11:02:36 +02:00
}
} catch ( error ) {
return error ;
}
}
export async function listSettings ( ) : Promise < any > {
const settings = await prisma . setting . findFirst ( { } ) ;
if ( settings . proxyPassword ) settings . proxyPassword = decrypt ( settings . proxyPassword ) ;
return settings ;
}
2022-08-23 10:11:38 +02:00
export function generatePassword ( { length = 24 , symbols = false , isHex = false } : { length? : number , symbols? : boolean , isHex? : boolean } | null ) : string {
if ( isHex ) {
return crypto . randomBytes ( length ) . toString ( "hex" ) ;
}
const password = generator . generate ( {
2022-07-06 11:02:36 +02:00
length ,
numbers : true ,
strict : true ,
symbols
} ) ;
2022-08-23 10:11:38 +02:00
return password ;
2022-07-06 11:02:36 +02:00
}
2022-09-07 11:02:13 +02:00
type DatabaseConfiguration = {
volume : string ;
image : string ;
command? : string ;
ulimits : Record < string , unknown > ;
privatePort : number ;
environmentVariables : {
MYSQL_DATABASE : string ;
MYSQL_PASSWORD : string ;
MYSQL_ROOT_USER : string ;
MYSQL_USER : string ;
MYSQL_ROOT_PASSWORD : string ;
} ;
}
2022-07-06 11:02:36 +02:00
| {
volume : string ;
image : string ;
2022-08-12 11:48:38 +02:00
command? : string ;
2022-07-06 11:02:36 +02:00
ulimits : Record < string , unknown > ;
privatePort : number ;
environmentVariables : {
2022-08-12 12:13:52 +02:00
MONGO_INITDB_ROOT_USERNAME? : string ;
MONGO_INITDB_ROOT_PASSWORD? : string ;
MONGODB_ROOT_USER? : string ;
MONGODB_ROOT_PASSWORD? : string ;
2022-07-06 11:02:36 +02:00
} ;
}
| {
volume : string ;
image : string ;
2022-08-12 11:48:38 +02:00
command? : string ;
2022-07-06 11:02:36 +02:00
ulimits : Record < string , unknown > ;
privatePort : number ;
environmentVariables : {
MARIADB_ROOT_USER : string ;
MARIADB_ROOT_PASSWORD : string ;
MARIADB_USER : string ;
MARIADB_PASSWORD : string ;
MARIADB_DATABASE : string ;
} ;
}
| {
volume : string ;
image : string ;
2022-08-12 11:48:38 +02:00
command? : string ;
2022-07-06 11:02:36 +02:00
ulimits : Record < string , unknown > ;
privatePort : number ;
environmentVariables : {
2022-09-07 11:02:13 +02:00
POSTGRES_PASSWORD? : string ;
POSTGRES_USER? : string ;
POSTGRES_DB? : string ;
POSTGRESQL_POSTGRES_PASSWORD? : string ;
POSTGRESQL_USERNAME? : string ;
POSTGRESQL_PASSWORD? : string ;
POSTGRESQL_DATABASE? : string ;
2022-08-12 11:48:38 +02:00
} ;
}
| {
volume : string ;
image : string ;
command? : string ;
2022-07-06 11:02:36 +02:00
ulimits : Record < string , unknown > ;
privatePort : number ;
environmentVariables : {
REDIS_AOF_ENABLED : string ;
REDIS_PASSWORD : string ;
} ;
}
| {
volume : string ;
image : string ;
2022-08-12 11:48:38 +02:00
command? : string ;
2022-07-06 11:02:36 +02:00
ulimits : Record < string , unknown > ;
privatePort : number ;
environmentVariables : {
COUCHDB_PASSWORD : string ;
COUCHDB_USER : string ;
} ;
2022-09-07 11:02:13 +02:00
}
2022-07-31 19:50:25 +02:00
| {
volume : string ;
image : string ;
2022-09-07 11:02:13 +02:00
command? : string ;
2022-07-31 19:50:25 +02:00
ulimits : Record < string , unknown > ;
privatePort : number ;
environmentVariables : {
2022-09-07 11:02:13 +02:00
EDGEDB_SERVER_PASSWORD : string ;
EDGEDB_SERVER_USER : string ;
EDGEDB_SERVER_DATABASE : string ;
2022-09-07 11:40:58 +02:00
EDGEDB_SERVER_TLS_CERT_MODE : string ;
2022-07-31 19:50:25 +02:00
} ;
2022-09-07 11:02:13 +02:00
}
export function generateDatabaseConfiguration ( database : any , arch : string ) : DatabaseConfiguration {
2022-07-06 11:02:36 +02:00
const {
id ,
dbUser ,
dbUserPassword ,
rootUser ,
rootUserPassword ,
defaultDatabase ,
version ,
type ,
settings : { appendOnly }
} = database ;
2022-08-12 11:48:38 +02:00
const baseImage = getDatabaseImage ( type , arch ) ;
2022-07-06 11:02:36 +02:00
if ( type === 'mysql' ) {
2022-08-12 11:48:38 +02:00
const configuration = {
2022-07-06 11:02:36 +02:00
privatePort : 3306 ,
environmentVariables : {
MYSQL_USER : dbUser ,
MYSQL_PASSWORD : dbUserPassword ,
MYSQL_ROOT_PASSWORD : rootUserPassword ,
MYSQL_ROOT_USER : rootUser ,
MYSQL_DATABASE : defaultDatabase
} ,
image : ` ${ baseImage } : ${ version } ` ,
volume : ` ${ id } - ${ type } -data:/bitnami/mysql/data ` ,
ulimits : { }
2022-08-12 11:48:38 +02:00
}
2022-08-12 11:54:06 +02:00
if ( isARM ( arch ) ) {
2022-08-12 11:48:38 +02:00
configuration . volume = ` ${ id } - ${ type } -data:/var/lib/mysql ` ;
}
return configuration
2022-07-06 11:02:36 +02:00
} else if ( type === 'mariadb' ) {
2022-09-07 11:02:13 +02:00
const configuration : DatabaseConfiguration = {
2022-07-06 11:02:36 +02:00
privatePort : 3306 ,
environmentVariables : {
MARIADB_ROOT_USER : rootUser ,
MARIADB_ROOT_PASSWORD : rootUserPassword ,
MARIADB_USER : dbUser ,
MARIADB_PASSWORD : dbUserPassword ,
MARIADB_DATABASE : defaultDatabase
} ,
image : ` ${ baseImage } : ${ version } ` ,
volume : ` ${ id } - ${ type } -data:/bitnami/mariadb ` ,
ulimits : { }
} ;
2022-08-12 11:54:06 +02:00
if ( isARM ( arch ) ) {
2022-08-12 11:48:38 +02:00
configuration . volume = ` ${ id } - ${ type } -data:/var/lib/mysql ` ;
}
return configuration
2022-07-06 11:02:36 +02:00
} else if ( type === 'mongodb' ) {
2022-09-07 11:02:13 +02:00
const configuration : DatabaseConfiguration = {
2022-07-06 11:02:36 +02:00
privatePort : 27017 ,
environmentVariables : {
MONGODB_ROOT_USER : rootUser ,
MONGODB_ROOT_PASSWORD : rootUserPassword
} ,
image : ` ${ baseImage } : ${ version } ` ,
volume : ` ${ id } - ${ type } -data:/bitnami/mongodb ` ,
ulimits : { }
} ;
2022-08-12 12:13:52 +02:00
if ( isARM ( arch ) ) {
configuration . environmentVariables = {
MONGO_INITDB_ROOT_USERNAME : rootUser ,
MONGO_INITDB_ROOT_PASSWORD : rootUserPassword
}
configuration . volume = ` ${ id } - ${ type } -data:/data/db ` ;
}
return configuration
2022-07-06 11:02:36 +02:00
} else if ( type === 'postgresql' ) {
2022-09-07 11:02:13 +02:00
const configuration : DatabaseConfiguration = {
2022-07-06 11:02:36 +02:00
privatePort : 5432 ,
environmentVariables : {
POSTGRESQL_POSTGRES_PASSWORD : rootUserPassword ,
POSTGRESQL_PASSWORD : dbUserPassword ,
POSTGRESQL_USERNAME : dbUser ,
POSTGRESQL_DATABASE : defaultDatabase
} ,
image : ` ${ baseImage } : ${ version } ` ,
volume : ` ${ id } - ${ type } -data:/bitnami/postgresql ` ,
ulimits : { }
2022-08-12 11:48:38 +02:00
}
2022-08-12 11:54:06 +02:00
if ( isARM ( arch ) ) {
2022-08-12 11:48:38 +02:00
configuration . volume = ` ${ id } - ${ type } -data:/var/lib/postgresql ` ;
2022-08-15 09:27:54 +00:00
configuration . environmentVariables = {
POSTGRES_PASSWORD : dbUserPassword ,
POSTGRES_USER : dbUser ,
POSTGRES_DB : defaultDatabase
}
2022-08-12 11:48:38 +02:00
}
return configuration
2022-07-06 11:02:36 +02:00
} else if ( type === 'redis' ) {
2022-09-07 11:02:13 +02:00
const configuration : DatabaseConfiguration = {
2022-07-06 11:02:36 +02:00
privatePort : 6379 ,
2022-08-12 11:48:38 +02:00
command : undefined ,
2022-07-06 11:02:36 +02:00
environmentVariables : {
REDIS_PASSWORD : dbUserPassword ,
REDIS_AOF_ENABLED : appendOnly ? 'yes' : 'no'
} ,
image : ` ${ baseImage } : ${ version } ` ,
volume : ` ${ id } - ${ type } -data:/bitnami/redis/data ` ,
ulimits : { }
} ;
2022-08-12 11:54:06 +02:00
if ( isARM ( arch ) ) {
2022-08-12 11:48:38 +02:00
configuration . volume = ` ${ id } - ${ type } -data:/data ` ;
configuration . command = ` /usr/local/bin/redis-server --appendonly ${ appendOnly ? 'yes' : 'no' } --requirepass ${ dbUserPassword } ` ;
}
return configuration
2022-07-06 11:02:36 +02:00
} else if ( type === 'couchdb' ) {
2022-09-07 11:02:13 +02:00
const configuration : DatabaseConfiguration = {
2022-07-06 11:02:36 +02:00
privatePort : 5984 ,
environmentVariables : {
COUCHDB_PASSWORD : dbUserPassword ,
COUCHDB_USER : dbUser
} ,
image : ` ${ baseImage } : ${ version } ` ,
volume : ` ${ id } - ${ type } -data:/bitnami/couchdb ` ,
ulimits : { }
} ;
2022-09-07 11:02:13 +02:00
if ( isARM ( arch ) ) {
2022-08-12 11:48:38 +02:00
configuration . volume = ` ${ id } - ${ type } -data:/opt/couchdb/data ` ;
}
return configuration
2022-07-31 19:50:25 +02:00
} else if ( type === 'edgedb' ) {
2022-09-07 11:02:13 +02:00
const configuration : DatabaseConfiguration = {
privatePort : 5656 ,
environmentVariables : {
EDGEDB_SERVER_PASSWORD : rootUserPassword ,
EDGEDB_SERVER_USER : rootUser ,
EDGEDB_SERVER_DATABASE : defaultDatabase ,
2022-09-07 11:40:58 +02:00
EDGEDB_SERVER_TLS_CERT_MODE : 'generate_self_signed'
2022-09-07 11:02:13 +02:00
} ,
image : ` ${ baseImage } : ${ version } ` ,
volume : ` ${ id } - ${ type } -data:/var/lib/edgedb/data ` ,
ulimits : { }
2022-07-31 19:50:25 +02:00
} ;
2022-09-07 11:02:13 +02:00
return configuration
2022-07-06 11:02:36 +02:00
}
}
2022-08-14 20:02:18 +00:00
export function isARM ( arch : string ) {
2022-08-12 11:48:38 +02:00
if ( arch === 'arm' || arch === 'arm64' ) {
return true
}
return false
}
export function getDatabaseImage ( type : string , arch : string ) : string {
2022-07-06 11:02:36 +02:00
const found = supportedDatabaseTypesAndVersions . find ( ( t ) = > t . name === type ) ;
if ( found ) {
2022-08-12 11:54:06 +02:00
if ( isARM ( arch ) ) {
2022-08-12 11:48:38 +02:00
return found . baseImageARM || found . baseImage
}
2022-07-06 11:02:36 +02:00
return found . baseImage ;
}
return '' ;
}
2022-08-12 11:48:38 +02:00
export function getDatabaseVersions ( type : string , arch : string ) : string [ ] {
2022-07-06 11:02:36 +02:00
const found = supportedDatabaseTypesAndVersions . find ( ( t ) = > t . name === type ) ;
if ( found ) {
2022-08-12 11:54:06 +02:00
if ( isARM ( arch ) ) {
2022-08-12 11:48:38 +02:00
return found . versionsARM || found . versions
}
2022-07-06 11:02:36 +02:00
return found . versions ;
}
return [ ] ;
}
export type ComposeFile = {
version : ComposerFileVersion ;
services : Record < string , ComposeFileService > ;
networks : Record < string , ComposeFileNetwork > ;
volumes? : Record < string , ComposeFileVolume > ;
} ;
export type ComposeFileService = {
container_name : string ;
image? : string ;
networks : string [ ] ;
environment? : Record < string , unknown > ;
volumes? : string [ ] ;
ulimits? : unknown ;
labels? : string [ ] ;
env_file? : string [ ] ;
extra_hosts? : string [ ] ;
restart : ComposeFileRestartOption ;
depends_on? : string [ ] ;
command? : string ;
ports? : string [ ] ;
build ? : {
context : string ;
dockerfile : string ;
args? : Record < string , unknown > ;
2022-07-14 12:47:26 +00:00
} | string ;
2022-07-06 11:02:36 +02:00
deploy ? : {
restart_policy ? : {
condition? : string ;
delay? : string ;
max_attempts? : number ;
window ? : string ;
} ;
} ;
} ;
export type ComposerFileVersion =
| '3.8'
| '3.7'
| '3.6'
| '3.5'
| '3.4'
| '3.3'
| '3.2'
| '3.1'
| '3.0'
| '2.4'
| '2.3'
| '2.2'
| '2.1'
| '2.0' ;
export type ComposeFileRestartOption = 'no' | 'always' | 'on-failure' | 'unless-stopped' ;
export type ComposeFileNetwork = {
external : boolean ;
} ;
export type ComposeFileVolume = {
external? : boolean ;
name? : string ;
} ;
export async function makeLabelForStandaloneDatabase ( { id , image , volume } ) {
const database = await prisma . database . findFirst ( { where : { id } } ) ;
delete database . destinationDockerId ;
delete database . createdAt ;
delete database . updatedAt ;
return [
'coolify.managed=true' ,
` coolify.version= ${ version } ` ,
` coolify.type=standalone-database ` ,
` coolify.configuration= ${ base64Encode (
JSON . stringify ( {
version ,
image ,
volume ,
. . . database
} )
) } `
] ;
}
export const createDirectories = async ( {
repository ,
buildId
} : {
repository : string ;
buildId : string ;
} ) : Promise < { workdir : string ; repodir : string } > = > {
const repodir = ` /tmp/build-sources/ ${ repository } / ` ;
const workdir = ` /tmp/build-sources/ ${ repository } / ${ buildId } ` ;
2022-09-06 14:32:50 +02:00
let workdirFound = false ;
2022-09-06 13:23:53 +02:00
try {
2022-09-06 14:32:50 +02:00
workdirFound = ! ! ( await fs . stat ( workdir ) ) ;
} catch ( error ) { }
if ( workdirFound ) {
2022-09-06 13:23:53 +02:00
await asyncExecShell ( ` rm -fr ${ workdir } ` ) ;
}
2022-07-06 11:02:36 +02:00
await asyncExecShell ( ` mkdir -p ${ workdir } ` ) ;
return {
workdir ,
repodir
} ;
} ;
export async function stopDatabaseContainer (
database : any
) : Promise < boolean > {
let everStarted = false ;
const {
id ,
destinationDockerId ,
2022-07-20 13:35:26 +00:00
destinationDocker : { engine , id : dockerId }
2022-07-06 11:02:36 +02:00
} = database ;
if ( destinationDockerId ) {
try {
2022-07-20 13:35:26 +00:00
const { stdout } = await executeDockerCmd ( { dockerId , command : ` docker inspect --format '{{json .State}}' ${ id } ` } )
2022-07-06 11:02:36 +02:00
if ( stdout ) {
everStarted = true ;
2022-07-20 13:35:26 +00:00
await removeContainer ( { id , dockerId } ) ;
2022-07-06 11:02:36 +02:00
}
} catch ( error ) {
//
}
}
return everStarted ;
}
export async function stopTcpHttpProxy (
id : string ,
destinationDocker : any ,
publicPort : number ,
forceName : string = null
) : Promise < { stdout : string ; stderr : string } | Error > {
2022-07-20 13:35:26 +00:00
const { id : dockerId } = destinationDocker ;
let container = ` ${ id } - ${ publicPort } ` ;
if ( forceName ) container = forceName ;
const found = await checkContainer ( { dockerId , container } ) ;
2022-07-06 11:02:36 +02:00
try {
if ( found ) {
2022-07-20 13:35:26 +00:00
return await executeDockerCmd ( {
dockerId ,
command :
` docker stop -t 0 ${ container } && docker rm ${ container } `
} ) ;
2022-07-06 11:02:36 +02:00
}
} catch ( error ) {
return error ;
}
}
export async function updatePasswordInDb ( database , user , newPassword , isRoot ) {
const {
id ,
type ,
rootUser ,
rootUserPassword ,
dbUser ,
dbUserPassword ,
defaultDatabase ,
destinationDockerId ,
2022-07-20 13:53:39 +00:00
destinationDocker : { id : dockerId }
2022-07-06 11:02:36 +02:00
} = database ;
if ( destinationDockerId ) {
if ( type === 'mysql' ) {
2022-07-20 13:53:39 +00:00
await executeDockerCmd ( {
dockerId ,
command : ` docker exec ${ id } mysql -u ${ rootUser } -p ${ rootUserPassword } -e \ "ALTER USER ' ${ user } '@'%' IDENTIFIED WITH caching_sha2_password BY ' ${ newPassword } '; \ " `
} )
2022-07-06 11:02:36 +02:00
} else if ( type === 'mariadb' ) {
2022-07-20 13:53:39 +00:00
await executeDockerCmd ( {
dockerId ,
command : ` docker exec ${ id } mysql -u ${ rootUser } -p ${ rootUserPassword } -e \ "SET PASSWORD FOR ' ${ user } '@'%' = PASSWORD(' ${ newPassword } '); \ " `
} )
2022-07-06 11:02:36 +02:00
} else if ( type === 'postgresql' ) {
if ( isRoot ) {
2022-07-20 13:53:39 +00:00
await executeDockerCmd ( {
dockerId ,
command : ` docker exec ${ id } psql postgresql://postgres: ${ rootUserPassword } @ ${ id } :5432/ ${ defaultDatabase } -c "ALTER role postgres WITH PASSWORD ' ${ newPassword } '" `
} )
2022-07-06 11:02:36 +02:00
} else {
2022-07-20 13:53:39 +00:00
await executeDockerCmd ( {
dockerId ,
command : ` docker exec ${ id } psql postgresql:// ${ dbUser } : ${ dbUserPassword } @ ${ id } :5432/ ${ defaultDatabase } -c "ALTER role ${ user } WITH PASSWORD ' ${ newPassword } '" `
} )
2022-07-06 11:02:36 +02:00
}
} else if ( type === 'mongodb' ) {
2022-07-20 13:53:39 +00:00
await executeDockerCmd ( {
dockerId ,
command : ` docker exec ${ id } mongo 'mongodb:// ${ rootUser } : ${ rootUserPassword } @ ${ id } :27017/admin?readPreference=primary&ssl=false' --eval "db.changeUserPassword(' ${ user } ',' ${ newPassword } ')" `
} )
2022-07-06 11:02:36 +02:00
} else if ( type === 'redis' ) {
2022-07-20 13:53:39 +00:00
await executeDockerCmd ( {
dockerId ,
command : ` docker exec ${ id } redis-cli -u redis:// ${ dbUserPassword } @ ${ id } :6379 --raw CONFIG SET requirepass ${ newPassword } `
} )
2022-07-06 11:02:36 +02:00
}
}
}
2022-08-18 15:29:59 +02:00
export async function checkExposedPort ( { id , configuredPort , exposePort , dockerId , remoteIpAddress } : { id : string , configuredPort? : number , exposePort : number , dockerId : string , remoteIpAddress? : string } ) {
if ( exposePort < 1024 || exposePort > 65535 ) {
throw { status : 500 , message : ` Exposed Port needs to be between 1024 and 65535. ` }
}
if ( configuredPort ) {
if ( configuredPort !== exposePort ) {
const availablePort = await getFreeExposedPort ( id , exposePort , dockerId , remoteIpAddress ) ;
if ( availablePort . toString ( ) !== exposePort . toString ( ) ) {
throw { status : 500 , message : ` Port ${ exposePort } is already in use. ` }
}
}
} else {
const availablePort = await getFreeExposedPort ( id , exposePort , dockerId , remoteIpAddress ) ;
if ( availablePort . toString ( ) !== exposePort . toString ( ) ) {
throw { status : 500 , message : ` Port ${ exposePort } is already in use. ` }
}
}
}
2022-07-22 12:01:07 +00:00
export async function getFreeExposedPort ( id , exposePort , dockerId , remoteIpAddress ) {
2022-08-22 19:30:09 +00:00
const { default : checkPort } = await import ( 'is-port-reachable' ) ;
2022-07-22 08:57:11 +00:00
const applicationUsed = await (
await prisma . application . findMany ( {
2022-07-22 12:01:07 +00:00
where : { exposePort : { not : null } , id : { not : id } , destinationDockerId : dockerId } ,
2022-07-22 08:57:11 +00:00
select : { exposePort : true }
} )
) . map ( ( a ) = > a . exposePort ) ;
const serviceUsed = await (
await prisma . service . findMany ( {
2022-07-22 12:01:07 +00:00
where : { exposePort : { not : null } , id : { not : id } , destinationDockerId : dockerId } ,
2022-07-22 08:57:11 +00:00
select : { exposePort : true }
} )
) . map ( ( a ) = > a . exposePort ) ;
const usedPorts = [ . . . applicationUsed , . . . serviceUsed ] ;
2022-08-22 19:30:09 +00:00
if ( usedPorts . includes ( exposePort ) ) {
2022-07-22 12:01:07 +00:00
return false
}
2022-08-22 19:30:09 +00:00
const found = await checkPort ( exposePort , { host : remoteIpAddress || 'localhost' } ) ;
if ( ! found ) {
return exposePort
}
return false
2022-07-06 11:02:36 +02:00
2022-07-22 08:57:11 +00:00
}
2022-08-22 19:30:09 +00:00
export function generateRangeArray ( start , end ) {
return Array . from ( { length : ( end - start ) } , ( v , k ) = > k + start ) ;
}
2022-07-22 12:01:07 +00:00
export async function getFreePublicPort ( id , dockerId ) {
2022-08-22 19:30:09 +00:00
const { default : isReachable } = await import ( 'is-port-reachable' ) ;
2022-07-06 11:02:36 +02:00
const data = await prisma . setting . findFirst ( ) ;
const { minPort , maxPort } = data ;
const dbUsed = await (
await prisma . database . findMany ( {
2022-07-22 12:01:07 +00:00
where : { publicPort : { not : null } , id : { not : id } , destinationDockerId : dockerId } ,
2022-07-06 11:02:36 +02:00
select : { publicPort : true }
} )
) . map ( ( a ) = > a . publicPort ) ;
const wpFtpUsed = await (
await prisma . wordpress . findMany ( {
2022-07-22 12:01:07 +00:00
where : { ftpPublicPort : { not : null } , id : { not : id } , service : { destinationDockerId : dockerId } } ,
2022-07-06 11:02:36 +02:00
select : { ftpPublicPort : true }
} )
) . map ( ( a ) = > a . ftpPublicPort ) ;
const wpUsed = await (
await prisma . wordpress . findMany ( {
2022-07-22 12:01:07 +00:00
where : { mysqlPublicPort : { not : null } , id : { not : id } , service : { destinationDockerId : dockerId } } ,
2022-07-06 11:02:36 +02:00
select : { mysqlPublicPort : true }
} )
) . map ( ( a ) = > a . mysqlPublicPort ) ;
const minioUsed = await (
await prisma . minio . findMany ( {
2022-07-22 12:01:07 +00:00
where : { publicPort : { not : null } , id : { not : id } , service : { destinationDockerId : dockerId } } ,
2022-07-06 11:02:36 +02:00
select : { publicPort : true }
} )
) . map ( ( a ) = > a . publicPort ) ;
const usedPorts = [ . . . dbUsed , . . . wpFtpUsed , . . . wpUsed , . . . minioUsed ] ;
2022-08-22 19:30:09 +00:00
const range = generateRangeArray ( minPort , maxPort )
const availablePorts = range . filter ( port = > ! usedPorts . includes ( port ) )
for ( const port of availablePorts ) {
const found = await isReachable ( port , { host : 'localhost' } )
if ( ! found ) {
return port
}
}
return false
2022-07-06 11:02:36 +02:00
}
export async function startTraefikTCPProxy (
destinationDocker : any ,
id : string ,
publicPort : number ,
privatePort : number ,
type ? : string
) : Promise < { stdout : string ; stderr : string } | Error > {
2022-07-22 21:14:12 +00:00
const { network , id : dockerId , remoteEngine } = destinationDocker ;
2022-07-20 13:35:26 +00:00
const container = ` ${ id } - ${ publicPort } ` ;
const found = await checkContainer ( { dockerId , container , remove : true } ) ;
2022-07-25 12:42:10 +00:00
const { ipv4 , ipv6 } = await listSettings ( ) ;
2022-07-22 21:14:12 +00:00
2022-07-06 11:02:36 +02:00
let dependentId = id ;
if ( type === 'wordpressftp' ) dependentId = ` ${ id } -ftp ` ;
2022-07-20 13:35:26 +00:00
const foundDependentContainer = await checkContainer ( { dockerId , container : dependentId , remove : true } ) ;
2022-07-06 11:02:36 +02:00
try {
if ( foundDependentContainer && ! found ) {
2022-07-20 13:35:26 +00:00
const { stdout : Config } = await executeDockerCmd ( {
dockerId ,
command : ` docker network inspect ${ network } --format '{{json .IPAM.Config }}' `
} )
2022-07-06 11:02:36 +02:00
const ip = JSON . parse ( Config ) [ 0 ] . Gateway ;
2022-07-22 21:14:12 +00:00
let traefikUrl = otherTraefikEndpoint
if ( remoteEngine ) {
let ip = null
if ( isDev ) {
ip = getAPIUrl ( )
} else {
2022-07-22 21:34:31 +00:00
ip = ` http:// ${ ipv4 || ipv6 } :3000 `
2022-07-22 21:14:12 +00:00
}
traefikUrl = ` ${ ip } /webhooks/traefik/other.json `
}
2022-07-06 11:02:36 +02:00
const tcpProxy = {
2022-07-22 12:01:07 +00:00
version : '3.8' ,
2022-07-06 11:02:36 +02:00
services : {
[ ` ${ id } - ${ publicPort } ` ] : {
2022-07-20 13:35:26 +00:00
container_name : container ,
2022-07-22 21:14:12 +00:00
image : defaultTraefikImage ,
2022-07-06 11:02:36 +02:00
command : [
` --entrypoints.tcp.address=: ${ publicPort } ` ,
` --entryPoints.tcp.forwardedHeaders.insecure=true ` ,
2022-07-22 21:21:40 +00:00
` --providers.http.endpoint= ${ traefikUrl } ?id= ${ id } &privatePort= ${ privatePort } &publicPort= ${ publicPort } &type=tcp&address= ${ dependentId } ` ,
2022-07-06 11:02:36 +02:00
'--providers.http.pollTimeout=2s' ,
'--log.level=error'
] ,
ports : [ ` ${ publicPort } : ${ publicPort } ` ] ,
2022-07-20 13:35:26 +00:00
extra_hosts : [ 'host.docker.internal:host-gateway' , ` host.docker.internal: ${ ip } ` ] ,
2022-07-06 11:02:36 +02:00
volumes : [ '/var/run/docker.sock:/var/run/docker.sock' ] ,
networks : [ 'coolify-infra' , network ]
}
} ,
networks : {
[ network ] : {
external : false ,
name : network
} ,
'coolify-infra' : {
external : false ,
name : 'coolify-infra'
}
}
} ;
await fs . writeFile ( ` /tmp/docker-compose- ${ id } .yaml ` , yaml . dump ( tcpProxy ) ) ;
2022-07-20 13:35:26 +00:00
await executeDockerCmd ( {
dockerId ,
command : ` docker compose -f /tmp/docker-compose- ${ id } .yaml up -d `
} )
2022-07-06 11:02:36 +02:00
await fs . rm ( ` /tmp/docker-compose- ${ id } .yaml ` ) ;
}
if ( ! foundDependentContainer && found ) {
2022-07-20 13:35:26 +00:00
await executeDockerCmd ( {
dockerId ,
command : ` docker stop -t 0 ${ container } && docker rm ${ container } `
} )
2022-07-06 11:02:36 +02:00
}
} catch ( error ) {
return error ;
}
}
export async function getServiceFromDB ( { id , teamId } : { id : string ; teamId : string } ) : Promise < any > {
const settings = await prisma . setting . findFirst ( ) ;
const body = await prisma . service . findFirst ( {
where : { id , teams : { some : { id : teamId === '0' ? undefined : teamId } } } ,
2022-08-30 09:19:34 +02:00
include : includeServices
2022-07-06 11:02:36 +02:00
} ) ;
let { type } = body
type = fixType ( type )
if ( body ? . serviceSecret . length > 0 ) {
body . serviceSecret = body . serviceSecret . map ( ( s ) = > {
s . value = decrypt ( s . value ) ;
return s ;
} ) ;
}
2022-08-12 19:29:53 +00:00
2022-07-06 11:02:36 +02:00
body [ type ] = { . . . body [ type ] , . . . getUpdateableFields ( type , body [ type ] ) }
return { . . . body , settings } ;
}
export function getServiceImage ( type : string ) : string {
const found = supportedServiceTypesAndVersions . find ( ( t ) = > t . name === type ) ;
if ( found ) {
return found . baseImage ;
}
return '' ;
}
export function getServiceImages ( type : string ) : string [ ] {
const found = supportedServiceTypesAndVersions . find ( ( t ) = > t . name === type ) ;
if ( found ) {
return found . images ;
}
return [ ] ;
}
export function saveUpdateableFields ( type : string , data : any ) {
2022-08-11 08:18:17 +00:00
const update = { } ;
2022-07-06 11:02:36 +02:00
if ( type && serviceFields [ type ] ) {
serviceFields [ type ] . map ( ( k ) = > {
let temp = data [ k . name ]
if ( temp ) {
if ( k . isEncrypted ) {
temp = encrypt ( temp )
}
if ( k . isLowerCase ) {
temp = temp . toLowerCase ( )
}
if ( k . isNumber ) {
temp = Number ( temp )
}
if ( k . isBoolean ) {
temp = Boolean ( temp )
}
}
2022-08-31 15:02:36 +02:00
if ( k . isNumber && temp === '' ) {
temp = null
2022-09-01 14:01:31 +02:00
}
2022-07-06 11:02:36 +02:00
update [ k . name ] = temp
} ) ;
}
return update
}
export function getUpdateableFields ( type : string , data : any ) {
2022-08-11 08:18:17 +00:00
const update = { } ;
2022-07-06 11:02:36 +02:00
if ( type && serviceFields [ type ] ) {
serviceFields [ type ] . map ( ( k ) = > {
let temp = data [ k . name ]
if ( temp ) {
if ( k . isEncrypted ) {
temp = decrypt ( temp )
}
update [ k . name ] = temp
}
update [ k . name ] = temp
} ) ;
}
return update
}
export function fixType ( type ) {
// Hack to fix the type case sensitivity...
if ( type === 'plausibleanalytics' ) type = 'plausibleAnalytics' ;
if ( type === 'meilisearch' ) type = 'meiliSearch' ;
return type
}
export const getServiceMainPort = ( service : string ) = > {
const serviceType = supportedServiceTypesAndVersions . find ( ( s ) = > s . name === service ) ;
if ( serviceType ) {
return serviceType . ports . main ;
}
return null ;
} ;
export function makeLabelForServices ( type ) {
return [
'coolify.managed=true' ,
2022-09-02 14:11:36 +02:00
` coolify.version= ${ version } ` ,
` coolify.type=service ` ,
` coolify.service.type= ${ type } `
2022-07-06 11:02:36 +02:00
] ;
}
export function errorHandler ( { status = 500 , message = 'Unknown error.' } : { status : number , message : string | any } ) {
if ( message . message ) message = message . message
throw { status , message } ;
}
export async function generateSshKeyPair ( ) : Promise < { publicKey : string ; privateKey : string } > {
return await new Promise ( ( resolve , reject ) = > {
forge . pki . rsa . generateKeyPair ( { bits : 4096 , workers : - 1 } , function ( err , keys ) {
if ( keys ) {
resolve ( {
publicKey : forge.ssh.publicKeyToOpenSSH ( keys . publicKey ) ,
privateKey : forge.ssh.privateKeyToOpenSSH ( keys . privateKey )
} ) ;
} else {
reject ( keys ) ;
}
} ) ;
} ) ;
}
export async function stopBuild ( buildId , applicationId ) {
let count = 0 ;
await new Promise < void > ( async ( resolve , reject ) = > {
const { destinationDockerId , status } = await prisma . build . findFirst ( { where : { id : buildId } } ) ;
2022-08-25 10:04:46 +02:00
const { id : dockerId } = await prisma . destinationDocker . findFirst ( { where : { id : destinationDockerId } } ) ;
2022-08-11 08:18:17 +00:00
const interval = setInterval ( async ( ) = > {
2022-07-06 11:02:36 +02:00
try {
2022-08-24 10:35:28 +02:00
if ( status === 'failed' || status === 'canceled' ) {
2022-07-06 11:02:36 +02:00
clearInterval ( interval ) ;
return resolve ( ) ;
}
2022-08-24 10:35:28 +02:00
if ( count > 15 ) {
2022-07-06 11:02:36 +02:00
clearInterval ( interval ) ;
2022-08-24 09:39:24 +02:00
if ( scheduler . workers . has ( 'deployApplication' ) ) {
2022-08-25 10:04:46 +02:00
scheduler . workers . get ( 'deployApplication' ) . postMessage ( 'cancel' )
2022-08-24 09:39:24 +02:00
}
2022-08-25 10:04:46 +02:00
await cleanupDB ( buildId , applicationId ) ;
return reject ( new Error ( 'Deployment canceled.' ) ) ;
2022-07-06 11:02:36 +02:00
}
2022-08-23 08:49:32 +02:00
const { stdout : buildContainers } = await executeDockerCmd ( { dockerId , command : ` docker container ls --filter "label=coolify.buildId= ${ buildId } " --format '{{json .}}' ` } )
2022-07-06 11:02:36 +02:00
if ( buildContainers ) {
const containersArray = buildContainers . trim ( ) . split ( '\n' ) ;
for ( const container of containersArray ) {
const containerObj = JSON . parse ( container ) ;
const id = containerObj . ID ;
2022-07-20 13:35:26 +00:00
if ( ! containerObj . Names . startsWith ( ` ${ applicationId } ` ) ) {
await removeContainer ( { id , dockerId } ) ;
2022-07-06 11:02:36 +02:00
clearInterval ( interval ) ;
2022-08-24 10:35:28 +02:00
if ( scheduler . workers . has ( 'deployApplication' ) ) {
2022-08-25 10:04:46 +02:00
scheduler . workers . get ( 'deployApplication' ) . postMessage ( 'cancel' )
2022-08-24 10:35:28 +02:00
}
2022-08-25 10:04:46 +02:00
await cleanupDB ( buildId , applicationId ) ;
2022-07-06 11:02:36 +02:00
return resolve ( ) ;
}
}
}
count ++ ;
} catch ( error ) { }
} , 100 ) ;
} ) ;
}
2022-08-25 10:04:46 +02:00
async function cleanupDB ( buildId : string , applicationId : string ) {
2022-07-06 11:02:36 +02:00
const data = await prisma . build . findUnique ( { where : { id : buildId } } ) ;
if ( data ? . status === 'queued' || data ? . status === 'running' ) {
2022-08-24 10:35:28 +02:00
await prisma . build . update ( { where : { id : buildId } , data : { status : 'canceled' } } ) ;
2022-07-06 11:02:36 +02:00
}
2022-08-25 10:04:46 +02:00
await saveBuildLog ( { line : 'Deployment canceled.' , buildId , applicationId } ) ;
2022-07-13 13:40:41 +00:00
}
export function convertTolOldVolumeNames ( type ) {
if ( type === 'nocodb' ) {
return 'nc'
}
}
2022-08-24 09:39:24 +02:00
2022-07-22 15:16:51 +00:00
export async function cleanupDockerStorage ( dockerId , lowDiskSpace , force ) {
2022-07-13 13:40:41 +00:00
// Cleanup old coolify images
try {
2022-09-05 13:09:59 +02:00
let { stdout : images } = await executeDockerCmd ( { dockerId , command : ` docker images coollabsio/coolify --filter before="coollabsio/coolify: ${ version } " -q | xargs -r ` } )
2022-07-22 15:16:51 +00:00
2022-07-13 13:40:41 +00:00
images = images . trim ( ) ;
if ( images ) {
2022-09-05 13:09:59 +02:00
await executeDockerCmd ( { dockerId , command : ` docker rmi -f ${ images } " -q | xargs -r ` } )
2022-07-13 13:40:41 +00:00
}
2022-09-06 08:01:04 +02:00
} catch ( error ) { }
2022-07-13 13:40:41 +00:00
if ( lowDiskSpace || force ) {
if ( isDev ) {
if ( ! force ) console . log ( ` [DEV MODE] Low disk space: ${ lowDiskSpace } ` ) ;
return
}
try {
2022-08-11 12:08:44 +00:00
await executeDockerCmd ( { dockerId , command : ` docker container prune -f --filter "label=coolify.managed=true" ` } )
2022-09-06 08:01:04 +02:00
} catch ( error ) { }
2022-07-13 13:40:41 +00:00
try {
2022-07-22 15:16:51 +00:00
await executeDockerCmd ( { dockerId , command : ` docker image prune -f ` } )
2022-09-06 08:01:04 +02:00
} catch ( error ) { }
2022-07-13 13:40:41 +00:00
try {
2022-07-22 15:16:51 +00:00
await executeDockerCmd ( { dockerId , command : ` docker image prune -a -f ` } )
2022-09-06 08:01:04 +02:00
} catch ( error ) { }
2022-08-27 07:46:30 +00:00
// Cleanup build caches
try {
await executeDockerCmd ( { dockerId , command : ` docker builder prune -a -f ` } )
2022-09-06 08:01:04 +02:00
} catch ( error ) { }
2022-07-13 13:40:41 +00:00
}
2022-08-08 11:54:05 +00:00
}
export function persistentVolumes ( id , persistentStorage , config ) {
2022-09-02 14:11:36 +02:00
let volumeSet = new Set ( ) ;
if ( Object . keys ( config ) . length > 0 ) {
for ( const [ key , value ] of Object . entries ( config ) ) {
if ( value . volumes ) {
for ( const volume of value . volumes ) {
volumeSet . add ( volume ) ;
}
}
}
}
const volumesArray = Array . from ( volumeSet ) ;
2022-08-08 11:54:05 +00:00
const persistentVolume =
persistentStorage ? . map ( ( storage ) = > {
return ` ${ id } ${ storage . path . replace ( /\//gi , '-' ) } : ${ storage . path } ` ;
} ) || [ ] ;
2022-08-12 09:38:02 +02:00
let volumes = [ . . . persistentVolume ]
2022-09-02 14:11:36 +02:00
if ( volumesArray ) volumes = [ . . . volumesArray , . . . volumes ]
2022-08-10 11:55:27 +00:00
const composeVolumes = volumes . length > 0 && volumes . map ( ( volume ) = > {
2022-08-08 11:54:05 +00:00
return {
[ ` ${ volume . split ( ':' ) [ 0 ] } ` ] : {
name : volume.split ( ':' ) [ 0 ]
}
} ;
2022-08-10 11:55:27 +00:00
} ) || [ ]
2022-09-02 14:11:36 +02:00
const volumeMounts = Object . assign (
2022-08-08 11:54:05 +00:00
{ } ,
. . . composeVolumes
2022-08-10 11:55:27 +00:00
) || { }
2022-09-02 14:11:36 +02:00
return { volumeMounts }
2022-08-15 21:30:53 +02:00
}
2022-08-17 13:59:23 +02:00
export function defaultComposeConfiguration ( network : string ) : any {
return {
networks : [ network ] ,
restart : 'on-failure' ,
deploy : {
restart_policy : {
condition : 'on-failure' ,
delay : '5s' ,
max_attempts : 10 ,
window : '120s'
}
}
}
2022-08-18 21:47:06 +02:00
}
2022-08-27 07:46:30 +00:00
export function decryptApplication ( application : any ) {
2022-09-01 14:01:31 +02:00
if ( application ) {
if ( application ? . gitSource ? . githubApp ? . clientSecret ) {
application . gitSource . githubApp . clientSecret = decrypt ( application . gitSource . githubApp . clientSecret ) || null ;
}
if ( application ? . gitSource ? . githubApp ? . webhookSecret ) {
application . gitSource . githubApp . webhookSecret = decrypt ( application . gitSource . githubApp . webhookSecret ) || null ;
}
if ( application ? . gitSource ? . githubApp ? . privateKey ) {
application . gitSource . githubApp . privateKey = decrypt ( application . gitSource . githubApp . privateKey ) || null ;
}
if ( application ? . gitSource ? . gitlabApp ? . appSecret ) {
application . gitSource . gitlabApp . appSecret = decrypt ( application . gitSource . gitlabApp . appSecret ) || null ;
2022-07-13 13:40:41 +00:00
}
2022-09-01 14:01:31 +02:00
if ( application ? . secrets . length > 0 ) {
application . secrets = application . secrets . map ( ( s : any ) = > {
s . value = decrypt ( s . value ) || null
return s ;
} ) ;
}
return application ;
2022-07-13 13:40:41 +00:00
}
2022-08-30 16:13:07 +02:00
}