2022-09-06 17:37:26 +00: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' ;
2022-07-20 13:35:26 +00:00
import os from 'os' ;
2022-09-06 17:37:26 +00:00
import sshConfig from 'ssh-config' ;
2022-11-18 14:28:05 +01:00
import jsonwebtoken from 'jsonwebtoken' ;
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-23 11:29:25 +02:00
import { saveBuildLog } from './buildPacks/common' ;
2022-08-24 09:39:24 +02:00
import { scheduler } from './scheduler' ;
2022-07-06 11:02:36 +02:00
2022-11-18 11:07:52 +01:00
export const version = '3.11.11' ;
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 ) {
2022-09-06 17:37:26 +00:00
const { href } = new URL ( process . env . GITPOD_WORKSPACE_URL ) ;
const newURL = href . replace ( 'https://' , 'https://3001-' ) . replace ( /\/$/ , '' ) ;
return newURL ;
2022-07-12 13:08:47 +00:00
}
2022-08-12 09:38:02 +02:00
if ( process . env . CODESANDBOX_HOST ) {
2022-09-06 17:37:26 +00:00
return ` https:// ${ process . env . CODESANDBOX_HOST . replace ( /\$PORT/ , '3001' ) } ` ;
2022-08-11 08:18:17 +00:00
}
2022-10-28 11:54:03 +02:00
return isDev ? 'http://host.docker.internal:3001' : 'http://localhost:3000' ;
2022-07-12 13:08:47 +00:00
}
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 ) {
2022-09-06 17:37:26 +00:00
const { href } = new URL ( process . env . GITPOD_WORKSPACE_URL ) ;
const newURL = href . replace ( 'https://' , 'https://3000-' ) . replace ( /\/$/ , '' ) ;
return newURL ;
2022-07-12 13:08:47 +00:00
}
2022-08-12 09:38:02 +02:00
if ( process . env . CODESANDBOX_HOST ) {
2022-09-06 17:37:26 +00:00
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 ) ;
2022-09-06 17:37:26 +00:00
export const asyncExecShellStream = async ( {
debug ,
buildId ,
applicationId ,
command ,
engine
} : {
debug : boolean ;
buildId : string ;
applicationId : string ;
command : string ;
engine : string ;
} ) = > {
2022-08-23 11:29:25 +02:00
return await new Promise ( async ( resolve , reject ) = > {
2022-09-06 17:37:26 +00:00
const { execaCommand } = await import ( 'execa' ) ;
const subprocess = execaCommand ( command , {
env : { DOCKER_BUILDKIT : '1' , DOCKER_HOST : engine }
} ) ;
2022-09-26 10:20:53 +02:00
const logs = [ ] ;
subprocess . stdout . on ( 'data' , async ( data ) = > {
const stdout = data . toString ( ) ;
const array = stdout . split ( '\n' ) ;
for ( const line of array ) {
if ( line !== '\n' && line !== '' ) {
const log = {
line : ` ${ line . replace ( '\n' , '' ) } ` ,
buildId ,
applicationId
}
logs . push ( log ) ;
if ( debug ) {
await saveBuildLog ( log ) ;
2022-08-23 11:29:25 +02:00
}
}
2022-09-26 10:20:53 +02:00
}
} ) ;
subprocess . stderr . on ( 'data' , async ( data ) = > {
const stderr = data . toString ( ) ;
const array = stderr . split ( '\n' ) ;
for ( const line of array ) {
if ( line !== '\n' && line !== '' ) {
const log = {
line : ` ${ line . replace ( '\n' , '' ) } ` ,
buildId ,
applicationId
}
logs . push ( log ) ;
if ( debug ) {
await saveBuildLog ( log ) ;
2022-08-23 11:29:25 +02:00
}
}
2022-09-26 10:20:53 +02:00
}
} ) ;
2022-08-23 11:29:25 +02:00
subprocess . on ( 'exit' , async ( code ) = > {
await asyncSleep ( 1000 ) ;
if ( code === 0 ) {
2022-09-06 17:37:26 +00:00
resolve ( code ) ;
2022-08-23 11:29:25 +02:00
} else {
2022-09-26 10:20:53 +02:00
if ( ! debug ) {
for ( const log of logs ) {
await saveBuildLog ( log ) ;
}
}
2022-09-06 17:37:26 +00:00
reject ( code ) ;
2022-08-23 11:29:25 +02:00
}
2022-09-06 17:37:26 +00:00
} ) ;
} ) ;
} ;
2022-08-23 11:29:25 +02:00
2022-07-06 11:02:36 +02:00
export const asyncSleep = ( delay : number ) : Promise < unknown > = >
new Promise ( ( resolve ) = > setTimeout ( resolve , delay ) ) ;
2022-10-04 22:00:50 +02:00
export const prisma = new PrismaClient ( {
errorFormat : 'minimal'
// 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 ) {
2022-09-06 17:37:26 +00:00
console . log ( { decryptionError : error.message } ) ;
return hashString ;
2022-08-12 19:39:03 +00:00
}
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 ) ;
2022-10-14 15:48:37 +02:00
const encrypted = Buffer . concat ( [ cipher . update ( text . trim ( ) ) , cipher . final ( ) ] ) ;
2022-07-06 11:02:36 +02:00
return JSON . stringify ( {
iv : iv.toString ( 'hex' ) ,
content : encrypted.toString ( 'hex' )
} ) ;
}
} ;
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 ) {
2022-09-06 17:37:26 +00:00
throw 'Invalid DNS.' ;
2022-07-06 11:02:36 +02:00
}
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 ) {
2022-09-06 17:37:26 +00:00
throw 'DNS not set' ;
2022-07-06 11:02:36 +02:00
}
}
export function getDomain ( domain : string ) : string {
2022-10-25 15:12:40 +02:00
if ( domain ) {
return domain ? . replace ( 'https://' , '' ) . replace ( 'http://' , '' ) ;
} else {
return '' ;
}
2022-07-06 11:02:36 +02:00
}
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 } ` } } ,
2022-10-12 11:17:02 +02:00
{ fqdn : { endsWith : ` //www. ${ nakedDomain } ` } } ,
{ dockerComposeConfiguration : { contains : ` // ${ nakedDomain } ` } } ,
{ dockerComposeConfiguration : { contains : ` //www. ${ nakedDomain } ` } }
2022-07-06 11:02:36 +02:00
] ,
2022-07-25 10:16:25 +00:00
id : { not : id } ,
destinationDocker : {
2022-09-06 17:37:26 +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 } ` } } ,
2022-10-27 20:05:02 +00:00
{ fqdn : { endsWith : ` //www. ${ nakedDomain } ` } }
2022-07-06 11:02:36 +02:00
] ,
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-09-06 17:37: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-09-06 17:37:26 +00: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-09-06 17:37:26 +00: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-09-06 17:37:26 +00: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-09-06 17:37:26 +00: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-09-06 17:37:26 +00: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 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-09-06 17:37:26 +00:00
const { remoteIpAddress , sshLocalPort } = await prisma . destinationDocker . findUnique ( {
where : { id }
} ) ;
2022-08-07 12:42:20 +00:00
if ( sshLocalPort ) {
2022-09-06 17:37:26 +00:00
return Number ( sshLocalPort ) ;
2022-08-07 12:42:20 +00:00
}
2022-08-22 19:30:09 +00:00
const data = await prisma . setting . findFirst ( ) ;
const { minPort , maxPort } = data ;
2022-09-06 17:37:26 +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 : {
2022-09-06 17:37:26 +00:00
remoteIpAddress ,
id : { not : id } ,
sshLocalPort : { not : null }
2022-08-22 19:30:09 +00:00
}
2022-09-06 17:37:26 +00:00
} ) ;
2022-08-07 12:42:20 +00:00
if ( alreadyConfigured ? . sshLocalPort ) {
2022-09-06 17:37:26 +00:00
await prisma . destinationDocker . update ( {
where : { id } ,
data : { sshLocalPort : alreadyConfigured.sshLocalPort }
} ) ;
return Number ( alreadyConfigured . sshLocalPort ) ;
2022-08-07 12:42:20 +00:00
}
2022-09-06 17:37:26 +00:00
const range = generateRangeArray ( minPort , maxPort ) ;
const availablePorts = range . filter ( ( port ) = > ! ports . map ( ( p ) = > p . sshLocalPort ) . includes ( port ) ) ;
2022-08-22 19:30:09 +00:00
for ( const port of availablePorts ) {
2022-09-06 17:37:26 +00:00
const found = await isReachable ( port , { host : 'localhost' } ) ;
2022-08-22 19:30:09 +00:00
if ( ! found ) {
2022-09-06 17:37:26 +00:00
await prisma . destinationDocker . update ( {
where : { id } ,
data : { sshLocalPort : Number ( port ) }
} ) ;
return Number ( port ) ;
2022-08-22 19:30:09 +00:00
}
}
2022-09-06 17:37:26 +00:00
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 ( ) ;
2022-09-06 17:37:26 +00:00
const sshKeyFile = ` /tmp/id_rsa- ${ id } ` ;
2022-08-07 12:42:20 +00:00
const localPort = await getFreeSSHLocalPort ( id ) ;
2022-09-06 17:37:26 +00:00
const {
sshKey : { privateKey } ,
2022-11-03 11:32:18 +01:00
network ,
2022-09-06 17:37:26 +00:00
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-11-03 11:32:18 +01:00
// const { stdout: numberOfSSHAgentsRunning } = await asyncExecShell(
// `ps ax | grep [s]sh-agent | grep coolify-ssh-agent.pid | grep -v grep | wc -l`
// );
// if (numberOfSSHAgentsRunning !== '' && Number(numberOfSSHAgentsRunning.trim()) == 0) {
// try {
// await fs.stat(`/tmp/coolify-ssh-agent.pid`);
// await fs.rm(`/tmp/coolify-ssh-agent.pid`);
// } catch (error) { }
// await asyncExecShell(`eval $(ssh-agent -sa /tmp/coolify-ssh-agent.pid)`);
// }
// await asyncExecShell(`SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh-add -q ${sshKeyFile}`);
2022-07-06 11:02:36 +02:00
2022-11-03 11:32:18 +01: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`
// );
// if (numberOfSSHTunnelsRunning !== '' && Number(numberOfSSHTunnelsRunning.trim()) == 0) {
// try {
// await asyncExecShell(
// `SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh -F /dev/null -o "StrictHostKeyChecking no" -fNL ${localPort}:localhost:${remotePort} ${remoteUser}@${remoteIpAddress}`
// );
// } catch (error) { }
// }
2022-09-06 17:37:26 +00:00
const config = sshConfig . parse ( '' ) ;
2022-11-03 11:32:18 +01:00
const Host = ` ${ remoteIpAddress } -remote `
2022-11-03 11:40:55 +01:00
try {
await asyncExecShell ( ` ssh-keygen -R ${ Host } ` ) ;
await asyncExecShell ( ` ssh-keygen -R ${ remoteIpAddress } ` ) ;
await asyncExecShell ( ` ssh-keygen -R localhost: ${ localPort } ` ) ;
} catch ( error ) { }
2022-11-03 11:32:18 +01:00
const found = config . find ( { Host } ) ;
const foundIp = config . find ( { Host : remoteIpAddress } ) ;
2022-11-03 11:40:55 +01:00
2022-11-03 11:32:18 +01:00
if ( found ) config . remove ( { Host } )
if ( foundIp ) config . remove ( { Host : remoteIpAddress } )
config . append ( {
Host ,
Hostname : remoteIpAddress ,
Port : remotePort.toString ( ) ,
User : remoteUser ,
StrictHostKeyChecking : 'no' ,
IdentityFile : sshKeyFile ,
ControlMaster : 'auto' ,
ControlPath : ` ${ homedir } /.ssh/coolify- ${ remoteIpAddress } -%r@%h:%p ` ,
ControlPersist : '10m'
} ) ;
2022-09-22 09:02:53 +02:00
2022-07-20 13:35:26 +00:00
try {
2022-09-06 17:37:26 +00:00
await fs . stat ( ` ${ homedir } /.ssh/ ` ) ;
2022-07-20 13:35:26 +00:00
} catch ( error ) {
2022-09-06 17:37:26 +00:00
await fs . mkdir ( ` ${ homedir } /.ssh/ ` ) ;
2022-07-20 13:35:26 +00:00
}
2022-09-06 17:37:26 +00:00
return await fs . writeFile ( ` ${ homedir } /.ssh/config ` , sshConfig . stringify ( config ) ) ;
2022-07-20 13:35:26 +00:00
}
2022-09-08 14:42:04 +02:00
export async function executeSSHCmd ( { dockerId , command } ) {
const { execaCommand } = await import ( 'execa' )
2022-11-03 11:32:18 +01:00
let { remoteEngine , remoteIpAddress } = await prisma . destinationDocker . findUnique ( { where : { id : dockerId } } )
2022-09-08 14:42:04 +02:00
if ( remoteEngine ) {
await createRemoteEngineConfiguration ( dockerId )
}
if ( process . env . CODESANDBOX_HOST ) {
if ( command . startsWith ( 'docker compose' ) ) {
command = command . replace ( /docker compose/gi , 'docker-compose' )
}
}
2022-11-03 11:32:18 +01:00
return await execaCommand ( ` ssh ${ remoteIpAddress } -remote ${ command } ` )
2022-09-08 14:42:04 +02: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-11-03 11:32:18 +01:00
let { remoteEngine , remoteIpAddress , engine } = await prisma . destinationDocker . findUnique ( { where : { id : dockerId } } )
2022-07-25 12:42:10 +00:00
if ( remoteEngine ) {
2022-09-06 17:37:26 +00:00
await createRemoteEngineConfiguration ( dockerId ) ;
2022-11-03 11:32:18 +01:00
engine = ` ssh:// ${ remoteIpAddress } -remote ` ;
2022-07-25 12:42:10 +00:00
} else {
2022-09-06 17:37:26 +00:00
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' ) ) {
2022-09-06 17:37:26 +00:00
command = command . replace ( /docker compose/gi , 'docker-compose' ) ;
2022-08-15 09:27:54 +00:00
}
}
2022-10-12 11:17:02 +02:00
if ( command . startsWith ( ` docker build ` ) || command . startsWith ( ` pack build ` ) || command . startsWith ( ` docker compose build ` ) ) {
2022-08-23 11:29:25 +02:00
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-09-09 15:14:58 +02: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-09-06 17:37:26 +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 ) {
2022-09-06 17:37:26 +00:00
await executeDockerCmd ( {
dockerId : id ,
command : ` docker network create --attachable coolify-infra `
} ) ;
2022-08-15 10:28:57 +00:00
}
2022-09-06 17:37: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-09-06 17:37:26 +00:00
let traefikUrl = mainTraefikEndpoint ;
2022-07-22 20:23:16 +00:00
if ( remoteEngine ) {
2022-09-06 17:37:26 +00:00
let ip = null ;
2022-07-22 20:23:16 +00:00
if ( isDev ) {
2022-09-06 17:37:26 +00:00
ip = getAPIUrl ( ) ;
2022-07-22 20:23:16 +00:00
} else {
2022-09-06 17:37:26 +00:00
ip = ` http:// ${ ipv4 || ipv6 } :3000 ` ;
2022-07-22 20:23:16 +00:00
}
2022-09-06 17:37:26 +00:00
traefikUrl = ` ${ ip } /webhooks/traefik/remote/ ${ id } ` ;
2022-07-22 20:23:16 +00:00
}
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-09-06 17:37:26 +00:00
} ) ;
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 > {
2022-09-06 17:37:26 +00:00
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-09-06 17:37:26 +00:00
command : ` docker ps -a --filter name=coolify-proxy --format '{{json .Networks}}' `
2022-07-20 13:35:26 +00:00
} ) ;
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 ) ) {
2022-09-06 17:37:26 +00:00
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-09-09 15:14:58 +02:00
const { found } = await checkContainer ( { dockerId : id , container : 'coolify-proxy' } ) ;
2022-07-20 13:35:26 +00:00
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 ,
2022-09-06 17:37:26 +00:00
command : ` docker stop -t 0 coolify-proxy && docker rm coolify-proxy `
2022-07-20 13:35:26 +00:00
} ) ;
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-11-18 14:28:05 +01:00
export function generateToken() {
return jsonwebtoken . sign ( {
nbf : Math.floor ( Date . now ( ) / 1000 ) - 30 ,
} , process . env [ 'COOLIFY_SECRET_KEY' ] )
}
2022-09-06 17:37:26 +00:00
export function generatePassword ( {
length = 24 ,
symbols = false ,
isHex = false
} : { length? : number ; symbols? : boolean ; isHex? : boolean } | null ) : string {
2022-08-23 10:11:38 +02:00
if ( isHex ) {
2022-09-06 17:37:26 +00:00
return crypto . randomBytes ( length ) . toString ( 'hex' ) ;
2022-08-23 10:11:38 +02:00
}
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
| {
2022-09-22 09:02:53 +02:00
volume : string ;
image : string ;
command? : string ;
ulimits : Record < string , unknown > ;
privatePort : number ;
environmentVariables : {
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
| {
2022-09-22 09:02:53 +02:00
volume : string ;
image : string ;
command? : string ;
ulimits : Record < string , unknown > ;
privatePort : number ;
environmentVariables : {
MARIADB_ROOT_USER : string ;
MARIADB_ROOT_PASSWORD : string ;
MARIADB_USER : string ;
MARIADB_PASSWORD : string ;
MARIADB_DATABASE : 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-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 ,
} = 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-09-06 17:37:26 +00: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 ` ;
}
2022-09-06 17:37:26 +00:00
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 ` ;
}
2022-09-06 17:37:26 +00:00
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
2022-09-06 17:37:26 +00:00
} ;
2022-08-12 12:13:52 +02:00
configuration . volume = ` ${ id } - ${ type } -data:/data/db ` ;
}
2022-09-06 17:37:26 +00:00
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-09-06 17:37:26 +00: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-09-06 17:37:26 +00:00
} ;
2022-08-12 11:48:38 +02:00
}
2022-09-06 17:37:26 +00:00
return configuration ;
2022-07-06 11:02:36 +02:00
} else if ( type === 'redis' ) {
2022-09-08 11:41:38 +02:00
const { settings : { appendOnly } } = database ;
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 ` ;
2022-09-22 09:02:53 +02:00
configuration . command = ` /usr/local/bin/redis-server --appendonly ${ appendOnly ? 'yes' : 'no'
} -- requirepass $ { dbUserPassword } ` ;
2022-08-12 11:48:38 +02:00
}
2022-09-06 17:37:26 +00:00
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-11-10 15:01:03 +01:00
if ( arch === 'arm' || arch === 'arm64' || arch === 'aarch' || arch === 'aarch64' ) {
2022-09-06 17:37:26 +00:00
return true ;
2022-08-12 11:48:38 +02:00
}
2022-09-06 17:37:26 +00:00
return false ;
2022-08-12 11:48:38 +02:00
}
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-09-06 17:37:26 +00:00
return found . baseImageARM || found . baseImage ;
2022-08-12 11:48:38 +02:00
}
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-09-06 17:37:26 +00:00
return found . versionsARM || found . versions ;
2022-08-12 11:48:38 +02:00
}
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 [ ] ;
2022-09-06 17:37:26 +00:00
build ? :
2022-09-22 09:02:53 +02:00
| {
context : string ;
dockerfile : string ;
args? : Record < string , unknown > ;
}
| 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 } > = > {
2022-10-26 13:44:32 +02:00
repository = repository . replaceAll ( ' ' , '' )
2022-07-06 11:02:36 +02:00
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 ) ) ;
2022-09-22 09:02:53 +02:00
} catch ( error ) { }
2022-09-06 14:32:50 +02:00
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
} ;
} ;
2022-09-06 17:37:26 +00:00
export async function stopDatabaseContainer ( database : any ) : Promise < boolean > {
2022-07-06 11:02:36 +02:00
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-09-06 17:37:26 +00:00
const { stdout } = await executeDockerCmd ( {
dockerId ,
command : ` docker inspect --format '{{json .State}}' ${ id } `
} ) ;
2022-07-20 13:35:26 +00:00
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 ;
2022-09-09 15:14:58 +02:00
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 ,
2022-09-06 17:37:26 +00:00
command : ` docker stop -t 0 ${ container } && docker rm ${ container } `
2022-07-20 13:35:26 +00:00
} ) ;
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-09-06 17:37:26 +00:00
} ) ;
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-09-06 17:37:26 +00:00
} ) ;
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-09-06 17:37:26 +00:00
} ) ;
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-09-06 17:37:26 +00:00
} ) ;
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-09-06 17:37:26 +00:00
} ) ;
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-09-06 17:37:26 +00:00
} ) ;
2022-07-06 11:02:36 +02:00
}
}
}
2022-09-08 11:41:38 +02:00
export async function checkExposedPort ( { id , configuredPort , exposePort , engine , remoteEngine , remoteIpAddress } : { id : string , configuredPort? : number , exposePort : number , engine : string , remoteEngine : boolean , remoteIpAddress? : string } ) {
2022-08-18 15:29:59 +02:00
if ( exposePort < 1024 || exposePort > 65535 ) {
2022-09-06 17:37:26 +00:00
throw { status : 500 , message : ` Exposed Port needs to be between 1024 and 65535. ` } ;
2022-08-18 15:29:59 +02:00
}
if ( configuredPort ) {
if ( configuredPort !== exposePort ) {
2022-09-08 11:41:38 +02:00
const availablePort = await getFreeExposedPort ( id , exposePort , engine , remoteEngine , remoteIpAddress ) ;
2022-08-18 15:29:59 +02:00
if ( availablePort . toString ( ) !== exposePort . toString ( ) ) {
2022-09-06 17:37:26 +00:00
throw { status : 500 , message : ` Port ${ exposePort } is already in use. ` } ;
2022-08-18 15:29:59 +02:00
}
}
} else {
2022-09-08 11:41:38 +02:00
const availablePort = await getFreeExposedPort ( id , exposePort , engine , remoteEngine , remoteIpAddress ) ;
2022-08-18 15:29:59 +02:00
if ( availablePort . toString ( ) !== exposePort . toString ( ) ) {
2022-09-06 17:37:26 +00:00
throw { status : 500 , message : ` Port ${ exposePort } is already in use. ` } ;
2022-08-18 15:29:59 +02:00
}
}
}
2022-09-08 11:41:38 +02:00
export async function getFreeExposedPort ( id , exposePort , engine , remoteEngine , remoteIpAddress ) {
2022-08-22 19:30:09 +00:00
const { default : checkPort } = await import ( 'is-port-reachable' ) ;
2022-09-08 11:41:38 +02:00
if ( remoteEngine ) {
const applicationUsed = await (
await prisma . application . findMany ( {
where : { exposePort : { not : null } , id : { not : id } , destinationDocker : { remoteIpAddress } } ,
select : { exposePort : true }
} )
) . map ( ( a ) = > a . exposePort ) ;
const serviceUsed = await (
await prisma . service . findMany ( {
where : { exposePort : { not : null } , id : { not : id } , destinationDocker : { remoteIpAddress } } ,
select : { exposePort : true }
} )
) . map ( ( a ) = > a . exposePort ) ;
const usedPorts = [ . . . applicationUsed , . . . serviceUsed ] ;
if ( usedPorts . includes ( exposePort ) ) {
return false
}
const found = await checkPort ( exposePort , { host : remoteIpAddress } ) ;
if ( ! found ) {
return exposePort
}
return false
} else {
const applicationUsed = await (
await prisma . application . findMany ( {
where : { exposePort : { not : null } , id : { not : id } , destinationDocker : { engine } } ,
select : { exposePort : true }
} )
) . map ( ( a ) = > a . exposePort ) ;
const serviceUsed = await (
await prisma . service . findMany ( {
where : { exposePort : { not : null } , id : { not : id } , destinationDocker : { engine } } ,
select : { exposePort : true }
} )
) . map ( ( a ) = > a . exposePort ) ;
const usedPorts = [ . . . applicationUsed , . . . serviceUsed ] ;
if ( usedPorts . includes ( exposePort ) ) {
return false
}
const found = await checkPort ( exposePort , { host : 'localhost' } ) ;
if ( ! found ) {
return exposePort
}
2022-07-22 12:01:07 +00:00
return false
}
2022-07-22 08:57:11 +00:00
}
2022-08-22 19:30:09 +00:00
export function generateRangeArray ( start , end ) {
2022-09-06 17:37:26 +00:00
return Array . from ( { length : end - start } , ( v , k ) = > k + start ) ;
2022-08-22 19:30:09 +00:00
}
2022-09-08 11:41:38 +02:00
export async function getFreePublicPort ( { id , remoteEngine , engine , remoteIpAddress } ) {
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 ;
2022-09-08 11:41:38 +02:00
if ( remoteEngine ) {
const dbUsed = await (
await prisma . database . findMany ( {
where : { publicPort : { not : null } , id : { not : id } , destinationDocker : { remoteIpAddress } } ,
select : { publicPort : true }
} )
) . map ( ( a ) = > a . publicPort ) ;
const wpFtpUsed = await (
await prisma . wordpress . findMany ( {
where : { ftpPublicPort : { not : null } , id : { not : id } , service : { destinationDocker : { remoteIpAddress } } } ,
select : { ftpPublicPort : true }
} )
) . map ( ( a ) = > a . ftpPublicPort ) ;
const wpUsed = await (
await prisma . wordpress . findMany ( {
where : { mysqlPublicPort : { not : null } , id : { not : id } , service : { destinationDocker : { remoteIpAddress } } } ,
select : { mysqlPublicPort : true }
} )
) . map ( ( a ) = > a . mysqlPublicPort ) ;
const minioUsed = await (
await prisma . minio . findMany ( {
where : { publicPort : { not : null } , id : { not : id } , service : { destinationDocker : { remoteIpAddress } } } ,
select : { publicPort : true }
} )
) . map ( ( a ) = > a . publicPort ) ;
const usedPorts = [ . . . dbUsed , . . . wpFtpUsed , . . . wpUsed , . . . minioUsed ] ;
const range = generateRangeArray ( minPort , maxPort )
const availablePorts = range . filter ( port = > ! usedPorts . includes ( port ) )
for ( const port of availablePorts ) {
const found = await isReachable ( port , { host : remoteIpAddress } )
if ( ! found ) {
return port
}
2022-08-22 19:30:09 +00:00
}
2022-09-08 11:41:38 +02:00
return false
} else {
const dbUsed = await (
await prisma . database . findMany ( {
where : { publicPort : { not : null } , id : { not : id } , destinationDocker : { engine } } ,
select : { publicPort : true }
} )
) . map ( ( a ) = > a . publicPort ) ;
const wpFtpUsed = await (
await prisma . wordpress . findMany ( {
where : { ftpPublicPort : { not : null } , id : { not : id } , service : { destinationDocker : { engine } } } ,
select : { ftpPublicPort : true }
} )
) . map ( ( a ) = > a . ftpPublicPort ) ;
const wpUsed = await (
await prisma . wordpress . findMany ( {
where : { mysqlPublicPort : { not : null } , id : { not : id } , service : { destinationDocker : { engine } } } ,
select : { mysqlPublicPort : true }
} )
) . map ( ( a ) = > a . mysqlPublicPort ) ;
const minioUsed = await (
await prisma . minio . findMany ( {
where : { publicPort : { not : null } , id : { not : id } , service : { destinationDocker : { engine } } } ,
select : { publicPort : true }
} )
) . map ( ( a ) = > a . publicPort ) ;
const usedPorts = [ . . . dbUsed , . . . wpFtpUsed , . . . wpUsed , . . . minioUsed ] ;
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-08-22 19:30:09 +00:00
}
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 } ` ;
2022-09-09 15:14:58 +02:00
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-09-29 14:32:35 +02:00
const { found : foundDependentContainer } = await checkContainer ( {
2022-09-06 17:37:26 +00:00
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-09-06 17:37:26 +00:00
} ) ;
2022-07-20 13:35:26 +00:00
2022-07-06 11:02:36 +02:00
const ip = JSON . parse ( Config ) [ 0 ] . Gateway ;
2022-09-06 17:37:26 +00:00
let traefikUrl = otherTraefikEndpoint ;
2022-07-22 21:14:12 +00:00
if ( remoteEngine ) {
2022-09-06 17:37:26 +00:00
let ip = null ;
2022-07-22 21:14:12 +00:00
if ( isDev ) {
2022-09-06 17:37:26 +00:00
ip = getAPIUrl ( ) ;
2022-07-22 21:14:12 +00:00
} else {
2022-09-06 17:37:26 +00:00
ip = ` http:// ${ ipv4 || ipv6 } :3000 ` ;
2022-07-22 21:14:12 +00:00
}
2022-09-06 17:37:26 +00:00
traefikUrl = ` ${ ip } /webhooks/traefik/other.json ` ;
2022-07-22 21:14:12 +00:00
}
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-11-03 09:43:34 +01:00
'--providers.http.pollTimeout=10s' ,
2022-07-06 11:02:36 +02:00
'--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-09-06 17:37:26 +00:00
} ) ;
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-09-06 17:37:26 +00:00
} ) ;
2022-07-06 11:02:36 +02:00
}
} catch ( error ) {
return error ;
}
}
2022-09-06 17:37:26 +00:00
export async function getServiceFromDB ( {
id ,
teamId
} : {
id : string ;
teamId : string ;
} ) : Promise < any > {
2022-07-06 11:02:36 +02:00
const settings = await prisma . setting . findFirst ( ) ;
const body = await prisma . service . findFirst ( {
where : { id , teams : { some : { id : teamId === '0' ? undefined : teamId } } } ,
2022-10-26 10:49:30 +02:00
include : {
destinationDocker : true ,
persistentStorage : true ,
serviceSecret : true ,
serviceSetting : true ,
2022-11-07 14:59:39 +01:00
wordpress : true ,
plausibleAnalytics : true ,
2022-10-26 10:49:30 +02:00
}
2022-07-06 11:02:36 +02:00
} ) ;
2022-09-22 09:02:53 +02:00
if ( ! body ) {
return null
}
2022-10-28 11:54:03 +02:00
// body.type = fixType(body.type);
2022-10-26 10:27:33 +02:00
2022-07-06 11:02:36 +02:00
if ( body ? . serviceSecret . length > 0 ) {
body . serviceSecret = body . serviceSecret . map ( ( s ) = > {
s . value = decrypt ( s . value ) ;
return s ;
} ) ;
}
2022-10-28 11:54:03 +02:00
if ( body . wordpress ) {
body . wordpress . ftpPassword = decrypt ( body . wordpress . ftpPassword ) ;
}
2022-08-12 19:29:53 +00:00
2022-07-06 11:02:36 +02:00
return { . . . body , settings } ;
}
export function fixType ( type ) {
2022-10-26 10:27:33 +02:00
return type ? . replaceAll ( ' ' , '' ) . toLowerCase ( ) || null ;
2022-07-06 11:02:36 +02:00
}
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
] ;
}
2022-09-06 17:37:26 +00:00
export function errorHandler ( {
status = 500 ,
message = 'Unknown error.'
} : {
status : number ;
message : string | any ;
} ) {
if ( message . message ) message = message . message ;
2022-07-06 11:02:36 +02:00
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 ) = > {
2022-09-06 17:37:26 +00:00
const { destinationDockerId , status } = await prisma . build . findFirst ( {
where : { id : buildId }
} ) ;
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-09-06 17:37:26 +00: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-09-06 17:37:26 +00: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-09-06 17:37:26 +00: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 ++ ;
2022-09-22 09:02:53 +02:00
} catch ( error ) { }
2022-07-06 11:02:36 +02:00
} , 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' ) {
2022-09-06 17:37:26 +00:00
return 'nc' ;
2022-07-13 13:40:41 +00:00
}
}
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-06 17:37:26 +00: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-06 17:37:26 +00:00
await executeDockerCmd ( { dockerId , command : ` docker rmi -f ${ images } " -q | xargs -r ` } ) ;
2022-07-13 13:40:41 +00:00
}
2022-09-22 09:02:53 +02:00
} catch ( error ) { }
2022-07-13 13:40:41 +00:00
if ( lowDiskSpace || force ) {
2022-09-28 08:39:33 +02:00
// if (isDev) {
// if (!force) console.log(`[DEV MODE] Low disk space: ${lowDiskSpace}`);
// return;
// }
2022-07-13 13:40:41 +00:00
try {
2022-09-06 17:37:26 +00:00
await executeDockerCmd ( {
dockerId ,
command : ` docker container prune -f --filter "label=coolify.managed=true" `
} ) ;
2022-09-22 09:02:53 +02:00
} catch ( error ) { }
2022-07-13 13:40:41 +00:00
try {
2022-09-06 17:37:26 +00:00
await executeDockerCmd ( { dockerId , command : ` docker image prune -f ` } ) ;
2022-09-22 09:02:53 +02:00
} catch ( error ) { }
2022-07-13 13:40:41 +00:00
try {
2022-09-06 17:37:26 +00:00
await executeDockerCmd ( { dockerId , command : ` docker image prune -a -f ` } ) ;
2022-09-22 09:02:53 +02:00
} catch ( error ) { }
2022-08-27 07:46:30 +00:00
// Cleanup build caches
try {
2022-09-06 17:37:26 +00:00
await executeDockerCmd ( { dockerId , command : ` docker builder prune -a -f ` } ) ;
2022-09-22 09:02:53 +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 ) {
2022-11-18 14:28:05 +01:00
if ( ! volume . startsWith ( '/' ) ) {
2022-10-14 15:48:37 +02:00
volumeSet . add ( volume ) ;
}
2022-09-02 14:11:36 +02:00
}
}
}
}
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-09-06 17:37:26 +00:00
let volumes = [ . . . persistentVolume ] ;
if ( volumesArray ) volumes = [ . . . volumesArray , . . . volumes ] ;
const composeVolumes =
( volumes . length > 0 &&
volumes . map ( ( volume ) = > {
return {
[ ` ${ volume . split ( ':' ) [ 0 ] } ` ] : {
name : volume.split ( ':' ) [ 0 ]
}
} ;
} ) ) ||
[ ] ;
2022-08-10 11:55:27 +00:00
2022-09-06 17:37:26 +00:00
const volumeMounts = Object . assign ( { } , . . . composeVolumes ) || { } ;
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-09-06 17:37:26 +00:00
} ;
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 ) {
2022-09-06 17:37:26 +00:00
application . gitSource . githubApp . clientSecret =
decrypt ( application . gitSource . githubApp . clientSecret ) || null ;
2022-09-01 14:01:31 +02:00
}
if ( application ? . gitSource ? . githubApp ? . webhookSecret ) {
2022-09-06 17:37:26 +00:00
application . gitSource . githubApp . webhookSecret =
decrypt ( application . gitSource . githubApp . webhookSecret ) || null ;
2022-09-01 14:01:31 +02:00
}
if ( application ? . gitSource ? . githubApp ? . privateKey ) {
2022-09-06 17:37:26 +00:00
application . gitSource . githubApp . privateKey =
decrypt ( application . gitSource . githubApp . privateKey ) || null ;
2022-09-01 14:01:31 +02:00
}
if ( application ? . gitSource ? . gitlabApp ? . appSecret ) {
2022-09-06 17:37:26 +00:00
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 ) = > {
2022-09-06 17:37:26 +00:00
s . value = decrypt ( s . value ) || null ;
2022-09-01 14:01:31 +02:00
return s ;
} ) ;
}
return application ;
2022-07-13 13:40:41 +00:00
}
2022-08-30 16:13:07 +02:00
}