Merge branch 'next' into feature/glitchtip-service

This commit is contained in:
Andras Bacsai
2022-08-18 21:47:06 +02:00
committed by GitHub
39 changed files with 1031 additions and 612 deletions

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Setting" ADD COLUMN "DNSServers" TEXT;

View File

@@ -0,0 +1,42 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_GitSource" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"forPublic" BOOLEAN NOT NULL DEFAULT false,
"type" TEXT,
"apiUrl" TEXT,
"htmlUrl" TEXT,
"customPort" INTEGER NOT NULL DEFAULT 22,
"organization" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"githubAppId" TEXT,
"gitlabAppId" TEXT,
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt" FROM "GitSource";
DROP TABLE "GitSource";
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
CREATE TABLE "new_ApplicationSettings" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT NOT NULL,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"debug" BOOLEAN NOT NULL DEFAULT false,
"previews" BOOLEAN NOT NULL DEFAULT false,
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
"isBot" BOOLEAN NOT NULL DEFAULT false,
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "previews", "updatedAt" FROM "ApplicationSettings";
DROP TABLE "ApplicationSettings";
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -20,6 +20,7 @@ model Setting {
proxyHash String?
isAutoUpdateEnabled Boolean @default(false)
isDNSCheckEnabled Boolean @default(true)
DNSServers String?
isTraefikUsed Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -118,16 +119,17 @@ model Application {
}
model ApplicationSettings {
id String @id @default(cuid())
applicationId String @unique
dualCerts Boolean @default(false)
debug Boolean @default(false)
previews Boolean @default(false)
autodeploy Boolean @default(true)
isBot Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])
id String @id @default(cuid())
applicationId String @unique
dualCerts Boolean @default(false)
debug Boolean @default(false)
previews Boolean @default(false)
autodeploy Boolean @default(true)
isBot Boolean @default(false)
isPublicRepository Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])
}
model ApplicationPersistentStorage {
@@ -237,6 +239,7 @@ model SshKey {
model GitSource {
id String @id @default(cuid())
name String
forPublic Boolean @default(false)
type String?
apiUrl String?
htmlUrl String?

View File

@@ -66,6 +66,34 @@ async function main() {
}
});
}
const github = await prisma.gitSource.findFirst({
where: { htmlUrl: 'https://github.com', forPublic: true }
});
const gitlab = await prisma.gitSource.findFirst({
where: { htmlUrl: 'https://gitlab.com', forPublic: true }
});
if (!github) {
await prisma.gitSource.create({
data: {
apiUrl: 'https://api.github.com',
htmlUrl: 'https://github.com',
forPublic: true,
name: 'Github Public',
type: 'github'
}
});
}
if (!gitlab) {
await prisma.gitSource.create({
data: {
apiUrl: 'https://gitlab.com/api/v4',
htmlUrl: 'https://gitlab.com',
forPublic: true,
name: 'Gitlab Public',
type: 'gitlab'
}
});
}
}
main()
.catch((e) => {

View File

@@ -5,8 +5,10 @@ import env from '@fastify/env';
import cookie from '@fastify/cookie';
import path, { join } from 'path';
import autoLoad from '@fastify/autoload';
import { asyncExecShell, isDev, listSettings, prisma } from './lib/common';
import { asyncExecShell, isDev, listSettings, prisma, version } from './lib/common';
import { scheduler } from './lib/scheduler';
import axios from 'axios';
import compareVersions from 'compare-versions';
declare module 'fastify' {
interface FastifyInstance {
@@ -113,8 +115,22 @@ fastify.listen({ port, host }, async (err: any, address: any) => {
setInterval(async () => {
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
if (isAutoUpdateEnabled) {
if (scheduler.workers.has('deployApplication')) {
scheduler.workers.get('deployApplication').postMessage("status:autoUpdater");
const currentVersion = version;
const { data: versions } = await axios
.get(
`https://get.coollabs.io/versions.json`
, {
params: {
appId: process.env['COOLIFY_APP_ID'] || undefined,
version: currentVersion
}
})
const latestVersion = versions['coolify'].main.version;
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
if (isUpdateAvailable === 1) {
if (scheduler.workers.has('deployApplication')) {
scheduler.workers.get('deployApplication').postMessage("status:autoUpdater");
}
}
}
}, isDev ? 5000 : 60000 * 15)

View File

@@ -4,7 +4,7 @@ import fs from 'fs/promises';
import yaml from 'js-yaml';
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
import { createDirectories, decrypt, executeDockerCmd, getDomain, prisma } from '../lib/common';
import { createDirectories, decrypt, defaultComposeConfiguration, executeDockerCmd, getDomain, prisma } from '../lib/common';
import * as importers from '../lib/importers';
import * as buildpacks from '../lib/buildPacks';
@@ -56,6 +56,7 @@ import * as buildpacks from '../lib/buildPacks';
baseImage,
baseBuildImage,
deploymentType,
forceRebuild
} = message
let {
branch,
@@ -69,6 +70,30 @@ import * as buildpacks from '../lib/buildPacks';
dockerFileLocation,
denoMainFile
} = message
const currentHash = crypto
.createHash('sha256')
.update(
JSON.stringify({
pythonWSGI,
pythonModule,
pythonVariable,
deploymentType,
denoOptions,
baseImage,
baseBuildImage,
buildPack,
port,
exposePort,
installCommand,
buildCommand,
startCommand,
secrets,
branch,
repository,
fqdn
})
)
.digest('hex');
try {
const { debug } = settings;
if (concurrency === 1) {
@@ -131,7 +156,8 @@ import * as buildpacks from '../lib/buildPacks';
htmlUrl: gitSource.htmlUrl,
projectId,
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null,
forPublic: gitSource.forPublic
});
if (!commit) {
throw new Error('No commit found?');
@@ -146,38 +172,10 @@ import * as buildpacks from '../lib/buildPacks';
} catch (err) {
console.log(err);
}
if (!pullmergeRequestId) {
const currentHash = crypto
//@ts-ignore
.createHash('sha256')
.update(
JSON.stringify({
pythonWSGI,
pythonModule,
pythonVariable,
deploymentType,
denoOptions,
baseImage,
baseBuildImage,
buildPack,
port,
exposePort,
installCommand,
buildCommand,
startCommand,
secrets,
branch,
repository,
fqdn
})
)
.digest('hex');
if (configHash !== currentHash) {
await prisma.application.update({
where: { id: applicationId },
data: { configHash: currentHash }
});
deployNeeded = true;
if (configHash) {
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
@@ -200,8 +198,10 @@ import * as buildpacks from '../lib/buildPacks';
//
}
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
if (forceRebuild) deployNeeded = true
if (!imageFound || deployNeeded) {
// if (true) {
// if (true) {
if (buildpacks[buildPack])
await buildpacks[buildPack]({
dockerId: destinationDocker.id,
@@ -250,16 +250,18 @@ import * as buildpacks from '../lib/buildPacks';
} catch (error) {
//
}
const envs = [];
const envs = [
`PORT=${port}`
];
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
envs.push(`${secret.name}='${secret.value}'`);
envs.push(`${secret.name}=${secret.value}`);
}
} else {
if (!secret.isPRMRSecret) {
envs.push(`${secret.name}='${secret.value}'`);
envs.push(`${secret.name}=${secret.value}`);
}
}
});
@@ -306,23 +308,14 @@ import * as buildpacks from '../lib/buildPacks';
container_name: imageId,
volumes,
env_file: envFound ? [`${workdir}/.env`] : [],
networks: [destinationDocker.network],
labels,
depends_on: [],
restart: 'always',
expose: [port],
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
// logging: {
// driver: 'fluentd',
// },
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
...defaultComposeConfiguration(destinationDocker.network),
}
},
networks: {
@@ -345,6 +338,10 @@ import * as buildpacks from '../lib/buildPacks';
}
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
await prisma.build.update({ where: { id: message.build_id }, data: { status: 'success' } });
if (!pullmergeRequestId) await prisma.application.update({
where: { id: applicationId },
data: { configHash: currentHash }
});
}
}

View File

@@ -17,7 +17,7 @@ import { checkContainer, removeContainer } from './docker';
import { day } from './dayjs';
import * as serviceFields from './serviceFields'
export const version = '3.5.0';
export const version = '3.6.0';
export const isDev = process.env.NODE_ENV === 'development';
const algorithm = 'aes-256-ctr';
@@ -319,6 +319,10 @@ export async function checkDoubleBranch(branch: string, projectId: number): Prom
}
export async function isDNSValid(hostname: any, domain: string): Promise<any> {
const { isIP } = await import('is-ip');
const { DNSServers } = await listSettings();
if (DNSServers) {
dns.setServers([DNSServers]);
}
let resolves = [];
try {
if (isIP(hostname)) {
@@ -332,7 +336,6 @@ export async function isDNSValid(hostname: any, domain: string): Promise<any> {
try {
let ipDomainFound = false;
dns.setServers(['1.1.1.1', '8.8.8.8']);
const dnsResolve = await dns.resolve4(domain);
if (dnsResolve.length > 0) {
for (const ip of dnsResolve) {
@@ -424,7 +427,12 @@ export async function checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts }): P
const { isIP } = await import('is-ip');
const domain = getDomain(fqdn);
const domainDualCert = domain.includes('www.') ? domain.replace('www.', '') : `www.${domain}`;
dns.setServers(['1.1.1.1', '8.8.8.8']);
const { DNSServers } = await listSettings();
if (DNSServers) {
dns.setServers([DNSServers]);
}
let resolves = [];
try {
if (isIP(hostname)) {
@@ -1180,6 +1188,25 @@ export async function updatePasswordInDb(database, user, newPassword, isRoot) {
}
}
}
export async function checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }: { id: string, configuredPort?: number, exposePort: number, dockerId: string, remoteIpAddress?: string }) {
if (exposePort < 1024 || exposePort > 65535) {
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
}
if (configuredPort) {
if (configuredPort !== exposePort) {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` }
}
}
} else {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` }
}
}
}
export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress) {
const { default: getPort } = await import('get-port');
const applicationUsed = await (
@@ -1565,7 +1592,7 @@ export async function configureServiceType({
});
} else if (type === 'appwrite') {
const opensslKeyV1 = encrypt(generatePassword());
const executorSecret = encrypt(generatePassword());
const executorSecret = encrypt(generatePassword());
const redisPassword = encrypt(generatePassword());
const mariadbHost = `${id}-mariadb`
const mariadbUser = cuid();
@@ -1845,3 +1872,17 @@ export function persistentVolumes(id, persistentStorage, config) {
) || {}
return { volumes, volumeMounts }
}
export function defaultComposeConfiguration(network: string): any {
return {
networks: [network],
restart: 'on-failure',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 10,
window: '120s'
}
}
}
}

View File

@@ -71,7 +71,6 @@ export async function removeContainer({
}): Promise<void> {
try {
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
console.log(id)
if (JSON.parse(stdout).Running) {
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })

View File

@@ -12,7 +12,8 @@ export default async function ({
htmlUrl,
branch,
buildId,
customPort
customPort,
forPublic
}: {
applicationId: string;
workdir: string;
@@ -23,41 +24,55 @@ export default async function ({
branch: string;
buildId: string;
customPort: number;
forPublic?: boolean;
}): Promise<string> {
const { default: got } = await import('got')
const url = htmlUrl.replace('https://', '').replace('http://', '');
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
if (forPublic) {
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch.`,
buildId,
applicationId
});
await asyncExecShell(
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
);
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
const { privateKey, appId, installationId } = body
} else {
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
const { privateKey, appId, installationId } = body
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
const payload = {
iat: Math.round(new Date().getTime() / 1000),
exp: Math.round(new Date().getTime() / 1000 + 60),
iss: appId
};
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
algorithm: 'RS256'
});
const { token } = await got
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
headers: {
Authorization: `Bearer ${jwtToken}`,
Accept: 'application/vnd.github.machine-man-preview+json'
}
})
.json();
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch.`,
buildId,
applicationId
});
await asyncExecShell(
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
);
const payload = {
iat: Math.round(new Date().getTime() / 1000),
exp: Math.round(new Date().getTime() / 1000 + 60),
iss: appId
};
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
algorithm: 'RS256'
});
const { token } = await got
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
headers: {
Authorization: `Bearer ${jwtToken}`,
Accept: 'application/vnd.github.machine-man-preview+json'
}
})
.json();
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch.`,
buildId,
applicationId
});
await asyncExecShell(
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
);
}
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
return commit.replace('\n', '');
}

View File

@@ -20,7 +20,6 @@ const options: any = {
}
if (message.caller === 'cleanupStorage') {
if (!scheduler.workers.has('cleanupStorage')) {
await scheduler.stop('deployApplication');
await scheduler.run('cleanupStorage')
}
}

View File

@@ -17,19 +17,4 @@ export async function defaultServiceConfigurations({ id, teamId }) {
});
}
return { ...service, network, port, workdir, image, secrets }
}
export function defaultServiceComposeConfiguration(network: string): any {
return {
networks: [network],
restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '10s',
max_attempts: 10,
window: '120s'
}
}
}
}

View File

@@ -5,7 +5,7 @@ import axios from 'axios';
import { FastifyReply } from 'fastify';
import { day } from '../../../../lib/dayjs';
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
import { checkDomainsIsValidInDNS, checkDoubleBranch, checkExposedPort, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
import { scheduler } from '../../../../lib/scheduler';
@@ -18,7 +18,7 @@ export async function listApplications(request: FastifyRequest) {
const { teamId } = request.user
const applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { teams: true, destinationDocker: true }
include: { teams: true, destinationDocker: true, settings: true }
});
const settings = await prisma.setting.findFirst()
return {
@@ -238,6 +238,9 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
if (exposePort) {
exposePort = Number(exposePort);
}
const { destinationDockerId } = await prisma.application.findUnique({ where: { id } })
if (exposePort) await checkExposedPort({ id, exposePort, dockerId: destinationDockerId })
if (denoOptions) denoOptions = denoOptions.trim();
const defaultConfiguration = await setDefaultConfiguration({
buildPack,
@@ -392,18 +395,7 @@ export async function checkDNS(request: FastifyRequest<CheckDNS>) {
if (found) {
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
}
if (exposePort) {
if (exposePort < 1024 || exposePort > 65535) {
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
}
if (configuredPort !== exposePort) {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` }
}
}
}
await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
if (isDNSCheckEnabled && !isDev && !forceSave) {
let hostname = request.hostname.split(':')[0];
if (remoteEngine) hostname = remoteIpAddress;
@@ -436,7 +428,7 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
try {
const { id } = request.params
const teamId = request.user?.teamId;
const { pullmergeRequestId = null, branch } = request.body
const { pullmergeRequestId = null, branch, forceRebuild } = request.body
const buildId = cuid();
const application = await getApplicationFromDB(id, teamId);
if (application) {
@@ -475,13 +467,15 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
type: 'manual',
...application,
sourceBranch: branch,
pullmergeRequestId
pullmergeRequestId,
forceRebuild
});
} else {
scheduler.workers.get('deployApplication').postMessage({
build_id: buildId,
type: 'manual',
...application
...application,
forceRebuild
});
}
@@ -499,11 +493,20 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) {
try {
const { id } = request.params
const { gitSourceId } = request.body
await prisma.application.update({
where: { id },
data: { gitSource: { connect: { id: gitSourceId } } }
});
const { gitSourceId, forPublic, type } = request.body
if (forPublic) {
const publicGit = await prisma.gitSource.findFirst({ where: { type, forPublic } });
await prisma.application.update({
where: { id },
data: { gitSource: { connect: { id: publicGit.id } } }
});
} else {
await prisma.application.update({
where: { id },
data: { gitSource: { connect: { id: gitSourceId } } }
});
}
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -557,7 +560,7 @@ export async function checkRepository(request: FastifyRequest<CheckRepository>)
export async function saveRepository(request, reply) {
try {
const { id } = request.params
let { repository, branch, projectId, autodeploy, webhookToken } = request.body
let { repository, branch, projectId, autodeploy, webhookToken, isPublicRepository = false } = request.body
repository = repository.toLowerCase();
branch = branch.toLowerCase();
@@ -565,17 +568,19 @@ export async function saveRepository(request, reply) {
if (webhookToken) {
await prisma.application.update({
where: { id },
data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy } } }
data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy, isPublicRepository } } }
});
} else {
await prisma.application.update({
where: { id },
data: { repository, branch, projectId, settings: { update: { autodeploy } } }
data: { repository, branch, projectId, settings: { update: { autodeploy, isPublicRepository } } }
});
}
const isDouble = await checkDoubleBranch(branch, projectId);
if (isDouble) {
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
if (!isPublicRepository) {
const isDouble = await checkDoubleBranch(branch, projectId);
if (isDouble) {
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false, isPublicRepository } })
}
}
return reply.code(201).send()
} catch ({ status, message }) {
@@ -607,7 +612,8 @@ export async function getBuildPack(request) {
projectId: application.projectId,
repository: application.repository,
branch: application.branch,
apiUrl: application.gitSource.apiUrl
apiUrl: application.gitSource.apiUrl,
isPublicRepository: application.settings.isPublicRepository
}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -657,13 +663,13 @@ export async function saveSecret(request: FastifyRequest<SaveSecret>, reply: Fas
if (found) {
throw { status: 500, message: `Secret ${name} already exists.` }
} else {
value = encrypt(value);
value = encrypt(value.trim());
await prisma.secret.create({
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
});
}
} else {
value = encrypt(value);
value = encrypt(value.trim());
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
if (found) {

View File

@@ -44,13 +44,13 @@ export interface CheckDNS extends OnlyId {
}
export interface DeployApplication {
Querystring: { domain: string }
Body: { pullmergeRequestId: string | null, branch: string }
Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean }
}
export interface GetImages {
Body: { buildPack: string, deploymentType: string }
}
export interface SaveApplicationSource extends OnlyId {
Body: { gitSourceId: string }
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string }
}
export interface CheckRepository extends OnlyId {
Querystring: { repository: string, branch: string }
@@ -115,7 +115,8 @@ export interface CancelDeployment {
export interface DeployApplication extends OnlyId {
Body: {
pullmergeRequestId: string | null,
branch: string
branch: string,
forceRebuild?: boolean
}
}

View File

@@ -79,7 +79,6 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
if (id === 'new') {
console.log(engine)
if (engine) {
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`);
if (stdout === '') {

View File

@@ -4,7 +4,7 @@ import axios from 'axios';
import compare from 'compare-versions';
import cuid from 'cuid';
import bcrypt from 'bcryptjs';
import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, prisma, uniqueName, version } from '../../../lib/common';
import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, listSettings, prisma, uniqueName, version } from '../../../lib/common';
import type { FastifyReply, FastifyRequest } from 'fastify';
import type { Login, Update } from '.';
@@ -97,7 +97,8 @@ export async function showDashboard(request: FastifyRequest) {
const userId = request.user.userId;
const teamId = request.user.teamId;
const applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { settings: true }
});
const databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
@@ -105,10 +106,12 @@ export async function showDashboard(request: FastifyRequest) {
const services = await prisma.service.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
});
const settings = await listSettings();
return {
applications,
databases,
services,
settings,
};
} catch ({ status, message }) {
return errorHandler({ status, message })

View File

@@ -2,14 +2,14 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
import fs from 'fs/promises';
import yaml from 'js-yaml';
import bcrypt from 'bcryptjs';
import { prisma, uniqueName, asyncExecShell, getServiceImage, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions, executeDockerCmd, listSettings, getFreeExposedPort, checkDomainsIsValidInDNS, persistentVolumes, asyncSleep, isARM } from '../../../../lib/common';
import { prisma, uniqueName, asyncExecShell, getServiceImage, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions, executeDockerCmd, listSettings, getFreeExposedPort, checkDomainsIsValidInDNS, persistentVolumes, asyncSleep, isARM, defaultComposeConfiguration, checkExposedPort } from '../../../../lib/common';
import { day } from '../../../../lib/dayjs';
import { checkContainer, isContainerExited, removeContainer } from '../../../../lib/docker';
import cuid from 'cuid';
import type { OnlyId } from '../../../../types';
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
import { defaultServiceComposeConfiguration, defaultServiceConfigurations } from '../../../../lib/services';
import { defaultServiceConfigurations } from '../../../../lib/services';
// async function startServiceNew(request: FastifyRequest<OnlyId>) {
// try {
@@ -378,18 +378,7 @@ export async function checkService(request: FastifyRequest<CheckService>) {
}
}
}
if (exposePort) {
if (exposePort < 1024 || exposePort > 65535) {
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
}
if (configuredPort !== exposePort) {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` }
}
}
}
await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
if (isDNSCheckEnabled && !isDev && !forceSave) {
let hostname = request.hostname.split(':')[0];
if (remoteEngine) hostname = remoteIpAddress;
@@ -458,13 +447,13 @@ export async function saveServiceSecret(request: FastifyRequest<SaveServiceSecre
if (found) {
throw `Secret ${name} already exists.`
} else {
value = encrypt(value);
value = encrypt(value.trim());
await prisma.serviceSecret.create({
data: { name, value, service: { connect: { id } } }
});
}
} else {
value = encrypt(value);
value = encrypt(value.trim());
const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
if (found) {
@@ -812,21 +801,21 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
depends_on: [`${id}-postgresql`, `${id}-clickhouse`],
labels: makeLabelForServices('plausibleAnalytics'),
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-postgresql`]: {
container_name: `${id}-postgresql`,
image: config.postgresql.image,
environment: config.postgresql.environmentVariables,
volumes: [config.postgresql.volume],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-clickhouse`]: {
build: workdir,
container_name: `${id}-clickhouse`,
environment: config.clickhouse.environmentVariables,
volumes: [config.clickhouse.volume],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -887,7 +876,7 @@ async function startNocodbService(request: FastifyRequest<ServiceStartStop>) {
environment: config.environmentVariables,
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
labels: makeLabelForServices('nocodb'),
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -959,7 +948,7 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
volumes,
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
labels: makeLabelForServices('minio'),
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -1025,7 +1014,7 @@ async function startVscodeService(request: FastifyRequest<ServiceStartStop>) {
volumes,
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
labels: makeLabelForServices('vscodeServer'),
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -1132,7 +1121,7 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
volumes,
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
labels: makeLabelForServices('wordpress'),
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -1149,7 +1138,7 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
image: config.mysql.image,
volumes: [config.mysql.volume],
environment: config.mysql.environmentVariables,
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
};
composeFile.volumes[config.mysql.volume.split(':')[0]] = {
@@ -1202,7 +1191,7 @@ async function startVaultwardenService(request: FastifyRequest<ServiceStartStop>
volumes,
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
labels: makeLabelForServices('vaultWarden'),
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -1258,7 +1247,7 @@ async function startLanguageToolService(request: FastifyRequest<ServiceStartStop
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
volumes,
labels: makeLabelForServices('languagetool'),
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -1315,7 +1304,7 @@ async function startN8nService(request: FastifyRequest<ServiceStartStop>) {
environment: config.environmentVariables,
labels: makeLabelForServices('n8n'),
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -1370,7 +1359,7 @@ async function startUptimekumaService(request: FastifyRequest<ServiceStartStop>)
environment: config.environmentVariables,
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
labels: makeLabelForServices('uptimekuma'),
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -1469,14 +1458,14 @@ async function startGhostService(request: FastifyRequest<ServiceStartStop>) {
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
labels: makeLabelForServices('ghost'),
depends_on: [`${id}-mariadb`],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-mariadb`]: {
container_name: `${id}-mariadb`,
image: config.mariadb.image,
volumes: [config.mariadb.volume],
environment: config.mariadb.environmentVariables,
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -1542,7 +1531,7 @@ async function startMeilisearchService(request: FastifyRequest<ServiceStartStop>
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
volumes,
labels: makeLabelForServices('meilisearch'),
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -1708,14 +1697,14 @@ async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
labels: makeLabelForServices('umami'),
depends_on: [`${id}-postgresql`],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-postgresql`]: {
build: workdir,
container_name: `${id}-postgresql`,
environment: config.postgresql.environmentVariables,
volumes: [config.postgresql.volume],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -1795,14 +1784,14 @@ async function startHasuraService(request: FastifyRequest<ServiceStartStop>) {
labels: makeLabelForServices('hasura'),
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
depends_on: [`${id}-postgresql`],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-postgresql`]: {
image: config.postgresql.image,
container_name: `${id}-postgresql`,
environment: config.postgresql.environmentVariables,
volumes: [config.postgresql.volume],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -1908,14 +1897,14 @@ async function startFiderService(request: FastifyRequest<ServiceStartStop>) {
labels: makeLabelForServices('fider'),
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
depends_on: [`${id}-postgresql`],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-postgresql`]: {
image: config.postgresql.image,
container_name: `${id}-postgresql`,
environment: config.postgresql.environmentVariables,
volumes: [config.postgresql.volume],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
},
networks: {
@@ -2001,7 +1990,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
"_APP_STATSD_PORT=8125",
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-realtime`]: {
image: `${image}:${version}`,
@@ -2024,7 +2013,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
`_APP_DB_PASS=${mariadbPassword}`,
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-worker-audits`]: {
@@ -2048,7 +2037,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
`_APP_DB_PASS=${mariadbPassword}`,
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-worker-webhooks`]: {
image: `${image}:${version}`,
@@ -2066,7 +2055,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
"_APP_REDIS_PORT=6379",
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-worker-deletes`]: {
image: `${image}:${version}`,
@@ -2099,7 +2088,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
`_APP_EXECUTOR_HOST=http://${id}-executor/v1`,
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-worker-databases`]: {
image: `${image}:${version}`,
@@ -2122,7 +2111,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
`_APP_DB_PASS=${mariadbPassword}`,
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-worker-builds`]: {
image: `${image}:${version}`,
@@ -2147,7 +2136,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
`_APP_DB_PASS=${mariadbPassword}`,
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-worker-certificates`]: {
image: `${image}:${version}`,
@@ -2176,7 +2165,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
`_APP_DB_PASS=${mariadbPassword}`,
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-worker-functions`]: {
image: `${image}:${version}`,
@@ -2202,7 +2191,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
`_APP_EXECUTOR_HOST=http://${id}-executor/v1`,
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-executor`]: {
image: `${image}:${version}`,
@@ -2226,7 +2215,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
`_APP_EXECUTOR_SECRET=${executorSecret}`,
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-worker-mails`]: {
image: `${image}:${version}`,
@@ -2243,7 +2232,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
"_APP_REDIS_PORT=6379",
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-worker-messaging`]: {
image: `${image}:${version}`,
@@ -2259,7 +2248,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
"_APP_REDIS_PORT=6379",
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-maintenance`]: {
image: `${image}:${version}`,
@@ -2283,7 +2272,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
`_APP_DB_PASS=${mariadbPassword}`,
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-schedule`]: {
image: `${image}:${version}`,
@@ -2299,7 +2288,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
"_APP_REDIS_PORT=6379",
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-mariadb`]: {
"image": "mariadb:10.7",
@@ -2316,7 +2305,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
`MYSQL_DATABASE=${mariadbDatabase}`
],
"command": "mysqld --innodb-flush-method=fsync",
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
[`${id}-redis`]: {
"image": "redis:6.2-alpine",
@@ -2325,7 +2314,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
"volumes": [
`${id}-redis:/data:rw`
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
},
};
@@ -2354,7 +2343,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
"_APP_REDIS_PORT=6379",
...secrets
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
dockerCompose[`${id}-influxdb`] = {
"image": "appwrite/influxdb:1.5.0",
@@ -2362,7 +2351,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
"volumes": [
`${id}-influxdb:/var/lib/influxdb:rw`
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
dockerCompose[`${id}-telegraf`] = {
"image": "appwrite/telegraf:1.4.0",
@@ -2371,7 +2360,7 @@ async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
`_APP_INFLUXDB_HOST=${id}-influxdb`,
"_APP_INFLUXDB_PORT=8086",
],
...defaultServiceComposeConfiguration(network),
...defaultComposeConfiguration(network),
}
}

View File

@@ -33,12 +33,13 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
minPort,
maxPort,
isAutoUpdateEnabled,
isDNSCheckEnabled
isDNSCheckEnabled,
DNSServers
} = request.body
const { id } = await listSettings();
await prisma.setting.update({
where: { id },
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled }
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers }
});
if (fqdn) {
await prisma.setting.update({ where: { id }, data: { fqdn } });
@@ -54,6 +55,10 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
try {
const { fqdn } = request.body
const { DNSServers } = await listSettings();
if (DNSServers) {
dns.setServers([DNSServers]);
}
let ip;
try {
ip = await dns.resolve(fqdn);

View File

@@ -8,7 +8,8 @@ export interface SaveSettings {
minPort: number,
maxPort: number,
isAutoUpdateEnabled: boolean,
isDNSCheckEnabled: boolean
isDNSCheckEnabled: boolean,
DNSServers: string
}
}
export interface DeleteDomain {

View File

@@ -484,7 +484,6 @@ export async function traefikOtherConfiguration(request: FastifyRequest<TraefikO
}
throw { status: 500 }
} catch ({ status, message }) {
console.log(status, message);
return errorHandler({ status, message })
}
}