mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-27 12:33:54 +00:00
Merge branch 'next' into main
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
import cuid from 'cuid';
|
||||
import crypto from 'node:crypto'
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import axios from 'axios';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
import csv from 'csvtojson';
|
||||
|
||||
import { day } from '../../../../lib/dayjs';
|
||||
import { makeLabelForStandaloneApplication, setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
||||
import { checkDomainsIsValidInDNS, checkDoubleBranch, checkExposedPort, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
||||
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
|
||||
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
||||
import { checkDomainsIsValidInDNS, checkExposedPort, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
||||
import { checkContainer, formatLabelsOnDocker, removeContainer } from '../../../../lib/docker';
|
||||
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication, RestartPreviewApplication, GetBuilds } from './types';
|
||||
@@ -242,7 +241,8 @@ export async function getApplicationFromDB(id: string, teamId: string) {
|
||||
secrets: true,
|
||||
persistentStorage: true,
|
||||
connectedDatabase: true,
|
||||
previewApplication: true
|
||||
previewApplication: true,
|
||||
dockerRegistry: true
|
||||
}
|
||||
});
|
||||
if (!application) {
|
||||
@@ -352,6 +352,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
||||
publishDirectory,
|
||||
baseDirectory,
|
||||
dockerFileLocation,
|
||||
dockerComposeFileLocation,
|
||||
denoMainFile
|
||||
});
|
||||
if (baseDatabaseBranch) {
|
||||
@@ -774,6 +775,7 @@ export async function saveApplicationSource(request: FastifyRequest<SaveApplicat
|
||||
|
||||
export async function getGitHubToken(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const { id } = request.params
|
||||
const { teamId } = request.user
|
||||
const application: any = await getApplicationFromDB(id, teamId);
|
||||
@@ -785,13 +787,13 @@ export async function getGitHubToken(request: FastifyRequest<OnlyId>, reply: Fas
|
||||
const githubToken = jsonwebtoken.sign(payload, application.gitSource.githubApp.privateKey, {
|
||||
algorithm: 'RS256'
|
||||
});
|
||||
const { data } = await axios.post(`${application.gitSource.apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`, {}, {
|
||||
const { token } = await got.post(`${application.gitSource.apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${githubToken}`
|
||||
'Authorization': `Bearer ${githubToken}`,
|
||||
}
|
||||
})
|
||||
}).json()
|
||||
return reply.code(201).send({
|
||||
token: data.token
|
||||
token
|
||||
})
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -822,7 +824,7 @@ export async function saveRepository(request, reply) {
|
||||
let { repository, branch, projectId, autodeploy, webhookToken, isPublicRepository = false } = request.body
|
||||
|
||||
repository = repository.toLowerCase();
|
||||
branch = branch.toLowerCase();
|
||||
|
||||
projectId = Number(projectId);
|
||||
if (webhookToken) {
|
||||
await prisma.application.update({
|
||||
@@ -879,6 +881,16 @@ export async function getBuildPack(request) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveRegistry(request, reply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { registryId } = request.body
|
||||
await prisma.application.update({ where: { id }, data: { dockerRegistry: { connect: { id: registryId } } } });
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function saveBuildPack(request, reply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
@@ -973,6 +985,10 @@ export async function saveSecret(request: FastifyRequest<SaveSecret>, reply: Fas
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { name, value, isBuildSecret = false } = request.body
|
||||
const found = await prisma.secret.findMany({ where: { applicationId: id, name } })
|
||||
if (found.length > 0) {
|
||||
throw ({ message: 'Secret already exists.' })
|
||||
}
|
||||
await prisma.secret.create({
|
||||
data: { name, value: encrypt(value.trim()), isBuildSecret, isPRMRSecret: false, application: { connect: { id } } }
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { OnlyId } from '../../../../types';
|
||||
import { cancelDeployment, checkDNS, checkDomain, checkRepository, cleanupUnconfiguredApplications, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, getUsageByContainer, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers';
|
||||
import { cancelDeployment, checkDNS, checkDomain, checkRepository, cleanupUnconfiguredApplications, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, getUsageByContainer, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRegistry, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers';
|
||||
|
||||
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuilds, GetImages, RestartPreviewApplication, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
|
||||
|
||||
@@ -64,6 +64,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get('/:id/configuration/buildpack', async (request) => await getBuildPack(request));
|
||||
fastify.post('/:id/configuration/buildpack', async (request, reply) => await saveBuildPack(request, reply));
|
||||
|
||||
fastify.post('/:id/configuration/registry', async (request, reply) => await saveRegistry(request, reply));
|
||||
|
||||
fastify.post('/:id/configuration/database', async (request, reply) => await saveConnectedDatabase(request, reply));
|
||||
|
||||
fastify.get<OnlyId>('/:id/configuration/sshkey', async (request) => await getGitLabSSHKey(request));
|
||||
|
||||
@@ -2,13 +2,20 @@ import { FastifyPluginAsync } from 'fastify';
|
||||
import { errorHandler, listSettings, version } from '../../../../lib/common';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.addHook('onRequest', async (request) => {
|
||||
try {
|
||||
await request.jwtVerify()
|
||||
} catch(error) {
|
||||
return
|
||||
}
|
||||
});
|
||||
fastify.get('/', async (request) => {
|
||||
const teamId = request.user?.teamId;
|
||||
const settings = await listSettings()
|
||||
try {
|
||||
return {
|
||||
ipv4: teamId ? settings.ipv4 : 'nope',
|
||||
ipv6: teamId ? settings.ipv6 : 'nope',
|
||||
ipv4: teamId ? settings.ipv4 : null,
|
||||
ipv6: teamId ? settings.ipv6 : null,
|
||||
version,
|
||||
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
||||
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
||||
|
||||
@@ -204,8 +204,8 @@ export async function assignSSHKey(request: FastifyRequest) {
|
||||
}
|
||||
export async function verifyRemoteDockerEngineFn(id: string) {
|
||||
await createRemoteEngineConfiguration(id);
|
||||
const { remoteIpAddress, remoteUser, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
|
||||
const host = `ssh://${remoteUser}@${remoteIpAddress}`
|
||||
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
|
||||
const host = `ssh://${remoteIpAddress}-remote`
|
||||
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`);
|
||||
if (!stdout) {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
|
||||
@@ -215,8 +215,8 @@ export async function verifyRemoteDockerEngineFn(id: string) {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable coolify-infra`);
|
||||
}
|
||||
if (isCoolifyProxyUsed) await startTraefikProxy(id);
|
||||
const { stdout: daemonJson } = await executeSSHCmd({ dockerId: id, command: `cat /etc/docker/daemon.json` });
|
||||
try {
|
||||
const { stdout: daemonJson } = await executeSSHCmd({ dockerId: id, command: `cat /etc/docker/daemon.json` });
|
||||
let daemonJsonParsed = JSON.parse(daemonJson);
|
||||
let isUpdated = false;
|
||||
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import axios from "axios";
|
||||
import { compareVersions } from "compare-versions";
|
||||
import cuid from "cuid";
|
||||
import bcrypt from "bcryptjs";
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
import {
|
||||
asyncExecShell,
|
||||
asyncSleep,
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
uniqueName,
|
||||
version,
|
||||
} from "../../../lib/common";
|
||||
import { supportedServiceTypesAndVersions } from "../../../lib/services/supportedVersions";
|
||||
import { scheduler } from "../../../lib/scheduler";
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import type { Login, Update } from ".";
|
||||
@@ -36,16 +36,59 @@ export async function cleanupManually(request: FastifyRequest) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function refreshTags() {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
try {
|
||||
if (isDev) {
|
||||
const tags = await fs.readFile('./devTags.json', 'utf8')
|
||||
await fs.writeFile('./tags.json', tags)
|
||||
} else {
|
||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
|
||||
await fs.writeFile('/app/tags.json', tags)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function refreshTemplates() {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
try {
|
||||
if (isDev) {
|
||||
const response = await fs.readFile('./devTemplates.yaml', 'utf8')
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)))
|
||||
} else {
|
||||
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
|
||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function checkUpdate(request: FastifyRequest) {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const isStaging =
|
||||
request.hostname === "staging.coolify.io" ||
|
||||
request.hostname === "arm.coolify.io";
|
||||
const currentVersion = version;
|
||||
const { data: versions } = await axios.get(
|
||||
`https://get.coollabs.io/versions.json?appId=${process.env["COOLIFY_APP_ID"]}&version=${currentVersion}`
|
||||
);
|
||||
const latestVersion = versions["coolify"].main.version;
|
||||
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
|
||||
searchParams: {
|
||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||
version: currentVersion
|
||||
}
|
||||
}).json()
|
||||
const latestVersion = coolify.main.version;
|
||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||
if (isStaging) {
|
||||
return {
|
||||
@@ -357,7 +400,6 @@ export async function getCurrentUser(
|
||||
return {
|
||||
settings: await prisma.setting.findFirst(),
|
||||
pendingInvitations,
|
||||
supportedServiceTypesAndVersions,
|
||||
token,
|
||||
...request.user,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { checkUpdate, login, showDashboard, update, resetQueue, getCurrentUser, cleanupManually, restartCoolify } from './handlers';
|
||||
import { GetCurrentUser } from './types';
|
||||
import pump from 'pump'
|
||||
import fs from 'fs'
|
||||
import { asyncExecShell, encrypt, errorHandler, prisma } from '../../../lib/common';
|
||||
|
||||
export interface Update {
|
||||
Body: { latestVersion: string }
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
import { prisma, uniqueName, asyncExecShell, getServiceFromDB, getContainerUsage, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, ComposeFile, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, executeDockerCmd, checkDomainsIsValidInDNS, checkExposedPort, listSettings } from '../../../../lib/common';
|
||||
import { day } from '../../../../lib/dayjs';
|
||||
import { checkContainer, isContainerExited } from '../../../../lib/docker';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import cuid from 'cuid';
|
||||
|
||||
import type { OnlyId } from '../../../../types';
|
||||
import { prisma, uniqueName, asyncExecShell, getServiceFromDB, getContainerUsage, isDomainConfigured, fixType, decrypt, encrypt, ComposeFile, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, executeDockerCmd, checkDomainsIsValidInDNS, checkExposedPort, listSettings, generateToken } from '../../../../lib/common';
|
||||
import { day } from '../../../../lib/dayjs';
|
||||
import { checkContainer, } from '../../../../lib/docker';
|
||||
import { removeService } from '../../../../lib/services/common';
|
||||
import { getTags, getTemplates } from '../../../../lib/services';
|
||||
|
||||
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetGlitchTipSettings, SetWordpressSettings } from './types';
|
||||
import { supportedServiceTypesAndVersions } from '../../../../lib/services/supportedVersions';
|
||||
import { configureServiceType, removeService } from '../../../../lib/services/common';
|
||||
import type { OnlyId } from '../../../../types';
|
||||
|
||||
export async function listServices(request: FastifyRequest) {
|
||||
try {
|
||||
@@ -67,30 +69,207 @@ export async function getServiceStatus(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.params;
|
||||
|
||||
let isRunning = false;
|
||||
let isExited = false
|
||||
let isRestarting = false;
|
||||
const service = await getServiceFromDB({ id, teamId });
|
||||
const { destinationDockerId, settings } = service;
|
||||
|
||||
let payload = {}
|
||||
if (destinationDockerId) {
|
||||
const status = await checkContainer({ dockerId: service.destinationDocker.id, container: id });
|
||||
if (status?.found) {
|
||||
isRunning = status.status.isRunning;
|
||||
isExited = status.status.isExited;
|
||||
isRestarting = status.status.isRestarting
|
||||
const { stdout: containers } = await executeDockerCmd({
|
||||
dockerId: service.destinationDocker.id,
|
||||
command:
|
||||
`docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'`
|
||||
});
|
||||
const containersArray = containers.trim().split('\n');
|
||||
if (containersArray.length > 0 && containersArray[0] !== '') {
|
||||
const templates = await getTemplates();
|
||||
let template = templates.find(t => t.type === service.type);
|
||||
const templateStr = JSON.stringify(template)
|
||||
if (templateStr) {
|
||||
template = JSON.parse(templateStr.replaceAll('$$id', service.id));
|
||||
}
|
||||
for (const container of containersArray) {
|
||||
let isRunning = false;
|
||||
let isExited = false;
|
||||
let isRestarting = false;
|
||||
let isExcluded = false;
|
||||
const containerObj = JSON.parse(container);
|
||||
const exclude = template?.services[containerObj.Names]?.exclude;
|
||||
if (exclude) {
|
||||
payload[containerObj.Names] = {
|
||||
status: {
|
||||
isExcluded: true,
|
||||
isRunning: false,
|
||||
isExited: false,
|
||||
isRestarting: false,
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const status = containerObj.State
|
||||
if (status === 'running') {
|
||||
isRunning = true;
|
||||
}
|
||||
if (status === 'exited') {
|
||||
isExited = true;
|
||||
}
|
||||
if (status === 'restarting') {
|
||||
isRestarting = true;
|
||||
}
|
||||
payload[containerObj.Names] = {
|
||||
status: {
|
||||
isExcluded,
|
||||
isRunning,
|
||||
isExited,
|
||||
isRestarting
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
isRunning,
|
||||
isExited,
|
||||
settings
|
||||
}
|
||||
return payload
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function parseAndFindServiceTemplates(service: any, workdir?: string, isDeploy: boolean = false) {
|
||||
const templates = await getTemplates()
|
||||
const foundTemplate = templates.find(t => fixType(t.type) === service.type)
|
||||
let parsedTemplate = {}
|
||||
if (foundTemplate) {
|
||||
if (!isDeploy) {
|
||||
for (const [key, value] of Object.entries(foundTemplate.services)) {
|
||||
const realKey = key.replace('$$id', service.id)
|
||||
let name = value.name
|
||||
if (!name) {
|
||||
if (Object.keys(foundTemplate.services).length === 1) {
|
||||
name = foundTemplate.name || service.name.toLowerCase()
|
||||
} else {
|
||||
if (key === '$$id') {
|
||||
name = foundTemplate.name || key.replaceAll('$$id-', '') || service.name.toLowerCase()
|
||||
} else {
|
||||
name = key.replaceAll('$$id-', '') || service.name.toLowerCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
parsedTemplate[realKey] = {
|
||||
value,
|
||||
name,
|
||||
documentation: value.documentation || foundTemplate.documentation || 'https://docs.coollabs.io',
|
||||
image: value.image,
|
||||
files: value?.files,
|
||||
environment: [],
|
||||
fqdns: [],
|
||||
hostPorts: [],
|
||||
proxy: {}
|
||||
}
|
||||
if (value.environment?.length > 0) {
|
||||
for (const env of value.environment) {
|
||||
let [envKey, ...envValue] = env.split('=')
|
||||
envValue = envValue.join("=")
|
||||
let variable = null
|
||||
if (foundTemplate?.variables) {
|
||||
variable = foundTemplate?.variables.find(v => v.name === envKey) || foundTemplate?.variables.find(v => v.id === envValue)
|
||||
}
|
||||
if (variable) {
|
||||
const id = variable.id.replaceAll('$$', '')
|
||||
const label = variable?.label
|
||||
const description = variable?.description
|
||||
const defaultValue = variable?.defaultValue
|
||||
const main = variable?.main || '$$id'
|
||||
const type = variable?.type || 'input'
|
||||
const placeholder = variable?.placeholder || ''
|
||||
const readOnly = variable?.readOnly || false
|
||||
const required = variable?.required || false
|
||||
if (envValue.startsWith('$$config') || variable?.showOnConfiguration) {
|
||||
if (envValue.startsWith('$$config_coolify')) {
|
||||
continue
|
||||
}
|
||||
parsedTemplate[realKey].environment.push(
|
||||
{ id, name: envKey, value: envValue, main, label, description, defaultValue, type, placeholder, required, readOnly }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (value?.proxy && value.proxy.length > 0) {
|
||||
for (const proxyValue of value.proxy) {
|
||||
if (proxyValue.domain) {
|
||||
const variable = foundTemplate?.variables.find(v => v.id === proxyValue.domain)
|
||||
if (variable) {
|
||||
const { id, name, label, description, defaultValue, required = false } = variable
|
||||
const found = await prisma.serviceSetting.findFirst({ where: { serviceId: service.id, variableName: proxyValue.domain } })
|
||||
parsedTemplate[realKey].fqdns.push(
|
||||
{ id, name, value: found?.value || '', label, description, defaultValue, required }
|
||||
)
|
||||
}
|
||||
}
|
||||
if (proxyValue.hostPort) {
|
||||
const variable = foundTemplate?.variables.find(v => v.id === proxyValue.hostPort)
|
||||
if (variable) {
|
||||
const { id, name, label, description, defaultValue, required = false } = variable
|
||||
const found = await prisma.serviceSetting.findFirst({ where: { serviceId: service.id, variableName: proxyValue.hostPort } })
|
||||
parsedTemplate[realKey].hostPorts.push(
|
||||
{ id, name, value: found?.value || '', label, description, defaultValue, required }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parsedTemplate = foundTemplate
|
||||
}
|
||||
let strParsedTemplate = JSON.stringify(parsedTemplate)
|
||||
|
||||
// replace $$id and $$workdir
|
||||
strParsedTemplate = strParsedTemplate.replaceAll('$$id', service.id)
|
||||
strParsedTemplate = strParsedTemplate.replaceAll('$$core_version', service.version || foundTemplate.defaultVersion)
|
||||
|
||||
// replace $$workdir
|
||||
if (workdir) {
|
||||
strParsedTemplate = strParsedTemplate.replaceAll('$$workdir', workdir)
|
||||
}
|
||||
|
||||
// replace $$config
|
||||
if (service.serviceSetting.length > 0) {
|
||||
for (const setting of service.serviceSetting) {
|
||||
const { value, variableName } = setting
|
||||
const regex = new RegExp(`\\$\\$config_${variableName.replace('$$config_', '')}\"`, 'gi')
|
||||
if (value === '$$generate_fqdn') {
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '"' || '' + '"')
|
||||
} else if (value === '$$generate_fqdn_slash') {
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '/' + '"')
|
||||
} else if (value === '$$generate_domain') {
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, getDomain(service.fqdn) + '"')
|
||||
} else if (service.destinationDocker?.network && value === '$$generate_network') {
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, service.destinationDocker.network + '"')
|
||||
} else {
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, value + '"')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replace $$secret
|
||||
if (service.serviceSecret.length > 0) {
|
||||
for (const secret of service.serviceSecret) {
|
||||
let { name, value } = secret
|
||||
name = name.toLowerCase()
|
||||
const regexHashed = new RegExp(`\\$\\$hashed\\$\\$secret_${name}\"`, 'gi')
|
||||
const regex = new RegExp(`\\$\\$secret_${name}\"`, 'gi')
|
||||
if (value) {
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, bcrypt.hashSync(value.replaceAll("\"", "\\\""), 10) + '"')
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, value.replaceAll("\"", "\\\"") + '"')
|
||||
} else {
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, '' + '"')
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, '' + '"')
|
||||
}
|
||||
}
|
||||
}
|
||||
parsedTemplate = JSON.parse(strParsedTemplate)
|
||||
}
|
||||
return parsedTemplate
|
||||
}
|
||||
|
||||
export async function getService(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
@@ -100,9 +279,17 @@ export async function getService(request: FastifyRequest<OnlyId>) {
|
||||
if (!service) {
|
||||
throw { status: 404, message: 'Service not found.' }
|
||||
}
|
||||
let template = {}
|
||||
let tags = []
|
||||
if (service.type) {
|
||||
template = await parseAndFindServiceTemplates(service)
|
||||
tags = await getTags(service.type)
|
||||
}
|
||||
return {
|
||||
settings: await listSettings(),
|
||||
service
|
||||
service,
|
||||
template,
|
||||
tags
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -111,7 +298,7 @@ export async function getService(request: FastifyRequest<OnlyId>) {
|
||||
export async function getServiceType(request: FastifyRequest) {
|
||||
try {
|
||||
return {
|
||||
types: supportedServiceTypesAndVersions
|
||||
services: await getTemplates()
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -121,25 +308,83 @@ export async function saveServiceType(request: FastifyRequest<SaveServiceType>,
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { type } = request.body;
|
||||
await configureServiceType({ id, type });
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function getServiceVersions(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.params;
|
||||
const { type } = await getServiceFromDB({ id, teamId });
|
||||
return {
|
||||
type,
|
||||
versions: supportedServiceTypesAndVersions.find((name) => name.name === type).versions
|
||||
const templates = await getTemplates()
|
||||
let foundTemplate = templates.find(t => fixType(t.type) === fixType(type))
|
||||
if (foundTemplate) {
|
||||
foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id))
|
||||
if (foundTemplate.variables) {
|
||||
if (foundTemplate.variables.length > 0) {
|
||||
for (const variable of foundTemplate.variables) {
|
||||
const { defaultValue } = variable;
|
||||
const regex = /^\$\$.*\((\d+)\)$/g;
|
||||
const length = Number(regex.exec(defaultValue)?.[1]) || undefined
|
||||
if (variable.defaultValue.startsWith('$$generate_password')) {
|
||||
variable.value = generatePassword({ length });
|
||||
} else if (variable.defaultValue.startsWith('$$generate_hex')) {
|
||||
variable.value = generatePassword({ length, isHex: true });
|
||||
} else if (variable.defaultValue.startsWith('$$generate_username')) {
|
||||
variable.value = cuid();
|
||||
} else if (variable.defaultValue.startsWith('$$generate_token')) {
|
||||
variable.value = generateToken()
|
||||
} else {
|
||||
variable.value = variable.defaultValue || '';
|
||||
}
|
||||
const foundVariableSomewhereElse = foundTemplate.variables.find(v => v.defaultValue.includes(variable.id))
|
||||
if (foundVariableSomewhereElse) {
|
||||
foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll(variable.id, variable.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const variable of foundTemplate.variables) {
|
||||
if (variable.id.startsWith('$$secret_')) {
|
||||
const found = await prisma.serviceSecret.findFirst({ where: { name: variable.name, serviceId: id } })
|
||||
if (!found) {
|
||||
await prisma.serviceSecret.create({
|
||||
data: { name: variable.name, value: encrypt(variable.value) || '', service: { connect: { id } } }
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
if (variable.id.startsWith('$$config_')) {
|
||||
const found = await prisma.serviceSetting.findFirst({ where: { name: variable.name, serviceId: id } })
|
||||
if (!found) {
|
||||
await prisma.serviceSetting.create({
|
||||
data: { name: variable.name, value: variable.value.toString(), variableName: variable.id, service: { connect: { id } } }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const service of Object.keys(foundTemplate.services)) {
|
||||
if (foundTemplate.services[service].volumes) {
|
||||
for (const volume of foundTemplate.services[service].volumes) {
|
||||
const [volumeName, path] = volume.split(':')
|
||||
if (!volumeName.startsWith('/')) {
|
||||
const found = await prisma.servicePersistentStorage.findFirst({ where: { volumeName, serviceId: id } })
|
||||
if (!found) {
|
||||
await prisma.servicePersistentStorage.create({
|
||||
data: { volumeName, path, containerId: service, predefined: true, service: { connect: { id } } }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await prisma.service.update({ where: { id }, data: { type, version: foundTemplate.defaultVersion, templateVersion: foundTemplate.templateVersion } })
|
||||
|
||||
if (type.startsWith('wordpress')) {
|
||||
await prisma.service.update({ where: { id }, data: { wordpress: { create: {} } } })
|
||||
}
|
||||
return reply.code(201).send()
|
||||
} else {
|
||||
throw { status: 404, message: 'Service type not found.' }
|
||||
}
|
||||
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveServiceVersion(request: FastifyRequest<SaveServiceVersion>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
@@ -186,7 +431,7 @@ export async function getServiceUsage(request: FastifyRequest<OnlyId>) {
|
||||
}
|
||||
export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { id, containerId } = request.params;
|
||||
let { since = 0 } = request.query
|
||||
if (since !== 0) {
|
||||
since = day(since).unix();
|
||||
@@ -197,10 +442,8 @@ export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
|
||||
});
|
||||
if (destinationDockerId) {
|
||||
try {
|
||||
// const found = await checkContainer({ dockerId, container: id })
|
||||
// if (found) {
|
||||
const { default: ansi } = await import('strip-ansi')
|
||||
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
|
||||
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${containerId}` })
|
||||
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||
const logs = stripLogsStderr.concat(stripLogsStdout)
|
||||
@@ -208,7 +451,10 @@ export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
|
||||
return { logs: sortedLogs }
|
||||
// }
|
||||
} catch (error) {
|
||||
const { statusCode } = error;
|
||||
const { statusCode, stderr } = error;
|
||||
if (stderr.startsWith('Error: No such container')) {
|
||||
return { logs: [], noContainer: true }
|
||||
}
|
||||
if (statusCode === 404) {
|
||||
return {
|
||||
logs: []
|
||||
@@ -258,26 +504,22 @@ export async function checkServiceDomain(request: FastifyRequest<CheckServiceDom
|
||||
export async function checkService(request: FastifyRequest<CheckService>) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
let { fqdn, exposePort, forceSave, otherFqdns, dualCerts } = request.body;
|
||||
let { fqdn, exposePort, forceSave, dualCerts, otherFqdn = false } = request.body;
|
||||
|
||||
const domainsList = await prisma.serviceSetting.findMany({ where: { variableName: { startsWith: '$$config_coolify_fqdn' } } })
|
||||
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase());
|
||||
if (exposePort) exposePort = Number(exposePort);
|
||||
|
||||
const { destinationDocker: { remoteIpAddress, remoteEngine, engine }, exposePort: configuredPort } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||
const { isDNSCheckEnabled } = await prisma.setting.findFirst({});
|
||||
|
||||
let found = await isDomainConfigured({ id, fqdn, remoteIpAddress });
|
||||
let found = await isDomainConfigured({ id, fqdn, remoteIpAddress, checkOwn: otherFqdn });
|
||||
if (found) {
|
||||
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
||||
}
|
||||
if (otherFqdns && otherFqdns.length > 0) {
|
||||
for (const ofqdn of otherFqdns) {
|
||||
found = await isDomainConfigured({ id, fqdn: ofqdn, remoteIpAddress });
|
||||
if (found) {
|
||||
throw { status: 500, message: `Domain ${getDomain(ofqdn).replace('www.', '')} is already in use!` }
|
||||
}
|
||||
}
|
||||
if (domainsList.find(d => getDomain(d.value) === getDomain(fqdn))) {
|
||||
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
||||
}
|
||||
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress })
|
||||
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||
@@ -293,20 +535,33 @@ export async function checkService(request: FastifyRequest<CheckService>) {
|
||||
export async function saveService(request: FastifyRequest<SaveService>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
let { name, fqdn, exposePort, type } = request.body;
|
||||
|
||||
let { name, fqdn, exposePort, type, serviceSetting, version } = request.body;
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (exposePort) exposePort = Number(exposePort);
|
||||
|
||||
type = fixType(type)
|
||||
const update = saveUpdateableFields(type, request.body[type])
|
||||
|
||||
const data = {
|
||||
fqdn,
|
||||
name,
|
||||
exposePort,
|
||||
version,
|
||||
}
|
||||
if (Object.keys(update).length > 0) {
|
||||
data[type] = { update: update }
|
||||
const templates = await getTemplates()
|
||||
const service = await prisma.service.findUnique({ where: { id } })
|
||||
const foundTemplate = templates.find(t => fixType(t.type) === fixType(service.type))
|
||||
for (const setting of serviceSetting) {
|
||||
let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting
|
||||
if (value) {
|
||||
if (changed) {
|
||||
await prisma.serviceSetting.update({ where: { id: settingId }, data: { value } })
|
||||
}
|
||||
if (isNew) {
|
||||
if (!variableName) {
|
||||
variableName = foundTemplate?.variables.find(v => v.name === name).id
|
||||
}
|
||||
await prisma.serviceSetting.create({ data: { name, value, variableName, service: { connect: { id } } } })
|
||||
}
|
||||
}
|
||||
}
|
||||
await prisma.service.update({
|
||||
where: { id }, data
|
||||
@@ -320,11 +575,19 @@ export async function saveService(request: FastifyRequest<SaveService>, reply: F
|
||||
export async function getServiceSecrets(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const teamId = request.user.teamId;
|
||||
const service = await getServiceFromDB({ id, teamId });
|
||||
let secrets = await prisma.serviceSecret.findMany({
|
||||
where: { serviceId: id },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
const templates = await getTemplates()
|
||||
const foundTemplate = templates.find(t => fixType(t.type) === service.type)
|
||||
secrets = secrets.map((secret) => {
|
||||
const foundVariable = foundTemplate?.variables.find(v => v.name === secret.name) || null
|
||||
if (foundVariable) {
|
||||
secret.readOnly = foundVariable.readOnly
|
||||
}
|
||||
secret.value = decrypt(secret.value);
|
||||
return secret;
|
||||
});
|
||||
@@ -341,7 +604,6 @@ export async function saveServiceSecret(request: FastifyRequest<SaveServiceSecre
|
||||
try {
|
||||
const { id } = request.params
|
||||
let { name, value, isNew } = request.body
|
||||
|
||||
if (isNew) {
|
||||
const found = await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
|
||||
if (found) {
|
||||
@@ -400,16 +662,21 @@ export async function getServiceStorages(request: FastifyRequest<OnlyId>) {
|
||||
export async function saveServiceStorage(request: FastifyRequest<SaveServiceStorage>, reply: FastifyReply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { path, newStorage, storageId } = request.body
|
||||
const { path, isNewStorage, storageId, containerId } = request.body
|
||||
|
||||
if (newStorage) {
|
||||
if (isNewStorage) {
|
||||
const volumeName = `${id}-custom${path.replace(/\//gi, '-')}`
|
||||
const found = await prisma.servicePersistentStorage.findFirst({ where: { path, containerId } });
|
||||
if (found) {
|
||||
throw { status: 500, message: 'Persistent storage already exists for this container and path.' }
|
||||
}
|
||||
await prisma.servicePersistentStorage.create({
|
||||
data: { path, service: { connect: { id } } }
|
||||
data: { path, volumeName, containerId, service: { connect: { id } } }
|
||||
});
|
||||
} else {
|
||||
await prisma.servicePersistentStorage.update({
|
||||
where: { id: storageId },
|
||||
data: { path }
|
||||
data: { path, containerId }
|
||||
});
|
||||
}
|
||||
return reply.code(201).send()
|
||||
@@ -420,9 +687,8 @@ export async function saveServiceStorage(request: FastifyRequest<SaveServiceStor
|
||||
|
||||
export async function deleteServiceStorage(request: FastifyRequest<DeleteServiceStorage>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { path } = request.body
|
||||
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id, path } });
|
||||
const { storageId } = request.body
|
||||
await prisma.servicePersistentStorage.deleteMany({ where: { id: storageId } });
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -478,14 +744,17 @@ export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, re
|
||||
const {
|
||||
destinationDockerId,
|
||||
destinationDocker,
|
||||
plausibleAnalytics: { postgresqlUser, postgresqlPassword, postgresqlDatabase }
|
||||
serviceSecret
|
||||
} = await getServiceFromDB({ id, teamId });
|
||||
if (destinationDockerId) {
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker exec ${id}-postgresql psql -H postgresql://${postgresqlUser}:${postgresqlPassword}@localhost:5432/${postgresqlDatabase} -c "UPDATE users SET email_verified = true;"`
|
||||
})
|
||||
return await reply.code(201).send()
|
||||
const databaseUrl = serviceSecret.find((secret) => secret.name === 'DATABASE_URL');
|
||||
if (databaseUrl) {
|
||||
await executeDockerCmd({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker exec ${id}-postgresql psql -H ${databaseUrl.value} -c "UPDATE users SET email_verified = true;"`
|
||||
})
|
||||
return await reply.code(201).send()
|
||||
}
|
||||
}
|
||||
throw { status: 500, message: 'Could not activate users.' }
|
||||
} catch ({ status, message }) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
getServiceStorages,
|
||||
getServiceType,
|
||||
getServiceUsage,
|
||||
getServiceVersions,
|
||||
listServices,
|
||||
newService,
|
||||
saveService,
|
||||
@@ -64,16 +63,15 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get('/:id/configuration/type', async (request) => await getServiceType(request));
|
||||
fastify.post<SaveServiceType>('/:id/configuration/type', async (request, reply) => await saveServiceType(request, reply));
|
||||
|
||||
fastify.get<OnlyId>('/:id/configuration/version', async (request) => await getServiceVersions(request));
|
||||
fastify.post<SaveServiceVersion>('/:id/configuration/version', async (request, reply) => await saveServiceVersion(request, reply));
|
||||
|
||||
fastify.post<SaveServiceDestination>('/:id/configuration/destination', async (request, reply) => await saveServiceDestination(request, reply));
|
||||
|
||||
fastify.get<OnlyId>('/:id/usage', async (request) => await getServiceUsage(request));
|
||||
fastify.get<GetServiceLogs>('/:id/logs', async (request) => await getServiceLogs(request));
|
||||
fastify.get<GetServiceLogs>('/:id/logs/:containerId', async (request) => await getServiceLogs(request));
|
||||
|
||||
fastify.post<ServiceStartStop>('/:id/:type/start', async (request) => await startService(request));
|
||||
fastify.post<ServiceStartStop>('/:id/:type/stop', async (request) => await stopService(request));
|
||||
fastify.post<ServiceStartStop>('/:id/start', async (request) => await startService(request, fastify));
|
||||
fastify.post<ServiceStartStop>('/:id/stop', async (request) => await stopService(request));
|
||||
fastify.post<ServiceStartStop & SetWordpressSettings & SetGlitchTipSettings>('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply));
|
||||
|
||||
fastify.post<OnlyId>('/:id/plausibleanalytics/activate', async (request, reply) => await activatePlausibleUsers(request, reply));
|
||||
|
||||
@@ -15,9 +15,13 @@ export interface SaveServiceDestination extends OnlyId {
|
||||
destinationId: string
|
||||
}
|
||||
}
|
||||
export interface GetServiceLogs extends OnlyId {
|
||||
export interface GetServiceLogs{
|
||||
Params: {
|
||||
id: string,
|
||||
containerId: string
|
||||
},
|
||||
Querystring: {
|
||||
since: number
|
||||
since: number,
|
||||
}
|
||||
}
|
||||
export interface SaveServiceSettings extends OnlyId {
|
||||
@@ -36,7 +40,7 @@ export interface CheckService extends OnlyId {
|
||||
forceSave: boolean,
|
||||
dualCerts: boolean,
|
||||
exposePort: number,
|
||||
otherFqdns: Array<string>
|
||||
otherFqdn: boolean
|
||||
}
|
||||
}
|
||||
export interface SaveService extends OnlyId {
|
||||
@@ -44,6 +48,8 @@ export interface SaveService extends OnlyId {
|
||||
name: string,
|
||||
fqdn: string,
|
||||
exposePort: number,
|
||||
version: string,
|
||||
serviceSetting: any
|
||||
type: string
|
||||
}
|
||||
}
|
||||
@@ -62,14 +68,15 @@ export interface DeleteServiceSecret extends OnlyId {
|
||||
export interface SaveServiceStorage extends OnlyId {
|
||||
Body: {
|
||||
path: string,
|
||||
newStorage: string,
|
||||
containerId: string,
|
||||
storageId: string,
|
||||
isNewStorage: boolean,
|
||||
}
|
||||
}
|
||||
|
||||
export interface DeleteServiceStorage extends OnlyId {
|
||||
Body: {
|
||||
path: string,
|
||||
storageId: string,
|
||||
}
|
||||
}
|
||||
export interface ServiceStartStop {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { promises as dns } from 'dns';
|
||||
import { X509Certificate } from 'node:crypto';
|
||||
|
||||
import * as Sentry from '@sentry/node';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { asyncExecShell, checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, isDNSValid, isDomainConfigured, listSettings, prisma } from '../../../../lib/common';
|
||||
import { CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey } from './types';
|
||||
import { asyncExecShell, checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, isDev, isDNSValid, isDomainConfigured, listSettings, prisma, sentryDSN, version } from '../../../../lib/common';
|
||||
import { AddDefaultRegistry, CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey, SetDefaultRegistry } from './types';
|
||||
|
||||
|
||||
export async function listAllSettings(request: FastifyRequest) {
|
||||
@@ -11,6 +11,20 @@ export async function listAllSettings(request: FastifyRequest) {
|
||||
const teamId = request.user.teamId;
|
||||
const settings = await listSettings();
|
||||
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } })
|
||||
let publicRegistries = await prisma.dockerRegistry.findMany({ where: { isSystemWide: true } })
|
||||
let privateRegistries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId }, isSystemWide: false } })
|
||||
publicRegistries = publicRegistries.map((registry) => {
|
||||
if (registry.password) {
|
||||
registry.password = decrypt(registry.password)
|
||||
}
|
||||
return registry
|
||||
})
|
||||
privateRegistries = privateRegistries.map((registry) => {
|
||||
if (registry.password) {
|
||||
registry.password = decrypt(registry.password)
|
||||
}
|
||||
return registry
|
||||
})
|
||||
const unencryptedKeys = []
|
||||
if (sshKeys.length > 0) {
|
||||
for (const key of sshKeys) {
|
||||
@@ -27,7 +41,11 @@ export async function listAllSettings(request: FastifyRequest) {
|
||||
return {
|
||||
settings,
|
||||
certificates: cns,
|
||||
sshKeys: unencryptedKeys
|
||||
sshKeys: unencryptedKeys,
|
||||
registries: {
|
||||
public: publicRegistries,
|
||||
private: privateRegistries
|
||||
}
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -36,6 +54,7 @@ export async function listAllSettings(request: FastifyRequest) {
|
||||
export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) {
|
||||
try {
|
||||
const {
|
||||
doNotTrack,
|
||||
fqdn,
|
||||
isAPIDebuggingEnabled,
|
||||
isRegistrationEnabled,
|
||||
@@ -44,19 +63,29 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
||||
maxPort,
|
||||
isAutoUpdateEnabled,
|
||||
isDNSCheckEnabled,
|
||||
DNSServers
|
||||
DNSServers,
|
||||
proxyDefaultRedirect
|
||||
} = request.body
|
||||
const { id } = await listSettings();
|
||||
await prisma.setting.update({
|
||||
where: { id },
|
||||
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers, isAPIDebuggingEnabled }
|
||||
data: { doNotTrack, isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers, isAPIDebuggingEnabled, }
|
||||
});
|
||||
if (fqdn) {
|
||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||
}
|
||||
await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } });
|
||||
if (minPort && maxPort) {
|
||||
await prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
|
||||
}
|
||||
if (doNotTrack === false) {
|
||||
Sentry.init({
|
||||
dsn: sentryDSN,
|
||||
environment: isDev ? 'development' : 'production',
|
||||
release: version
|
||||
});
|
||||
console.log('Sentry initialized')
|
||||
}
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -89,9 +118,9 @@ export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
const found = await isDomainConfigured({ id, fqdn });
|
||||
if (found) {
|
||||
throw "Domain already configured";
|
||||
throw { message: "Domain already configured" };
|
||||
}
|
||||
if (isDNSCheckEnabled && !forceSave) {
|
||||
if (isDNSCheckEnabled && !forceSave && !isDev) {
|
||||
const hostname = request.hostname.split(':')[0]
|
||||
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||
}
|
||||
@@ -129,8 +158,9 @@ export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: Fas
|
||||
}
|
||||
export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.body;
|
||||
await prisma.sshKey.delete({ where: { id } })
|
||||
await prisma.sshKey.deleteMany({ where: { id, teamId } })
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
@@ -139,9 +169,54 @@ export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply:
|
||||
|
||||
export async function deleteCertificates(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.body;
|
||||
await asyncExecShell(`docker exec coolify-proxy sh -c 'rm -f /etc/traefik/acme/custom/${id}-key.pem /etc/traefik/acme/custom/${id}-cert.pem'`)
|
||||
await prisma.certificate.delete({ where: { id } })
|
||||
await prisma.certificate.deleteMany({ where: { id, teamId } })
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
|
||||
export async function setDockerRegistry(request: FastifyRequest<SetDefaultRegistry>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id, username, password } = request.body;
|
||||
|
||||
let encryptedPassword = ''
|
||||
if (password) encryptedPassword = encrypt(password)
|
||||
|
||||
if (teamId === '0') {
|
||||
await prisma.dockerRegistry.update({ where: { id }, data: { username, password: encryptedPassword } })
|
||||
} else {
|
||||
await prisma.dockerRegistry.updateMany({ where: { id, teamId }, data: { username, password: encryptedPassword } })
|
||||
}
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function addDockerRegistry(request: FastifyRequest<AddDefaultRegistry>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { name, url, username, password, isSystemWide } = request.body;
|
||||
|
||||
let encryptedPassword = ''
|
||||
if (password) encryptedPassword = encrypt(password)
|
||||
await prisma.dockerRegistry.create({ data: { name, url, username, password: encryptedPassword, isSystemWide, team: { connect: { id: teamId } } } })
|
||||
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
export async function deleteDockerRegistry(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.body;
|
||||
await prisma.application.updateMany({ where: { dockerRegistryId: id }, data: { dockerRegistryId: '0' } })
|
||||
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } })
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
|
||||
@@ -2,8 +2,8 @@ import { FastifyPluginAsync } from 'fastify';
|
||||
import { X509Certificate } from 'node:crypto';
|
||||
|
||||
import { encrypt, errorHandler, prisma } from '../../../../lib/common';
|
||||
import { checkDNS, checkDomain, deleteCertificates, deleteDomain, deleteSSHKey, listAllSettings, saveSettings, saveSSHKey } from './handlers';
|
||||
import { CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey } from './types';
|
||||
import { addDockerRegistry, checkDNS, checkDomain, deleteCertificates, deleteDockerRegistry, deleteDomain, deleteSSHKey, listAllSettings, saveSettings, saveSSHKey, setDockerRegistry } from './handlers';
|
||||
import { AddDefaultRegistry, CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey, SetDefaultRegistry } from './types';
|
||||
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
@@ -20,6 +20,11 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.post<SaveSSHKey>('/sshKey', async (request, reply) => await saveSSHKey(request, reply));
|
||||
fastify.delete<OnlyIdInBody>('/sshKey', async (request, reply) => await deleteSSHKey(request, reply));
|
||||
|
||||
fastify.post<SetDefaultRegistry>('/registry', async (request, reply) => await setDockerRegistry(request, reply));
|
||||
fastify.post<AddDefaultRegistry>('/registry/new', async (request, reply) => await addDockerRegistry(request, reply));
|
||||
fastify.delete<OnlyIdInBody>('/registry', async (request, reply) => await deleteDockerRegistry(request, reply));
|
||||
// fastify.delete<>('/registry', async (request, reply) => await deleteSSHKey(request, reply));
|
||||
|
||||
fastify.post('/upload', async (request) => {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
@@ -53,7 +58,6 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
|
||||
});
|
||||
fastify.delete<OnlyIdInBody>('/certificate', async (request, reply) => await deleteCertificates(request, reply))
|
||||
// fastify.get('/certificates', async (request) => await getCertificates(request))
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { OnlyId } from "../../../../types"
|
||||
|
||||
export interface SaveSettings {
|
||||
Body: {
|
||||
doNotTrack: boolean,
|
||||
fqdn: string,
|
||||
isAPIDebuggingEnabled: boolean,
|
||||
isRegistrationEnabled: boolean,
|
||||
@@ -10,7 +11,8 @@ export interface SaveSettings {
|
||||
maxPort: number,
|
||||
isAutoUpdateEnabled: boolean,
|
||||
isDNSCheckEnabled: boolean,
|
||||
DNSServers: string
|
||||
DNSServers: string,
|
||||
proxyDefaultRedirect: string
|
||||
}
|
||||
}
|
||||
export interface DeleteDomain {
|
||||
@@ -20,30 +22,47 @@ export interface DeleteDomain {
|
||||
}
|
||||
export interface CheckDomain extends OnlyId {
|
||||
Body: {
|
||||
fqdn: string,
|
||||
forceSave: boolean,
|
||||
dualCerts: boolean,
|
||||
isDNSCheckEnabled: boolean,
|
||||
fqdn: string,
|
||||
forceSave: boolean,
|
||||
dualCerts: boolean,
|
||||
isDNSCheckEnabled: boolean,
|
||||
}
|
||||
}
|
||||
export interface CheckDNS {
|
||||
Params: {
|
||||
domain: string,
|
||||
domain: string,
|
||||
}
|
||||
}
|
||||
export interface SaveSSHKey {
|
||||
Body: {
|
||||
privateKey: string,
|
||||
privateKey: string,
|
||||
name: string
|
||||
}
|
||||
}
|
||||
export interface DeleteSSHKey {
|
||||
Body: {
|
||||
id: string
|
||||
id: string
|
||||
}
|
||||
}
|
||||
export interface OnlyIdInBody {
|
||||
Body: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface SetDefaultRegistry {
|
||||
Body: {
|
||||
id: string
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
}
|
||||
export interface AddDefaultRegistry {
|
||||
Body: {
|
||||
url: string
|
||||
name: string
|
||||
username: string
|
||||
password: string
|
||||
isSystemWide: boolean
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,7 @@ export async function getSource(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { teamId } = request.user
|
||||
|
||||
const settings = await prisma.setting.findFirst({});
|
||||
if (settings.proxyPassword) settings.proxyPassword = decrypt(settings.proxyPassword);
|
||||
|
||||
if (id === 'new') {
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import axios from "axios";
|
||||
import cuid from "cuid";
|
||||
import crypto from "crypto";
|
||||
import { encrypt, errorHandler, getDomain, getUIUrl, isDev, prisma } from "../../../lib/common";
|
||||
@@ -32,13 +31,14 @@ export async function installGithub(request: FastifyRequest<InstallGithub>, repl
|
||||
}
|
||||
export async function configureGitHubApp(request, reply) {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const { code, state } = request.query;
|
||||
const { apiUrl } = await prisma.gitSource.findFirst({
|
||||
where: { id: state },
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
|
||||
const { data }: any = await axios.post(`${apiUrl}/app-manifests/${code}/conversions`);
|
||||
const data: any = await got.post(`${apiUrl}/app-manifests/${code}/conversions`).json()
|
||||
const { id, client_id, slug, client_secret, pem, webhook_secret } = data
|
||||
|
||||
const encryptedClientSecret = encrypt(client_secret);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import axios from "axios";
|
||||
import cuid from "cuid";
|
||||
import crypto from "crypto";
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
@@ -10,6 +9,7 @@ import type { ConfigureGitLabApp, GitLabEvents } from "./types";
|
||||
|
||||
export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLabApp>, reply: FastifyReply) {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const { code, state } = request.query;
|
||||
const { fqdn } = await listSettings();
|
||||
const { gitSource: { gitlabApp: { appId, appSecret }, htmlUrl } }: any = await getApplicationFromDB(state, undefined);
|
||||
@@ -19,19 +19,21 @@ export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLab
|
||||
if (isDev) {
|
||||
domain = getAPIUrl();
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
client_id: appId,
|
||||
client_secret: appSecret,
|
||||
code,
|
||||
state,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: `${domain}/webhooks/gitlab`
|
||||
});
|
||||
const { data } = await axios.post(`${htmlUrl}/oauth/token`, params)
|
||||
|
||||
const { access_token } = await got.post(`${htmlUrl}/oauth/token`, {
|
||||
searchParams: {
|
||||
client_id: appId,
|
||||
client_secret: appSecret,
|
||||
code,
|
||||
state,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: `${domain}/webhooks/gitlab`
|
||||
}
|
||||
}).json()
|
||||
if (isDev) {
|
||||
return reply.redirect(`${getUIUrl()}/webhooks/success?token=${data.access_token}`)
|
||||
return reply.redirect(`${getUIUrl()}/webhooks/success?token=${access_token}`)
|
||||
}
|
||||
return reply.redirect(`/webhooks/success?token=${data.access_token}`)
|
||||
return reply.redirect(`/webhooks/success?token=${access_token}`)
|
||||
} catch ({ status, message, ...other }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,12 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { OnlyId } from '../../../types';
|
||||
import { remoteTraefikConfiguration, traefikConfiguration, traefikOtherConfiguration } from './handlers';
|
||||
import { TraefikOtherConfiguration } from './types';
|
||||
import { proxyConfiguration, otherProxyConfiguration } from './handlers';
|
||||
import { OtherProxyConfiguration } from './types';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply));
|
||||
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
|
||||
|
||||
fastify.get<OnlyId>('/remote/:id', async (request) => remoteTraefikConfiguration(request));
|
||||
fastify.get<OnlyId>('/main.json', async (request, reply) => proxyConfiguration(request, false));
|
||||
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
||||
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) => otherProxyConfiguration(request));
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface TraefikOtherConfiguration {
|
||||
export interface OtherProxyConfiguration {
|
||||
Querystring: {
|
||||
id: string,
|
||||
privatePort: number,
|
||||
|
||||
Reference in New Issue
Block a user