Merge branch 'next' into main
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { base64Encode, encrypt, executeDockerCmd, generateTimestamp, getDomain, isDev, prisma, version } from "../common";
|
||||
import { base64Encode, decrypt, encrypt, executeDockerCmd, generateTimestamp, getDomain, isDev, prisma, version } from "../common";
|
||||
import { promises as fs } from 'fs';
|
||||
import { day } from "../dayjs";
|
||||
|
||||
@@ -363,6 +363,7 @@ export const setDefaultConfiguration = async (data: any) => {
|
||||
publishDirectory,
|
||||
baseDirectory,
|
||||
dockerFileLocation,
|
||||
dockerComposeFileLocation,
|
||||
denoMainFile
|
||||
} = data;
|
||||
//@ts-ignore
|
||||
@@ -392,6 +393,12 @@ export const setDefaultConfiguration = async (data: any) => {
|
||||
} else {
|
||||
dockerFileLocation = '/Dockerfile';
|
||||
}
|
||||
if (dockerComposeFileLocation) {
|
||||
if (!dockerComposeFileLocation.startsWith('/')) dockerComposeFileLocation = `/${dockerComposeFileLocation}`;
|
||||
if (dockerComposeFileLocation.endsWith('/')) dockerComposeFileLocation = dockerComposeFileLocation.slice(0, -1);
|
||||
} else {
|
||||
dockerComposeFileLocation = '/Dockerfile';
|
||||
}
|
||||
if (!denoMainFile) {
|
||||
denoMainFile = 'main.ts';
|
||||
}
|
||||
@@ -405,6 +412,7 @@ export const setDefaultConfiguration = async (data: any) => {
|
||||
publishDirectory,
|
||||
baseDirectory,
|
||||
dockerFileLocation,
|
||||
dockerComposeFileLocation,
|
||||
denoMainFile
|
||||
};
|
||||
};
|
||||
@@ -462,7 +470,13 @@ export const saveBuildLog = async ({
|
||||
applicationId: string;
|
||||
}): Promise<any> => {
|
||||
const { default: got } = await import('got')
|
||||
|
||||
if (typeof line === 'object' && line) {
|
||||
if (line.shortMessage) {
|
||||
line = line.shortMessage + '\n' + line.stderr;
|
||||
} else {
|
||||
line = JSON.stringify(line);
|
||||
}
|
||||
}
|
||||
if (line && typeof line === 'string' && line.includes('ghs_')) {
|
||||
const regex = /ghs_.*@/g;
|
||||
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
||||
@@ -480,7 +494,6 @@ export const saveBuildLog = async ({
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
if (isDev) return
|
||||
return await prisma.buildLog.create({
|
||||
data: {
|
||||
line: addTimestamp, buildId, time: Number(day().valueOf()), applicationId
|
||||
@@ -572,6 +585,29 @@ export function checkPnpm(installCommand = null, buildCommand = null, startComma
|
||||
);
|
||||
}
|
||||
|
||||
export async function saveDockerRegistryCredentials({ url, username, password, workdir }) {
|
||||
let decryptedPassword = decrypt(password);
|
||||
const location = `${workdir}/.docker`;
|
||||
|
||||
if (!username || !password) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.mkdir(`${workdir}/.docker`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
const payload = JSON.stringify({
|
||||
"auths": {
|
||||
[url]: {
|
||||
"auth": Buffer.from(`${username}:${decryptedPassword}`).toString('base64')
|
||||
}
|
||||
}
|
||||
})
|
||||
await fs.writeFile(`${location}/config.json`, payload)
|
||||
return location
|
||||
}
|
||||
export async function buildImage({
|
||||
applicationId,
|
||||
tag,
|
||||
@@ -590,15 +626,18 @@ export async function buildImage({
|
||||
}
|
||||
if (!debug) {
|
||||
await saveBuildLog({
|
||||
line: `Debug turned off. To see more details, allow it in the features tab.`,
|
||||
line: `Debug logging is disabled. Enable it above if necessary!`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||
|
||||
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` })
|
||||
const { dockerRegistry: { url, username, password } } = await prisma.application.findUnique({ where: { id: applicationId }, select: { dockerRegistry: true } })
|
||||
const location = await saveDockerRegistryCredentials({ url, username, password, workdir })
|
||||
console.log(`docker ${location ? `--config ${location}` : ''} build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}`)
|
||||
|
||||
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker ${location ? `--config ${location}` : ''} build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` })
|
||||
|
||||
const { status } = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
if (status === 'canceled') {
|
||||
|
@@ -17,30 +17,19 @@ export default async function (data) {
|
||||
secrets,
|
||||
pullmergeRequestId,
|
||||
port,
|
||||
dockerComposeConfiguration
|
||||
dockerComposeConfiguration,
|
||||
dockerComposeFileLocation
|
||||
} = data
|
||||
const fileYml = `${workdir}${baseDirectory}/docker-compose.yml`;
|
||||
const fileYaml = `${workdir}${baseDirectory}/docker-compose.yaml`;
|
||||
let dockerComposeRaw = null;
|
||||
let isYml = false;
|
||||
try {
|
||||
dockerComposeRaw = await fs.readFile(`${fileYml}`, 'utf8')
|
||||
isYml = true
|
||||
} catch (error) { }
|
||||
try {
|
||||
dockerComposeRaw = await fs.readFile(`${fileYaml}`, 'utf8')
|
||||
} catch (error) { }
|
||||
|
||||
if (!dockerComposeRaw) {
|
||||
throw ('docker-compose.yml or docker-compose.yaml are not found!');
|
||||
}
|
||||
const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`
|
||||
const dockerComposeRaw = await fs.readFile(fileYaml, 'utf8');
|
||||
const dockerComposeYaml = yaml.load(dockerComposeRaw)
|
||||
if (!dockerComposeYaml.services) {
|
||||
throw 'No Services found in docker-compose file.'
|
||||
}
|
||||
const envs = [
|
||||
`PORT=${port}`
|
||||
];
|
||||
const envs = [];
|
||||
if (Object.entries(dockerComposeYaml.services).length === 1) {
|
||||
envs.push(`PORT=${port}`)
|
||||
}
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (pullmergeRequestId) {
|
||||
@@ -64,19 +53,42 @@ export default async function (data) {
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
const composeVolumes = [];
|
||||
if (volumes.length > 0) {
|
||||
for (const volume of volumes) {
|
||||
let [v, path] = volume.split(':');
|
||||
composeVolumes[v] = {
|
||||
name: v,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let networks = {}
|
||||
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
||||
value['container_name'] = `${applicationId}-${key}`
|
||||
value['env_file'] = envFound ? [`${workdir}/.env`] : []
|
||||
value['labels'] = labels
|
||||
value['volumes'] = volumes
|
||||
// TODO: If we support separated volume for each service, we need to add it here
|
||||
if (value['volumes']?.length > 0) {
|
||||
value['volumes'] = value['volumes'].map((volume) => {
|
||||
let [v, path, permission] = volume.split(':');
|
||||
if (!path) {
|
||||
path = v;
|
||||
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`
|
||||
} else {
|
||||
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`
|
||||
}
|
||||
composeVolumes[v] = {
|
||||
name: v
|
||||
}
|
||||
return `${v}:${path}${permission ? ':' + permission : ''}`
|
||||
})
|
||||
}
|
||||
if (volumes.length > 0) {
|
||||
for (const volume of volumes) {
|
||||
value['volumes'].push(volume)
|
||||
}
|
||||
}
|
||||
if (dockerComposeConfiguration[key].port) {
|
||||
value['expose'] = [dockerComposeConfiguration[key].port]
|
||||
}
|
||||
@@ -89,10 +101,13 @@ export default async function (data) {
|
||||
}
|
||||
value['networks'] = [...value['networks'] || '', network]
|
||||
dockerComposeYaml.services[key] = { ...dockerComposeYaml.services[key], restart: defaultComposeConfiguration(network).restart, deploy: defaultComposeConfiguration(network).deploy }
|
||||
|
||||
}
|
||||
if (Object.keys(composeVolumes).length > 0) {
|
||||
dockerComposeYaml['volumes'] = { ...composeVolumes }
|
||||
}
|
||||
dockerComposeYaml['volumes'] = Object.assign({ ...dockerComposeYaml['volumes'] }, ...composeVolumes)
|
||||
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } })
|
||||
await fs.writeFile(`${workdir}/docker-compose.${isYml ? 'yml' : 'yaml'}`, yaml.dump(dockerComposeYaml));
|
||||
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
|
||||
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} pull` })
|
||||
await saveBuildLog({ line: 'Pulling images from Compose file.', buildId, applicationId });
|
||||
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} build --progress plain` })
|
||||
|
@@ -2,13 +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, baseDirectory } = data
|
||||
const { buildId, applicationId, tag, dockerId, debug, workdir, baseDirectory, baseImage } = data
|
||||
try {
|
||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
||||
await executeDockerCmd({
|
||||
buildId,
|
||||
debug,
|
||||
dockerId,
|
||||
command: `pack build -p ${workdir}${baseDirectory} ${applicationId}:${tag} --builder heroku/buildpacks:20`
|
||||
command: `pack build -p ${workdir}${baseDirectory} ${applicationId}:${tag} --builder ${baseImage}`
|
||||
})
|
||||
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
|
||||
} catch (error) {
|
||||
|
@@ -8,21 +8,19 @@ import type { Config } from 'unique-names-generator';
|
||||
import generator from 'generate-password';
|
||||
import crypto from 'crypto';
|
||||
import { promises as dns } from 'dns';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import os from 'os';
|
||||
import sshConfig from 'ssh-config';
|
||||
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import { checkContainer, removeContainer } from './docker';
|
||||
import { day } from './dayjs';
|
||||
import * as serviceFields from './services/serviceFields';
|
||||
import { saveBuildLog } from './buildPacks/common';
|
||||
import { scheduler } from './scheduler';
|
||||
import { supportedServiceTypesAndVersions } from './services/supportedVersions';
|
||||
import { includeServices } from './services/common';
|
||||
|
||||
export const version = '3.10.16';
|
||||
export const version = '3.12.0';
|
||||
export const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
export const sentryDSN = 'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
|
||||
const algorithm = 'aes-256-ctr';
|
||||
const customConfig: Config = {
|
||||
dictionaries: [adjectives, colors, animals],
|
||||
@@ -31,9 +29,6 @@ const customConfig: Config = {
|
||||
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`;
|
||||
export const defaultTraefikImage = `traefik:v2.8`;
|
||||
export function getAPIUrl() {
|
||||
if (process.env.GITPOD_WORKSPACE_URL) {
|
||||
@@ -44,7 +39,7 @@ export function getAPIUrl() {
|
||||
if (process.env.CODESANDBOX_HOST) {
|
||||
return `https://${process.env.CODESANDBOX_HOST.replace(/\$PORT/, '3001')}`;
|
||||
}
|
||||
return isDev ? 'http://localhost:3001' : 'http://localhost:3000';
|
||||
return isDev ? 'http://host.docker.internal:3001' : 'http://localhost:3000';
|
||||
}
|
||||
|
||||
export function getUIUrl() {
|
||||
@@ -198,7 +193,7 @@ 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()]);
|
||||
const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]);
|
||||
return JSON.stringify({
|
||||
iv: iv.toString('hex'),
|
||||
content: encrypted.toString('hex')
|
||||
@@ -244,7 +239,11 @@ export async function isDNSValid(hostname: any, domain: string): Promise<any> {
|
||||
}
|
||||
|
||||
export function getDomain(domain: string): string {
|
||||
return domain?.replace('https://', '').replace('http://', '');
|
||||
if (domain) {
|
||||
return domain?.replace('https://', '').replace('http://', '');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export async function isDomainConfigured({
|
||||
@@ -279,9 +278,7 @@ export async function isDomainConfigured({
|
||||
where: {
|
||||
OR: [
|
||||
{ fqdn: { endsWith: `//${nakedDomain}` } },
|
||||
{ fqdn: { endsWith: `//www.${nakedDomain}` } },
|
||||
{ minio: { apiFqdn: { endsWith: `//${nakedDomain}` } } },
|
||||
{ minio: { apiFqdn: { endsWith: `//www.${nakedDomain}` } } }
|
||||
{ fqdn: { endsWith: `//www.${nakedDomain}` } }
|
||||
],
|
||||
id: { not: checkOwn ? undefined : id },
|
||||
destinationDocker: {
|
||||
@@ -396,12 +393,6 @@ export function generateTimestamp(): string {
|
||||
return `${day().format('HH:mm:ss.SSS')}`;
|
||||
}
|
||||
|
||||
export async function listServicesWithIncludes(): Promise<any> {
|
||||
return await prisma.service.findMany({
|
||||
include: includeServices,
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
}
|
||||
|
||||
export const supportedDatabaseTypesAndVersions = [
|
||||
{
|
||||
@@ -511,56 +502,62 @@ export async function createRemoteEngineConfiguration(id: string) {
|
||||
const localPort = await getFreeSSHLocalPort(id);
|
||||
const {
|
||||
sshKey: { privateKey },
|
||||
network,
|
||||
remoteIpAddress,
|
||||
remotePort,
|
||||
remoteUser
|
||||
} = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } });
|
||||
await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 });
|
||||
// Needed for remote docker compose
|
||||
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}`);
|
||||
// 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}`);
|
||||
|
||||
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) { }
|
||||
}
|
||||
// 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) { }
|
||||
// }
|
||||
const config = sshConfig.parse('');
|
||||
const foundWildcard = config.find({ Host: '*' });
|
||||
if (!foundWildcard) {
|
||||
config.append({
|
||||
Host: '*',
|
||||
StrictHostKeyChecking: 'no',
|
||||
ControlMaster: 'auto',
|
||||
ControlPath: `${homedir}/.ssh/coolify-%r@%h:%p`,
|
||||
ControlPersist: '10m'
|
||||
})
|
||||
}
|
||||
const found = config.find({ Host: remoteIpAddress });
|
||||
if (!found) {
|
||||
config.append({
|
||||
Host: remoteIpAddress,
|
||||
Hostname: 'localhost',
|
||||
Port: localPort.toString(),
|
||||
User: remoteUser,
|
||||
IdentityFile: sshKeyFile,
|
||||
StrictHostKeyChecking: 'no'
|
||||
});
|
||||
}
|
||||
const Host = `${remoteIpAddress}-remote`
|
||||
|
||||
try {
|
||||
await asyncExecShell(`ssh-keygen -R ${Host}`);
|
||||
await asyncExecShell(`ssh-keygen -R ${remoteIpAddress}`);
|
||||
await asyncExecShell(`ssh-keygen -R localhost:${localPort}`);
|
||||
} catch (error) { }
|
||||
|
||||
|
||||
const found = config.find({ Host });
|
||||
const foundIp = config.find({ Host: remoteIpAddress });
|
||||
|
||||
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'
|
||||
});
|
||||
|
||||
try {
|
||||
await fs.stat(`${homedir}/.ssh/`);
|
||||
@@ -571,27 +568,23 @@ export async function createRemoteEngineConfiguration(id: string) {
|
||||
}
|
||||
export async function executeSSHCmd({ dockerId, command }) {
|
||||
const { execaCommand } = await import('execa')
|
||||
let { remoteEngine, remoteIpAddress, engine, remoteUser } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
||||
let { remoteEngine, remoteIpAddress } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
||||
if (remoteEngine) {
|
||||
await createRemoteEngineConfiguration(dockerId)
|
||||
engine = `ssh://${remoteIpAddress}`
|
||||
} 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')
|
||||
}
|
||||
}
|
||||
command = `ssh ${remoteIpAddress} ${command}`
|
||||
return await execaCommand(command)
|
||||
return await execaCommand(`ssh ${remoteIpAddress}-remote ${command}`)
|
||||
}
|
||||
export async function executeDockerCmd({ debug, buildId, applicationId, dockerId, command }: { debug?: boolean, buildId?: string, applicationId?: string, dockerId: string, command: string }): Promise<any> {
|
||||
const { execaCommand } = await import('execa')
|
||||
let { remoteEngine, remoteIpAddress, engine, remoteUser } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
||||
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
|
||||
if (remoteEngine) {
|
||||
await createRemoteEngineConfiguration(dockerId);
|
||||
engine = `ssh://${remoteIpAddress}`;
|
||||
engine = `ssh://${remoteIpAddress}-remote`;
|
||||
} else {
|
||||
engine = 'unix:///var/run/docker.sock';
|
||||
}
|
||||
@@ -722,11 +715,14 @@ export async function stopTraefikProxy(
|
||||
}
|
||||
|
||||
export async function listSettings(): Promise<any> {
|
||||
const settings = await prisma.setting.findFirst({});
|
||||
if (settings.proxyPassword) settings.proxyPassword = decrypt(settings.proxyPassword);
|
||||
return settings;
|
||||
return await prisma.setting.findFirst({});
|
||||
}
|
||||
|
||||
export function generateToken() {
|
||||
return jsonwebtoken.sign({
|
||||
nbf: Math.floor(Date.now() / 1000) - 30,
|
||||
}, process.env['COOLIFY_SECRET_KEY'])
|
||||
}
|
||||
export function generatePassword({
|
||||
length = 24,
|
||||
symbols = false,
|
||||
@@ -977,7 +973,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
||||
}
|
||||
}
|
||||
export function isARM(arch: string) {
|
||||
if (arch === 'arm' || arch === 'arm64') {
|
||||
if (arch === 'arm' || arch === 'arm64' || arch === 'aarch' || arch === 'aarch64') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1095,6 +1091,7 @@ export const createDirectories = async ({
|
||||
repository: string;
|
||||
buildId: string;
|
||||
}): Promise<{ workdir: string; repodir: string }> => {
|
||||
repository = repository.replaceAll(' ', '')
|
||||
const repodir = `/tmp/build-sources/${repository}/`;
|
||||
const workdir = `/tmp/build-sources/${repository}/${buildId}`;
|
||||
let workdirFound = false;
|
||||
@@ -1399,7 +1396,7 @@ export async function startTraefikTCPProxy(
|
||||
`--entrypoints.tcp.address=:${publicPort}`,
|
||||
`--entryPoints.tcp.forwardedHeaders.insecure=true`,
|
||||
`--providers.http.endpoint=${traefikUrl}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp&address=${dependentId}`,
|
||||
'--providers.http.pollTimeout=2s',
|
||||
'--providers.http.pollTimeout=10s',
|
||||
'--log.level=error'
|
||||
],
|
||||
ports: [`${publicPort}:${publicPort}`],
|
||||
@@ -1447,13 +1444,19 @@ export async function getServiceFromDB({
|
||||
const settings = await prisma.setting.findFirst();
|
||||
const body = await prisma.service.findFirst({
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: includeServices
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
persistentStorage: true,
|
||||
serviceSecret: true,
|
||||
serviceSetting: true,
|
||||
wordpress: true,
|
||||
plausibleAnalytics: true,
|
||||
}
|
||||
});
|
||||
if (!body) {
|
||||
return null
|
||||
}
|
||||
let { type } = body;
|
||||
type = fixType(type);
|
||||
// body.type = fixType(body.type);
|
||||
|
||||
if (body?.serviceSecret.length > 0) {
|
||||
body.serviceSecret = body.serviceSecret.map((s) => {
|
||||
@@ -1461,87 +1464,18 @@ export async function getServiceFromDB({
|
||||
return s;
|
||||
});
|
||||
}
|
||||
if (body.wordpress) {
|
||||
body.wordpress.ftpPassword = decrypt(body.wordpress.ftpPassword);
|
||||
}
|
||||
|
||||
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) {
|
||||
const update = {};
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (k.isNumber && temp === '') {
|
||||
temp = null;
|
||||
}
|
||||
update[k.name] = temp;
|
||||
});
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
export function getUpdateableFields(type: string, data: any) {
|
||||
const update = {};
|
||||
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;
|
||||
return type?.replaceAll(' ', '').toLowerCase() || null;
|
||||
}
|
||||
|
||||
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',
|
||||
@@ -1558,6 +1492,7 @@ export function errorHandler({
|
||||
message: string | any;
|
||||
}) {
|
||||
if (message.message) message = message.message;
|
||||
Sentry.captureException(message);
|
||||
throw { status, message };
|
||||
}
|
||||
export async function generateSshKeyPair(): Promise<{ publicKey: string; privateKey: string }> {
|
||||
@@ -1681,7 +1616,9 @@ export function persistentVolumes(id, persistentStorage, config) {
|
||||
for (const [key, value] of Object.entries(config)) {
|
||||
if (value.volumes) {
|
||||
for (const volume of value.volumes) {
|
||||
volumeSet.add(volume);
|
||||
if (!volume.startsWith('/')) {
|
||||
volumeSet.add(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ Bree.extend(TSBree);
|
||||
|
||||
const options: any = {
|
||||
defaultExtension: 'js',
|
||||
logger: new Cabin(),
|
||||
logger: false,
|
||||
// logger: false,
|
||||
// workerMessageHandler: async ({ name, message }) => {
|
||||
// if (name === 'deployApplication' && message?.deploying) {
|
||||
|
@@ -1,20 +1,51 @@
|
||||
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}`);
|
||||
});
|
||||
import { isARM, isDev } from "./common";
|
||||
import fs from 'fs/promises';
|
||||
export async function getTemplates() {
|
||||
const templatePath = isDev ? './templates.json' : '/app/templates.json';
|
||||
const open = await fs.open(templatePath, 'r');
|
||||
try {
|
||||
let data = await open.readFile({ encoding: 'utf-8' });
|
||||
let jsonData = JSON.parse(data)
|
||||
if (isARM(process.arch)) {
|
||||
jsonData = jsonData.filter(d => d.arch !== 'amd64')
|
||||
}
|
||||
return jsonData;
|
||||
} catch (error) {
|
||||
return []
|
||||
} finally {
|
||||
await open?.close()
|
||||
}
|
||||
return { ...service, network, port, workdir, image, secrets }
|
||||
}
|
||||
}
|
||||
const compareSemanticVersions = (a: string, b: string) => {
|
||||
const a1 = a.split('.');
|
||||
const b1 = b.split('.');
|
||||
const len = Math.min(a1.length, b1.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const a2 = +a1[i] || 0;
|
||||
const b2 = +b1[i] || 0;
|
||||
if (a2 !== b2) {
|
||||
return a2 > b2 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return b1.length - a1.length;
|
||||
};
|
||||
export async function getTags(type: string) {
|
||||
|
||||
try {
|
||||
if (type) {
|
||||
const tagsPath = isDev ? './tags.json' : '/app/tags.json';
|
||||
const data = await fs.readFile(tagsPath, 'utf8')
|
||||
let tags = JSON.parse(data)
|
||||
if (tags) {
|
||||
tags = tags.find((tag: any) => tag.name.includes(type))
|
||||
tags.tags = tags.tags.sort(compareSemanticVersions).reverse();
|
||||
return tags
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return []
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -1,367 +1,9 @@
|
||||
|
||||
import cuid from 'cuid';
|
||||
import { encrypt, generatePassword, prisma } from '../common';
|
||||
|
||||
export const includeServices: any = {
|
||||
destinationDocker: true,
|
||||
persistentStorage: true,
|
||||
serviceSecret: true,
|
||||
minio: true,
|
||||
plausibleAnalytics: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true,
|
||||
ghost: true,
|
||||
meiliSearch: true,
|
||||
umami: true,
|
||||
hasura: true,
|
||||
fider: true,
|
||||
moodle: true,
|
||||
appwrite: true,
|
||||
glitchTip: true,
|
||||
searxng: true,
|
||||
weblate: true,
|
||||
taiga: true,
|
||||
};
|
||||
export async function configureServiceType({
|
||||
id,
|
||||
type
|
||||
}: {
|
||||
id: string;
|
||||
type: string;
|
||||
}): Promise<void> {
|
||||
if (type === 'plausibleanalytics') {
|
||||
const password = encrypt(generatePassword({}));
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'plausibleanalytics';
|
||||
const secretKeyBase = encrypt(generatePassword({ length: 64 }));
|
||||
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
plausibleAnalytics: {
|
||||
create: {
|
||||
postgresqlDatabase,
|
||||
postgresqlUser,
|
||||
postgresqlPassword,
|
||||
password,
|
||||
secretKeyBase
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'nocodb') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: { type }
|
||||
});
|
||||
} else if (type === 'minio') {
|
||||
const rootUser = cuid();
|
||||
const rootUserPassword = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: { type, minio: { create: { rootUser, rootUserPassword } } }
|
||||
});
|
||||
} else if (type === 'vscodeserver') {
|
||||
const password = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: { type, vscodeserver: { create: { password } } }
|
||||
});
|
||||
} else if (type === 'wordpress') {
|
||||
const mysqlUser = cuid();
|
||||
const mysqlPassword = encrypt(generatePassword({}));
|
||||
const mysqlRootUser = cuid();
|
||||
const mysqlRootUserPassword = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } }
|
||||
}
|
||||
});
|
||||
} else if (type === 'vaultwarden') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type
|
||||
}
|
||||
});
|
||||
} else if (type === 'languagetool') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type
|
||||
}
|
||||
});
|
||||
} else if (type === 'n8n') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type
|
||||
}
|
||||
});
|
||||
} else if (type === 'uptimekuma') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type
|
||||
}
|
||||
});
|
||||
} else if (type === 'ghost') {
|
||||
const defaultEmail = `${cuid()}@example.com`;
|
||||
const defaultPassword = encrypt(generatePassword({}));
|
||||
const mariadbUser = cuid();
|
||||
const mariadbPassword = encrypt(generatePassword({}));
|
||||
const mariadbRootUser = cuid();
|
||||
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
ghost: {
|
||||
create: {
|
||||
defaultEmail,
|
||||
defaultPassword,
|
||||
mariadbUser,
|
||||
mariadbPassword,
|
||||
mariadbRootUser,
|
||||
mariadbRootUserPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'meilisearch') {
|
||||
const masterKey = encrypt(generatePassword({ length: 32 }));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
meiliSearch: { create: { masterKey } }
|
||||
}
|
||||
});
|
||||
} else if (type === 'umami') {
|
||||
const umamiAdminPassword = encrypt(generatePassword({}));
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'umami';
|
||||
const hashSalt = encrypt(generatePassword({ length: 64 }));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
umami: {
|
||||
create: {
|
||||
umamiAdminPassword,
|
||||
postgresqlDatabase,
|
||||
postgresqlPassword,
|
||||
postgresqlUser,
|
||||
hashSalt
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'hasura') {
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'hasura';
|
||||
const graphQLAdminPassword = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
hasura: {
|
||||
create: {
|
||||
postgresqlDatabase,
|
||||
postgresqlPassword,
|
||||
postgresqlUser,
|
||||
graphQLAdminPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'fider') {
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'fider';
|
||||
const jwtSecret = encrypt(generatePassword({ length: 64, symbols: true }));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
fider: {
|
||||
create: {
|
||||
postgresqlDatabase,
|
||||
postgresqlPassword,
|
||||
postgresqlUser,
|
||||
jwtSecret
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'moodle') {
|
||||
const defaultUsername = cuid();
|
||||
const defaultPassword = encrypt(generatePassword({}));
|
||||
const defaultEmail = `${cuid()} @example.com`;
|
||||
const mariadbUser = cuid();
|
||||
const mariadbPassword = encrypt(generatePassword({}));
|
||||
const mariadbDatabase = 'moodle_db';
|
||||
const mariadbRootUser = cuid();
|
||||
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
moodle: {
|
||||
create: {
|
||||
defaultUsername,
|
||||
defaultPassword,
|
||||
defaultEmail,
|
||||
mariadbUser,
|
||||
mariadbPassword,
|
||||
mariadbDatabase,
|
||||
mariadbRootUser,
|
||||
mariadbRootUserPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} 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`;
|
||||
const defaultPassword = encrypt(generatePassword({}));
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'glitchTip';
|
||||
const secretKeyBase = encrypt(generatePassword({ length: 64 }));
|
||||
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
glitchTip: {
|
||||
create: {
|
||||
postgresqlDatabase,
|
||||
postgresqlUser,
|
||||
postgresqlPassword,
|
||||
secretKeyBase,
|
||||
defaultEmail,
|
||||
defaultUsername,
|
||||
defaultPassword,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'searxng') {
|
||||
const secretKey = encrypt(generatePassword({ length: 32, isHex: true }))
|
||||
const redisPassword = encrypt(generatePassword({}));
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
searxng: {
|
||||
create: {
|
||||
secretKey,
|
||||
redisPassword,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'weblate') {
|
||||
const adminPassword = encrypt(generatePassword({}))
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'weblate';
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
weblate: {
|
||||
create: {
|
||||
adminPassword,
|
||||
postgresqlHost: `${id}-postgresql`,
|
||||
postgresqlPort: 5432,
|
||||
postgresqlUser,
|
||||
postgresqlPassword,
|
||||
postgresqlDatabase,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'taiga') {
|
||||
const secretKey = encrypt(generatePassword({}))
|
||||
const erlangSecret = encrypt(generatePassword({}))
|
||||
const rabbitMQUser = cuid();
|
||||
const djangoAdminUser = cuid();
|
||||
const djangoAdminPassword = encrypt(generatePassword({}))
|
||||
const rabbitMQPassword = encrypt(generatePassword({}))
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword({}));
|
||||
const postgresqlDatabase = 'taiga';
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
taiga: {
|
||||
create: {
|
||||
secretKey,
|
||||
erlangSecret,
|
||||
djangoAdminUser,
|
||||
djangoAdminPassword,
|
||||
rabbitMQUser,
|
||||
rabbitMQPassword,
|
||||
postgresqlHost: `${id}-postgresql`,
|
||||
postgresqlPort: 5432,
|
||||
postgresqlUser,
|
||||
postgresqlPassword,
|
||||
postgresqlDatabase,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
import { decrypt, prisma } from '../common';
|
||||
|
||||
export async function removeService({ id }: { id: string }): Promise<void> {
|
||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.serviceSetting.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 } });
|
||||
@@ -380,4 +22,18 @@ export async function removeService({ id }: { id: string }): Promise<void> {
|
||||
await prisma.taiga.deleteMany({ where: { serviceId: id } });
|
||||
|
||||
await prisma.service.delete({ where: { id } });
|
||||
}
|
||||
export async function verifyAndDecryptServiceSecrets(id: string) {
|
||||
const secrets = await prisma.serviceSecret.findMany({ where: { serviceId: id } })
|
||||
let decryptedSecrets = secrets.map(secret => {
|
||||
const { name, value } = secret
|
||||
if (value) {
|
||||
let rawValue = decrypt(value)
|
||||
rawValue = rawValue.replaceAll(/\$/gi, '$$$')
|
||||
return { name, value: rawValue }
|
||||
}
|
||||
return { name, value }
|
||||
|
||||
})
|
||||
return decryptedSecrets
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,278 +0,0 @@
|
||||
/*
|
||||
Example of a supported version:
|
||||
{
|
||||
// Name used to identify the service internally
|
||||
name: 'umami',
|
||||
// Fancier name to show to the user
|
||||
fancyName: 'Umami',
|
||||
// Docker base image for the service
|
||||
baseImage: 'ghcr.io/mikecao/umami',
|
||||
// Optional: If there is any dependent image, you should list it here
|
||||
images: [],
|
||||
// Usable tags
|
||||
versions: ['postgresql-latest'],
|
||||
// Which tag is the recommended
|
||||
recommendedVersion: 'postgresql-latest',
|
||||
// Application's default port, Umami listens on 3000
|
||||
ports: {
|
||||
main: 3000
|
||||
}
|
||||
}
|
||||
*/
|
||||
export const supportedServiceTypesAndVersions = [
|
||||
{
|
||||
name: 'plausibleanalytics',
|
||||
fancyName: 'Plausible Analytics',
|
||||
baseImage: 'plausible/analytics',
|
||||
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
|
||||
versions: ['latest', 'stable'],
|
||||
recommendedVersion: 'stable',
|
||||
ports: {
|
||||
main: 8000
|
||||
},
|
||||
labels: ['analytics', 'plausible', 'plausible-analytics', 'gdpr', 'no-cookie']
|
||||
},
|
||||
{
|
||||
name: 'nocodb',
|
||||
fancyName: 'NocoDB',
|
||||
baseImage: 'nocodb/nocodb',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8080
|
||||
},
|
||||
labels: ['nocodb', 'airtable', 'database']
|
||||
},
|
||||
{
|
||||
name: 'minio',
|
||||
fancyName: 'MinIO',
|
||||
baseImage: 'minio/minio',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 9001
|
||||
},
|
||||
labels: ['minio', 's3', 'storage']
|
||||
},
|
||||
{
|
||||
name: 'vscodeserver',
|
||||
fancyName: 'VSCode Server',
|
||||
baseImage: 'codercom/code-server',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8080
|
||||
},
|
||||
labels: ['vscodeserver', 'vscode', 'code-server', 'ide']
|
||||
},
|
||||
{
|
||||
name: 'wordpress',
|
||||
fancyName: 'WordPress',
|
||||
baseImage: 'wordpress',
|
||||
images: ['bitnami/mysql:5.7'],
|
||||
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 80
|
||||
},
|
||||
labels: ['wordpress', 'blog', 'cms']
|
||||
},
|
||||
{
|
||||
name: 'vaultwarden',
|
||||
fancyName: 'Vaultwarden',
|
||||
baseImage: 'vaultwarden/server',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 80
|
||||
},
|
||||
labels: ['vaultwarden', 'password-manager', 'passwords']
|
||||
},
|
||||
{
|
||||
name: 'languagetool',
|
||||
fancyName: 'LanguageTool',
|
||||
baseImage: 'silviof/docker-languagetool',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8010
|
||||
},
|
||||
labels: ['languagetool', 'grammar', 'spell-checker']
|
||||
},
|
||||
{
|
||||
name: 'n8n',
|
||||
fancyName: 'n8n',
|
||||
baseImage: 'n8nio/n8n',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 5678
|
||||
},
|
||||
labels: ['n8n', 'workflow', 'automation', 'ifttt', 'zapier', 'nodered']
|
||||
},
|
||||
{
|
||||
name: 'uptimekuma',
|
||||
fancyName: 'Uptime Kuma',
|
||||
baseImage: 'louislam/uptime-kuma',
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 3001
|
||||
},
|
||||
labels: ['uptimekuma', 'uptime', 'monitoring']
|
||||
},
|
||||
{
|
||||
name: 'ghost',
|
||||
fancyName: 'Ghost',
|
||||
baseImage: 'bitnami/ghost',
|
||||
images: ['bitnami/mariadb'],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 2368
|
||||
},
|
||||
labels: ['ghost', 'blog', 'cms']
|
||||
},
|
||||
{
|
||||
name: 'meilisearch',
|
||||
fancyName: 'Meilisearch',
|
||||
baseImage: 'getmeili/meilisearch',
|
||||
images: [],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 7700
|
||||
},
|
||||
labels: ['meilisearch', 'search', 'search-engine']
|
||||
},
|
||||
{
|
||||
name: 'umami',
|
||||
fancyName: 'Umami',
|
||||
baseImage: 'ghcr.io/umami-software/umami',
|
||||
images: ['postgres:12-alpine'],
|
||||
versions: ['postgresql-latest'],
|
||||
recommendedVersion: 'postgresql-latest',
|
||||
ports: {
|
||||
main: 3000
|
||||
},
|
||||
labels: ['umami', 'analytics', 'gdpr', 'no-cookie']
|
||||
},
|
||||
{
|
||||
name: 'hasura',
|
||||
fancyName: 'Hasura',
|
||||
baseImage: 'hasura/graphql-engine',
|
||||
images: ['postgres:12-alpine'],
|
||||
versions: ['latest', 'v2.10.0', 'v2.5.1'],
|
||||
recommendedVersion: 'v2.10.0',
|
||||
ports: {
|
||||
main: 8080
|
||||
},
|
||||
labels: ['hasura', 'graphql', 'database']
|
||||
},
|
||||
{
|
||||
name: 'fider',
|
||||
fancyName: 'Fider',
|
||||
baseImage: 'getfider/fider',
|
||||
images: ['postgres:12-alpine'],
|
||||
versions: ['stable'],
|
||||
recommendedVersion: 'stable',
|
||||
ports: {
|
||||
main: 3000
|
||||
},
|
||||
labels: ['fider', 'feedback', 'suggestions']
|
||||
},
|
||||
{
|
||||
name: 'appwrite',
|
||||
fancyName: 'Appwrite',
|
||||
baseImage: 'appwrite/appwrite',
|
||||
images: ['mariadb:10.7', 'redis:6.2-alpine', 'appwrite/telegraf:1.4.0'],
|
||||
versions: ['latest', '1.0', '0.15.3'],
|
||||
recommendedVersion: '1.0',
|
||||
ports: {
|
||||
main: 80
|
||||
},
|
||||
labels: ['appwrite', 'database', 'storage', 'api', 'serverless']
|
||||
},
|
||||
// {
|
||||
// name: 'moodle',
|
||||
// fancyName: 'Moodle',
|
||||
// baseImage: 'bitnami/moodle',
|
||||
// images: [],
|
||||
// versions: ['latest', 'v4.0.2'],
|
||||
// recommendedVersion: 'latest',
|
||||
// ports: {
|
||||
// main: 8080
|
||||
// }
|
||||
// }
|
||||
{
|
||||
name: 'glitchTip',
|
||||
fancyName: 'GlitchTip',
|
||||
baseImage: 'glitchtip/glitchtip',
|
||||
images: ['postgres:14-alpine', 'redis:7-alpine'],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8000
|
||||
},
|
||||
labels: ['glitchtip', 'error-reporting', 'error', 'sentry', 'bugsnag']
|
||||
},
|
||||
{
|
||||
name: 'searxng',
|
||||
fancyName: 'SearXNG',
|
||||
baseImage: 'searxng/searxng',
|
||||
images: [],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8080
|
||||
},
|
||||
labels: ['searxng', 'search', 'search-engine']
|
||||
},
|
||||
{
|
||||
name: 'weblate',
|
||||
fancyName: 'Weblate',
|
||||
baseImage: 'weblate/weblate',
|
||||
images: ['postgres:14-alpine', 'redis:6-alpine'],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8080
|
||||
},
|
||||
labels: ['weblate', 'translation', 'localization']
|
||||
},
|
||||
// {
|
||||
// name: 'taiga',
|
||||
// fancyName: 'Taiga',
|
||||
// baseImage: 'taigaio/taiga-front',
|
||||
// images: ['postgres:12.3', 'rabbitmq:3.8-management-alpine', 'taigaio/taiga-back', 'taigaio/taiga-events', 'taigaio/taiga-protected'],
|
||||
// versions: ['latest'],
|
||||
// recommendedVersion: 'latest',
|
||||
// ports: {
|
||||
// main: 80
|
||||
// }
|
||||
// },
|
||||
{
|
||||
name: 'grafana',
|
||||
fancyName: 'Grafana',
|
||||
baseImage: 'grafana/grafana',
|
||||
images: [],
|
||||
versions: ['latest', '9.1.3', '9.1.2', '9.0.8', '8.3.11', '8.4.11', '8.5.11'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 3000
|
||||
},
|
||||
labels: ['grafana', 'monitoring', 'metrics', 'dashboard']
|
||||
},
|
||||
{
|
||||
name: 'trilium',
|
||||
fancyName: 'Trilium Notes',
|
||||
baseImage: 'zadam/trilium',
|
||||
images: [],
|
||||
versions: ['latest'],
|
||||
recommendedVersion: 'latest',
|
||||
ports: {
|
||||
main: 8080
|
||||
},
|
||||
labels: ['trilium', 'notes', 'note-taking', 'wiki']
|
||||
},
|
||||
];
|
Reference in New Issue
Block a user