Merge remote-tracking branch 'upstream/next' into feature/glitchtip-service
# Conflicts: # apps/api/prisma/schema.prisma # apps/api/src/lib/common.ts # apps/api/src/lib/serviceFields.ts # apps/api/src/routes/api/v1/services/handlers.ts
This commit is contained in:
@@ -2,14 +2,14 @@ import { executeDockerCmd, prisma } from "../common"
|
||||
import { saveBuildLog } from "./common";
|
||||
|
||||
export default async function (data: any): Promise<void> {
|
||||
const { buildId, applicationId, tag, dockerId, debug, workdir } = data
|
||||
try {
|
||||
const { buildId, applicationId, tag, dockerId, debug, workdir } = data
|
||||
|
||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
||||
const { stdout } = await executeDockerCmd({
|
||||
dockerId,
|
||||
command: `pack build -p ${workdir} ${applicationId}:${tag} --builder heroku/buildpacks:20`
|
||||
})
|
||||
|
||||
if (debug) {
|
||||
const array = stdout.split('\n')
|
||||
for (const line of array) {
|
||||
@@ -24,6 +24,16 @@ export default async function (data: any): Promise<void> {
|
||||
}
|
||||
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
|
||||
} catch (error) {
|
||||
const array = error.stdout.split('\n')
|
||||
for (const line of array) {
|
||||
if (line !== '\n') {
|
||||
await saveBuildLog({
|
||||
line: `${line.replace('\n', '')}`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ import { checkContainer, removeContainer } from './docker';
|
||||
import { day } from './dayjs';
|
||||
import * as serviceFields from './serviceFields'
|
||||
|
||||
export const version = '3.3.2';
|
||||
export const version = '3.4.0';
|
||||
export const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
const algorithm = 'aes-256-ctr';
|
||||
@@ -78,6 +78,8 @@ export const include: any = {
|
||||
umami: true,
|
||||
hasura: true,
|
||||
fider: true,
|
||||
moodle: true,
|
||||
appwrite: true,
|
||||
glitchTip: true,
|
||||
};
|
||||
|
||||
@@ -97,17 +99,23 @@ export const base64Decode = (text: string): string => {
|
||||
};
|
||||
export const decrypt = (hashString: string) => {
|
||||
if (hashString) {
|
||||
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();
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
export const encrypt = (text: string) => {
|
||||
@@ -270,6 +278,17 @@ export const supportedServiceTypesAndVersions = [
|
||||
main: 3000
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'appwrite',
|
||||
fancyName: 'Appwrite',
|
||||
baseImage: 'appwrite/appwrite',
|
||||
images: ['mariadb:10.7', 'redis:6.2-alpine', 'appwrite/telegraf:1.4.0'],
|
||||
versions: ['latest', '0.15.3'],
|
||||
recommendedVersion: '0.15.3',
|
||||
ports: {
|
||||
main: 80
|
||||
}
|
||||
}
|
||||
// {
|
||||
// name: 'moodle',
|
||||
// fancyName: 'Moodle',
|
||||
@@ -585,6 +604,11 @@ export async function executeDockerCmd({ dockerId, command }: { dockerId: string
|
||||
} else {
|
||||
engine = 'unix:///var/run/docker.sock'
|
||||
}
|
||||
if (process.env.CODESANDBOX_HOST) {
|
||||
if (command.startsWith('docker compose')) {
|
||||
command = command.replace(/docker compose/gi, 'docker-compose')
|
||||
}
|
||||
}
|
||||
return await asyncExecShell(
|
||||
`DOCKER_BUILDKIT=1 DOCKER_HOST="${engine}" ${command}`
|
||||
);
|
||||
@@ -596,6 +620,11 @@ export async function startTraefikProxy(id: string): Promise<void> {
|
||||
const { id: settingsId, ipv4, ipv6 } = await listSettings();
|
||||
|
||||
if (!found) {
|
||||
const { stdout: coolifyNetwork } = await executeDockerCmd({ dockerId: id, command: `docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"` })
|
||||
|
||||
if (!coolifyNetwork) {
|
||||
await executeDockerCmd({ dockerId: id, command: `docker network create --attachable coolify-infra` })
|
||||
}
|
||||
const { stdout: Config } = await executeDockerCmd({ dockerId: id, command: `docker network inspect ${network} --format '{{json .IPAM.Config }}'` })
|
||||
const ip = JSON.parse(Config)[0].Gateway;
|
||||
let traefikUrl = mainTraefikEndpoint
|
||||
@@ -879,6 +908,11 @@ export function generateDatabaseConfiguration(database: any, arch: string):
|
||||
}
|
||||
if (isARM(arch)) {
|
||||
configuration.volume = `${id}-${type}-data:/var/lib/postgresql`;
|
||||
configuration.environmentVariables = {
|
||||
POSTGRES_PASSWORD: dbUserPassword,
|
||||
POSTGRES_USER: dbUser,
|
||||
POSTGRES_DB: defaultDatabase
|
||||
}
|
||||
}
|
||||
return configuration
|
||||
} else if (type === 'redis') {
|
||||
@@ -915,7 +949,7 @@ export function generateDatabaseConfiguration(database: any, arch: string):
|
||||
return configuration
|
||||
}
|
||||
}
|
||||
export function isARM(arch) {
|
||||
export function isARM(arch: string) {
|
||||
if (arch === 'arm' || arch === 'arm64') {
|
||||
return true
|
||||
}
|
||||
@@ -1238,7 +1272,6 @@ export async function startTraefikTCPProxy(
|
||||
}
|
||||
traefikUrl = `${ip}/webhooks/traefik/other.json`
|
||||
}
|
||||
console.log(traefikUrl)
|
||||
const tcpProxy = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
@@ -1303,6 +1336,7 @@ export async function getServiceFromDB({ id, teamId }: { id: string; teamId: str
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
body[type] = { ...body[type], ...getUpdateableFields(type, body[type]) }
|
||||
return { ...body, settings };
|
||||
}
|
||||
@@ -1529,6 +1563,35 @@ export async function configureServiceType({
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'appwrite') {
|
||||
const opensslKeyV1 = encrypt(generatePassword());
|
||||
const executorSecret = encrypt(generatePassword());
|
||||
const redisPassword = encrypt(generatePassword());
|
||||
const mariadbHost = `${id}-mariadb`
|
||||
const mariadbUser = cuid();
|
||||
const mariadbPassword = encrypt(generatePassword());
|
||||
const mariadbDatabase = 'appwrite';
|
||||
const mariadbRootUser = cuid();
|
||||
const mariadbRootUserPassword = encrypt(generatePassword());
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
appwrite: {
|
||||
create: {
|
||||
opensslKeyV1,
|
||||
executorSecret,
|
||||
redisPassword,
|
||||
mariadbHost,
|
||||
mariadbUser,
|
||||
mariadbPassword,
|
||||
mariadbDatabase,
|
||||
mariadbRootUser,
|
||||
mariadbRootUserPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'glitchTip') {
|
||||
const defaultUsername = cuid();
|
||||
const defaultEmail = `${defaultUsername}@example.com`;
|
||||
@@ -1566,6 +1629,7 @@ export async function configureServiceType({
|
||||
}
|
||||
|
||||
export async function removeService({ id }: { id: string }): Promise<void> {
|
||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.fider.deleteMany({ where: { serviceId: id } });
|
||||
@@ -1577,8 +1641,8 @@ export async function removeService({ id }: { id: string }): Promise<void> {
|
||||
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.glitchTip.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||
|
||||
await prisma.moodle.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.service.delete({ where: { id } });
|
||||
}
|
||||
|
||||
@@ -1643,9 +1707,9 @@ export const getServiceMainPort = (service: string) => {
|
||||
export function makeLabelForServices(type) {
|
||||
return [
|
||||
'coolify.managed=true',
|
||||
`coolify.version = ${version} `,
|
||||
`coolify.version = ${version}`,
|
||||
`coolify.type = service`,
|
||||
`coolify.service.type = ${type} `
|
||||
`coolify.service.type = ${type}`
|
||||
];
|
||||
}
|
||||
export function errorHandler({ status = 500, message = 'Unknown error.' }: { status: number, message: string | any }) {
|
||||
@@ -1717,7 +1781,7 @@ export function convertTolOldVolumeNames(type) {
|
||||
// export async function getAvailableServices(): Promise<any> {
|
||||
// const { data } = await axios.get(`https://gist.githubusercontent.com/andrasbacsai/4aac36d8d6214dbfc34fa78110554a50/raw/5b27e6c37d78aaeedc1148d797112c827a2f43cf/availableServices.json`)
|
||||
// return data
|
||||
//
|
||||
//
|
||||
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
|
||||
// Cleanup old coolify images
|
||||
try {
|
||||
@@ -1780,4 +1844,4 @@ export function persistentVolumes(id, persistentStorage, config) {
|
||||
...composeVolumes
|
||||
) || {}
|
||||
return { volumes, volumeMounts }
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@ export function formatLabelsOnDocker(data) {
|
||||
export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<boolean> {
|
||||
let containerFound = false;
|
||||
try {
|
||||
console.log('checking ', container)
|
||||
const { stdout } = await executeDockerCmd({
|
||||
dockerId,
|
||||
command:
|
||||
@@ -71,7 +72,7 @@ export async function removeContainer({
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
|
||||
|
||||
console.log(id)
|
||||
if (JSON.parse(stdout).Running) {
|
||||
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
||||
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
||||
|
@@ -326,7 +326,7 @@ export const fider = [{
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
}, {
|
||||
name: 'postgreslUser',
|
||||
name: 'postgresqlUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
@@ -477,6 +477,88 @@ export const moodle = [{
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
}]
|
||||
|
||||
export const appwrite = [{
|
||||
name: 'opensslKeyV1',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'executorSecret',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'redisPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'mariadbHost',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'mariadbPort',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'mariadbUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'mariadbPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'mariadbRootUser',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'mariadbRootUserPassword',
|
||||
isEditable: false,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: true
|
||||
},
|
||||
{
|
||||
name: 'mariadbDatabase',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
isBoolean: false,
|
||||
isEncrypted: false
|
||||
}]
|
||||
|
||||
export const glitchTip = [{
|
||||
name: 'postgresqlUser',
|
||||
isEditable: false,
|
||||
@@ -588,4 +670,4 @@ export const glitchTip = [{
|
||||
isNumber: false,
|
||||
isBoolean: true,
|
||||
isEncrypted: false
|
||||
}]
|
||||
}]
|
||||
|
35
apps/api/src/lib/services.ts
Normal file
35
apps/api/src/lib/services.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { createDirectories, getServiceFromDB, getServiceImage, getServiceMainPort, makeLabelForServices } from "./common";
|
||||
|
||||
export async function defaultServiceConfigurations({ id, teamId }) {
|
||||
const service = await getServiceFromDB({ id, teamId });
|
||||
const { destinationDockerId, destinationDocker, type, serviceSecret } = service;
|
||||
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const port = getServiceMainPort(type);
|
||||
|
||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
||||
|
||||
const image = getServiceImage(type);
|
||||
let secrets = [];
|
||||
if (serviceSecret.length > 0) {
|
||||
serviceSecret.forEach((secret) => {
|
||||
secrets.push([secret.name]=secret.value);
|
||||
});
|
||||
}
|
||||
return { ...service, network, port, workdir, image, secrets }
|
||||
}
|
||||
|
||||
export function defaultServiceComposeConfiguration(network: string) {
|
||||
return {
|
||||
networks: [network],
|
||||
restart: 'always',
|
||||
deploy: {
|
||||
restart_policy: {
|
||||
condition: 'on-failure',
|
||||
delay: '10s',
|
||||
max_attempts: 10,
|
||||
window: '120s'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user