diff --git a/.gitignore b/.gitignore index 7e5eecf8e..9e73cbebf 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ package dist client apps/api/db/*.db -local-serve \ No newline at end of file +local-serve +apps/api/db/migration.db-journal \ No newline at end of file diff --git a/apps/api/package.json b/apps/api/package.json index aac907a28..8aacd775f 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -6,6 +6,7 @@ "db:push": "prisma db push && prisma generate", "db:seed": "prisma db seed", "db:studio": "prisma studio", + "db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name", "dev": "nodemon", "build": "rimraf build && esbuild `find src \\( -name '*.ts' \\)| grep -v client/` --platform=node --outdir=build --format=cjs", "format": "prettier --write 'src/**/*.{js,ts,json,md}'", diff --git a/apps/api/prisma/migrations/20220708132655_deployment_type_for_applications/migration.sql b/apps/api/prisma/migrations/20220708132655_deployment_type_for_applications/migration.sql new file mode 100644 index 000000000..2776d1b88 --- /dev/null +++ b/apps/api/prisma/migrations/20220708132655_deployment_type_for_applications/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Application" ADD COLUMN "deploymentType" TEXT; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 7cef6adda..7aba93db4 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -91,6 +91,7 @@ model Application { startCommand String? baseDirectory String? publishDirectory String? + deploymentType String? phpModules String? pythonWSGI String? pythonModule String? diff --git a/apps/api/src/jobs/deployApplication.ts b/apps/api/src/jobs/deployApplication.ts index c975de41f..ea090ebfd 100644 --- a/apps/api/src/jobs/deployApplication.ts +++ b/apps/api/src/jobs/deployApplication.ts @@ -55,7 +55,8 @@ import * as buildpacks from '../lib/buildPacks'; denoOptions, exposePort, baseImage, - baseBuildImage + baseBuildImage, + deploymentType } = message let { branch, @@ -225,7 +226,8 @@ import * as buildpacks from '../lib/buildPacks'; denoMainFile, denoOptions, baseImage, - baseBuildImage + baseBuildImage, + deploymentType }); else { await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId }); diff --git a/apps/api/src/lib/buildPacks/common.ts b/apps/api/src/lib/buildPacks/common.ts index a2330cf52..f66464133 100644 --- a/apps/api/src/lib/buildPacks/common.ts +++ b/apps/api/src/lib/buildPacks/common.ts @@ -17,7 +17,7 @@ const nodeBased = [ 'nextjs' ]; -export function setDefaultBaseImage(buildPack: string | null) { +export function setDefaultBaseImage(buildPack: string | null, deploymentType: string | null) { const nodeVersions = [ { value: 'node:lts', @@ -259,10 +259,17 @@ export function setDefaultBaseImage(buildPack: string | null) { baseBuildImages: [] }; if (nodeBased.includes(buildPack)) { - payload.baseImage = 'node:lts'; - payload.baseImages = nodeVersions; - payload.baseBuildImage = 'node:lts'; - payload.baseBuildImages = nodeVersions; + if (deploymentType === 'static') { + payload.baseImage = 'webdevops/nginx:alpine'; + payload.baseImages = staticVersions; + payload.baseBuildImage = 'node:lts'; + payload.baseBuildImages = nodeVersions; + } else { + payload.baseImage = 'node:lts'; + payload.baseImages = nodeVersions; + payload.baseBuildImage = 'node:lts'; + payload.baseBuildImages = nodeVersions; + } } if (staticApps.includes(buildPack)) { payload.baseImage = 'webdevops/nginx:alpine'; @@ -431,7 +438,7 @@ export async function copyBaseConfigurationFiles( buildId, applicationId }); - } else if (staticApps.includes(buildPack) && baseImage.includes('nginx')) { + } else if (baseImage.includes('nginx')) { await fs.writeFile( `${workdir}/nginx.conf`, `user nginx; diff --git a/apps/api/src/lib/buildPacks/nextjs.ts b/apps/api/src/lib/buildPacks/nextjs.ts index e7cc69290..acf17ac8c 100644 --- a/apps/api/src/lib/buildPacks/nextjs.ts +++ b/apps/api/src/lib/buildPacks/nextjs.ts @@ -1,17 +1,22 @@ import { promises as fs } from 'fs'; -import { buildImage, checkPnpm } from './common'; +import { buildCacheImageWithNode, buildImage, checkPnpm } from './common'; const createDockerfile = async (data, image): Promise => { const { + applicationId, buildId, + tag, workdir, + publishDirectory, port, installCommand, buildCommand, startCommand, baseDirectory, secrets, - pullmergeRequestId + pullmergeRequestId, + deploymentType, + baseImage } = data; const Dockerfile: Array = []; const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); @@ -36,22 +41,35 @@ const createDockerfile = async (data, image): Promise => { if (isPnpm) { Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7'); } - Dockerfile.push(`COPY .${baseDirectory || ''} ./`); - Dockerfile.push(`RUN ${installCommand}`); - - if (buildCommand) { + if (deploymentType === 'node') { + Dockerfile.push(`COPY .${baseDirectory || ''} ./`); + Dockerfile.push(`RUN ${installCommand}`); Dockerfile.push(`RUN ${buildCommand}`); + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD ${startCommand}`); + } else if (deploymentType === 'static') { + if (baseImage.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); + Dockerfile.push(`EXPOSE 80`); } - Dockerfile.push(`EXPOSE ${port}`); - Dockerfile.push(`CMD ${startCommand}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); }; export default async function (data) { try { - const { baseImage, baseBuildImage } = data; - await createDockerfile(data, baseImage); - await buildImage(data); + const { baseImage, baseBuildImage, deploymentType, buildCommand } = data; + if (deploymentType === 'node') { + await createDockerfile(data, baseImage); + await buildImage(data); + } else if (deploymentType === 'static') { + if (buildCommand) await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); + await buildImage(data); + } + } catch (error) { throw error; } diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index 9465d33ca..9aeb7fa94 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -31,6 +31,31 @@ export async function listApplications(request: FastifyRequest) { return errorHandler({ status, message }) } } +export async function getImages(request: FastifyRequest) { + try { + const { buildPack, deploymentType } = request.body + let publishDirectory = undefined; + let port = undefined + const { baseImage, baseBuildImage, baseBuildImages, baseImages, } = setDefaultBaseImage( + buildPack, deploymentType + ); + if (buildPack === 'nextjs') { + if (deploymentType === 'static') { + publishDirectory = 'out' + port = '80' + } else { + publishDirectory = '' + port = '3000' + } + } + + + return { baseImage, baseBuildImage, baseBuildImages, baseImages, publishDirectory, port } + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} + export async function getApplication(request: FastifyRequest) { try { const { id } = request.params @@ -184,7 +209,8 @@ export async function saveApplication(request: FastifyRequest, denoMainFile, denoOptions, baseImage, - baseBuildImage + baseBuildImage, + deploymentType } = request.body if (port) port = Number(port); @@ -215,6 +241,7 @@ export async function saveApplication(request: FastifyRequest, denoOptions, baseImage, baseBuildImage, + deploymentType, ...defaultConfiguration } }); diff --git a/apps/api/src/routes/api/v1/applications/index.ts b/apps/api/src/routes/api/v1/applications/index.ts index 74a481358..224bacc22 100644 --- a/apps/api/src/routes/api/v1/applications/index.ts +++ b/apps/api/src/routes/api/v1/applications/index.ts @@ -1,5 +1,5 @@ import { FastifyPluginAsync } from 'fastify'; -import { cancelDeployment, checkDNS, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers'; +import { cancelDeployment, checkDNS, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers'; export interface GetApplication { Params: { id: string; } @@ -37,6 +37,7 @@ const root: FastifyPluginAsync = async (fastify, opts): Promise => { return await request.jwtVerify() }) fastify.get('/', async (request) => await listApplications(request)); + fastify.post('/images', async (request) => await getImages(request)); fastify.post('/new', async (request, reply) => await newApplication(request, reply)); @@ -67,7 +68,7 @@ const root: FastifyPluginAsync = async (fastify, opts): Promise => { fastify.post('/:id/deploy', async (request) => await deployApplication(request)) fastify.post('/:id/cancel', async (request, reply) => await cancelDeployment(request, reply)); - + fastify.post('/:id/configuration/source', async (request, reply) => await saveApplicationSource(request, reply)); fastify.get('/:id/configuration/repository', async (request) => await checkRepository(request)); diff --git a/apps/ui/src/lib/templates.ts b/apps/ui/src/lib/templates.ts index 7b81dbadd..9ad0a9531 100644 --- a/apps/ui/src/lib/templates.ts +++ b/apps/ui/src/lib/templates.ts @@ -70,7 +70,8 @@ export function findBuildPack(pack: string, packageManager = 'npm') { ...metaData, ...defaultBuildAndDeploy(packageManager), publishDirectory: null, - port: 3000 + port: 3000, + deploymentType: 'node' }; } if (pack === 'gatsby') { diff --git a/apps/ui/src/routes/applications/[id]/configuration/buildpack.svelte b/apps/ui/src/routes/applications/[id]/configuration/buildpack.svelte index 8515bc2bd..7330bbbd0 100644 --- a/apps/ui/src/routes/applications/[id]/configuration/buildpack.svelte +++ b/apps/ui/src/routes/applications/[id]/configuration/buildpack.svelte @@ -60,7 +60,7 @@ } } } - async function scanRepository() { + async function scanRepository(): Promise { try { if (type === 'gitlab') { const files = await get(`${apiUrl}/v4/projects/${projectId}/repository/tree`, { diff --git a/apps/ui/src/routes/applications/[id]/index.svelte b/apps/ui/src/routes/applications/[id]/index.svelte index a00f404eb..a1874f502 100644 --- a/apps/ui/src/routes/applications/[id]/index.svelte +++ b/apps/ui/src/routes/applications/[id]/index.svelte @@ -103,8 +103,18 @@ usageInterval = setInterval(async () => { await getUsage(); }, 1000); + await getBaseBuildImages(); }); - + async function getBaseBuildImages() { + const data = await post(`/applications/images`, { + buildPack: application.buildPack, + deploymentType: application.deploymentType + }); + application = { + ...application, + ...data + }; + } async function changeSettings(name: any) { if (name === 'debug') { debug = !debug; @@ -145,10 +155,12 @@ } } async function handleSubmit() { - if (loading) return; + if (loading || !application.fqdn) return; loading = true; try { nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, ''); + if (application.deploymentType) + application.deploymentType = application.deploymentType.toLowerCase(); await post(`/applications/${id}/check`, { fqdn: application.fqdn, forceSave, @@ -192,6 +204,11 @@ application.baseBuildImage = event.detail.value; await handleSubmit(); } + async function selectDeploymentType(event: any) { + application.deploymentType = event.detail.value; + await getBaseBuildImages(); + await handleSubmit(); + } async function isDNSValid(domain: any, isWWW: any) { try { @@ -404,26 +421,6 @@ /> - {#if application.buildPack !== 'docker'} -
- -
- +
+ +
+ {/if} + {#if application.buildPack !== 'docker' && application.buildPack === 'nextjs'} +
+ +
+ {$t('application.install_command')} {$t('application.build_command')} {$t('application.start_command')} Dockerfile Location
diff --git a/package.json b/package.json index 2719d45a1..645cdc77b 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "db:studio": "pnpm run --filter coolify-api db:studio", "db:push": "pnpm run --filter coolify-api db:push", "db:seed": "pnpm run --filter coolify-api db:seed", + "db:migrate": "pnpm run --filter coolify-api db:migrate", "format": "run-p -l -n format:*", "format:api": "NODE_ENV=development pnpm run --filter coolify-api format", "lint": "run-p -l -n lint:*",