diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2a9a43c2b..95310d2aa 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -72,9 +72,9 @@ model TeamInvitation { } model Application { - id String @id @default(cuid()) + id String @id @default(cuid()) name String - fqdn String? @unique + fqdn String? @unique repository String? configHash String? branch String? @@ -86,16 +86,17 @@ model Application { startCommand String? baseDirectory String? publishDirectory String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + phpModules String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt settings ApplicationSettings? teams Team[] destinationDockerId String? - destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) + destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) gitSourceId String? - gitSource GitSource? @relation(fields: [gitSourceId], references: [id]) + gitSource GitSource? @relation(fields: [gitSourceId], references: [id]) secrets Secret[] - phpModules String? + persistentStorage ApplicationPersistentStorage[] } model ApplicationSettings { @@ -110,6 +111,17 @@ model ApplicationSettings { updatedAt DateTime @updatedAt } +model ApplicationPersistentStorage { + id String @id @default(cuid()) + application Application @relation(fields: [applicationId], references: [id]) + applicationId String @unique + path String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([applicationId, path]) +} + model Secret { id String @id @default(cuid()) name String diff --git a/src/app.d.ts b/src/app.d.ts index c3921e0f2..63734a3af 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -7,7 +7,9 @@ declare namespace App { } interface Platform {} interface Session extends SessionData {} - interface Stuff {} + interface Stuff { + application: any; + } } interface SessionData { diff --git a/src/lib/buildPacks/gatsby.ts b/src/lib/buildPacks/gatsby.ts index 3e9c34a2d..a721af128 100644 --- a/src/lib/buildPacks/gatsby.ts +++ b/src/lib/buildPacks/gatsby.ts @@ -8,7 +8,7 @@ const createDockerfile = async (data, imageforBuild): Promise => { Dockerfile.push(`FROM ${imageforBuild}`); Dockerfile.push('WORKDIR /usr/share/nginx/html'); Dockerfile.push(`LABEL coolify.image=true`); - Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); Dockerfile.push(`EXPOSE 80`); Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); diff --git a/src/lib/buildPacks/nestjs.ts b/src/lib/buildPacks/nestjs.ts index b30c5ecd9..915bdd3d7 100644 --- a/src/lib/buildPacks/nestjs.ts +++ b/src/lib/buildPacks/nestjs.ts @@ -7,15 +7,13 @@ const createDockerfile = async (data, image): Promise => { const isPnpm = startCommand.includes('pnpm'); Dockerfile.push(`FROM ${image}`); - Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.image=true`); if (isPnpm) { Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); Dockerfile.push('RUN pnpm add -g pnpm'); } - Dockerfile.push( - `COPY --from=${applicationId}:${tag}-cache /usr/src/app/${baseDirectory || ''} ./` - ); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${baseDirectory || ''} ./`); Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`CMD ${startCommand}`); diff --git a/src/lib/buildPacks/nextjs.ts b/src/lib/buildPacks/nextjs.ts index c8d6a03bc..4f1f3b0d6 100644 --- a/src/lib/buildPacks/nextjs.ts +++ b/src/lib/buildPacks/nextjs.ts @@ -16,7 +16,7 @@ const createDockerfile = async (data, image): Promise => { const Dockerfile: Array = []; const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); Dockerfile.push(`FROM ${image}`); - Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.image=true`); if (secrets.length > 0) { secrets.forEach((secret) => { diff --git a/src/lib/buildPacks/node.ts b/src/lib/buildPacks/node.ts index cb99ecec8..54527a728 100644 --- a/src/lib/buildPacks/node.ts +++ b/src/lib/buildPacks/node.ts @@ -17,7 +17,7 @@ const createDockerfile = async (data, image): Promise => { const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); Dockerfile.push(`FROM ${image}`); - Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.image=true`); if (secrets.length > 0) { secrets.forEach((secret) => { diff --git a/src/lib/buildPacks/nuxtjs.ts b/src/lib/buildPacks/nuxtjs.ts index ca7c1809c..207afe864 100644 --- a/src/lib/buildPacks/nuxtjs.ts +++ b/src/lib/buildPacks/nuxtjs.ts @@ -16,7 +16,7 @@ const createDockerfile = async (data, image): Promise => { const Dockerfile: Array = []; const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); Dockerfile.push(`FROM ${image}`); - Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.image=true`); if (secrets.length > 0) { secrets.forEach((secret) => { diff --git a/src/lib/buildPacks/react.ts b/src/lib/buildPacks/react.ts index 29c462d02..3e372b776 100644 --- a/src/lib/buildPacks/react.ts +++ b/src/lib/buildPacks/react.ts @@ -8,7 +8,7 @@ const createDockerfile = async (data, image): Promise => { Dockerfile.push(`FROM ${image}`); Dockerfile.push(`LABEL coolify.image=true`); Dockerfile.push('WORKDIR /usr/share/nginx/html'); - Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); Dockerfile.push(`EXPOSE 80`); Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); diff --git a/src/lib/buildPacks/rust.ts b/src/lib/buildPacks/rust.ts index 49eb59815..586d63140 100644 --- a/src/lib/buildPacks/rust.ts +++ b/src/lib/buildPacks/rust.ts @@ -7,23 +7,21 @@ const createDockerfile = async (data, image, name): Promise => { const { workdir, port, applicationId, tag } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); - Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.image=true`); - Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/target target`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target target`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/local/cargo /usr/local/cargo`); Dockerfile.push(`COPY . .`); Dockerfile.push(`RUN cargo build --release --bin ${name}`); Dockerfile.push('FROM debian:buster-slim'); - Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push('WORKDIR /app'); Dockerfile.push( `RUN apt-get update -y && apt-get install -y --no-install-recommends openssl libcurl4 ca-certificates && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*` ); Dockerfile.push(`RUN update-ca-certificates`); - Dockerfile.push( - `COPY --from=${applicationId}:${tag}-cache /usr/src/app/target/release/${name} ${name}` - ); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`); Dockerfile.push(`EXPOSE ${port}`); - Dockerfile.push(`CMD ["/usr/src/app/${name}"]`); + Dockerfile.push(`CMD ["/app/${name}"]`); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); }; diff --git a/src/lib/buildPacks/static.ts b/src/lib/buildPacks/static.ts index 075fad0aa..20393ac22 100644 --- a/src/lib/buildPacks/static.ts +++ b/src/lib/buildPacks/static.ts @@ -33,9 +33,7 @@ const createDockerfile = async (data, image): Promise => { }); } if (buildCommand) { - Dockerfile.push( - `COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./` - ); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); } else { Dockerfile.push(`COPY .${baseDirectory || ''} ./`); } diff --git a/src/lib/buildPacks/svelte.ts b/src/lib/buildPacks/svelte.ts index e6821dafe..181782b89 100644 --- a/src/lib/buildPacks/svelte.ts +++ b/src/lib/buildPacks/svelte.ts @@ -8,7 +8,7 @@ const createDockerfile = async (data, image): Promise => { Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /usr/share/nginx/html'); Dockerfile.push(`LABEL coolify.image=true`); - Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); Dockerfile.push(`EXPOSE 80`); Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); diff --git a/src/lib/buildPacks/vuejs.ts b/src/lib/buildPacks/vuejs.ts index 4fe90038b..7953977ce 100644 --- a/src/lib/buildPacks/vuejs.ts +++ b/src/lib/buildPacks/vuejs.ts @@ -8,7 +8,7 @@ const createDockerfile = async (data, image): Promise => { Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /usr/share/nginx/html'); Dockerfile.push(`LABEL coolify.image=true`); - Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); Dockerfile.push(`EXPOSE 80`); Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); diff --git a/src/lib/database/applications.ts b/src/lib/database/applications.ts index d5285e79a..1ee24a567 100644 --- a/src/lib/database/applications.ts +++ b/src/lib/database/applications.ts @@ -134,7 +134,8 @@ export async function getApplication({ id, teamId }) { destinationDocker: true, settings: true, gitSource: { include: { githubApp: true, gitlabApp: true } }, - secrets: true + secrets: true, + persistentStorage: true } }); @@ -268,3 +269,7 @@ export async function createBuild({ } }); } + +export async function getPersistentStorage(id) { + return await prisma.applicationPersistentStorage.findMany({ where: { applicationId: id } }); +} diff --git a/src/lib/docker.ts b/src/lib/docker.ts index ccbc21b2a..717303040 100644 --- a/src/lib/docker.ts +++ b/src/lib/docker.ts @@ -20,7 +20,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) { const isPnpm = checkPnpm(installCommand, buildCommand); const Dockerfile: Array = []; Dockerfile.push(`FROM ${imageForBuild}`); - Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.image=true`); if (secrets.length > 0) { secrets.forEach((secret) => { @@ -65,14 +65,14 @@ export async function buildCacheImageWithCargo(data, imageForBuild) { } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`); - Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push('WORKDIR /app'); Dockerfile.push('RUN cargo install cargo-chef'); Dockerfile.push('COPY . .'); Dockerfile.push('RUN cargo chef prepare --recipe-path recipe.json'); Dockerfile.push(`FROM ${imageForBuild}`); - Dockerfile.push('WORKDIR /usr/src/app'); + Dockerfile.push('WORKDIR /app'); Dockerfile.push('RUN cargo install cargo-chef'); - Dockerfile.push(`COPY --from=planner-${applicationId} /usr/src/app/recipe.json recipe.json`); + Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`); Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json'); await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); diff --git a/src/lib/queues/builder.ts b/src/lib/queues/builder.ts index f0617d328..cad82bfe0 100644 --- a/src/lib/queues/builder.ts +++ b/src/lib/queues/builder.ts @@ -49,8 +49,10 @@ export default async function (job) { type, pullmergeRequestId = null, sourceBranch = null, - settings + settings, + persistentStorage } = job.data; + console.log(persistentStorage); const { debug } = settings; await asyncSleep(1000); @@ -68,6 +70,10 @@ export default async function (job) { let domain = getDomain(fqdn); const isHttps = fqdn.startsWith('https://'); + let volumes = + persistentStorage?.map((storage) => { + return `${applicationId}-${storage.id}:${storage.path}`; + }) || []; // Previews, we need to get the source branch and set subdomain if (pullmergeRequestId) { branch = sourceBranch; @@ -252,12 +258,22 @@ export default async function (job) { } try { saveBuildLog({ line: 'Deployment started.', buildId, applicationId }); + for await (const volume of volumes) { + const id = volume.split(':')[0]; + try { + await asyncExecShell(`DOCKER_HOST=${host} docker volume inspect ${id}`); + } catch (error) { + await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}`); + } + } + volumes = volumes.map((volume) => `-v ${volume} `).join(); + console.log(volumes); const { stderr } = await asyncExecShell( `DOCKER_HOST=${host} docker run ${envFound && `--env-file=${workdir}/.env`} ${labels.join( ' ' - )} --name ${imageId} --network ${ - docker.network - } --restart always -d ${applicationId}:${tag}` + )} --name ${imageId} --network ${docker.network} --restart always ${ + volumes.length > 0 && volumes + } -d ${applicationId}:${tag}` ); if (stderr) console.log(stderr); saveBuildLog({ line: 'Deployment successful!', buildId, applicationId }); diff --git a/src/routes/applications/[id]/__layout.svelte b/src/routes/applications/[id]/__layout.svelte index fb944519b..c41f33282 100644 --- a/src/routes/applications/[id]/__layout.svelte +++ b/src/routes/applications/[id]/__layout.svelte @@ -271,6 +271,35 @@ + + + export let isNew = false; + export let storage = { + id: null, + path: null + }; + import { del, post } from '$lib/api'; + import { page } from '$app/stores'; + import { errorNotification } from '$lib/form'; + const { id } = $page.params; + + async function saveStorage() { + try { + await post(`/applications/${id}/storage.json`, { + path: storage.path + }); + } catch ({ error }) { + return errorNotification(error); + } + } + async function removeStorage() { + try { + await del(`/applications/${id}/storage.json`, { path: storage.path }); + } catch ({ error }) { + return errorNotification(error); + } + } + + + + + + +
+ +
+
+ +
+ diff --git a/src/routes/applications/[id]/storage/index.json.ts b/src/routes/applications/[id]/storage/index.json.ts new file mode 100644 index 000000000..1c0bcab6a --- /dev/null +++ b/src/routes/applications/[id]/storage/index.json.ts @@ -0,0 +1,58 @@ +import { getTeam, getUserDetails } from '$lib/common'; +import * as db from '$lib/database'; +import { ErrorHandler } from '$lib/database'; +import { dockerInstance } from '$lib/docker'; +import type { RequestHandler } from '@sveltejs/kit'; +import jsonwebtoken from 'jsonwebtoken'; + +export const get: RequestHandler = async (event) => { + const { status, body, teamId } = await getUserDetails(event, false); + if (status === 401) return { status, body }; + + const { id } = event.params; + try { + const persistentStorages = await db.getPersistentStorage(id); + return { + body: { + persistentStorages + } + }; + } catch (error) { + return ErrorHandler(error); + } +}; + +export const post: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { path } = await event.request.json(); + try { + await db.prisma.applicationPersistentStorage.create({ + data: { path, application: { connect: { id } } } + }); + return { + status: 201 + }; + } catch (error) { + return ErrorHandler(error); + } +}; + +export const del: RequestHandler = async (event) => { + const { teamId, status, body } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const { path } = await event.request.json(); + + try { + await db.prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id, path } }); + return { + status: 200 + }; + } catch (error) { + return ErrorHandler(error); + } +}; diff --git a/src/routes/applications/[id]/storage/index.svelte b/src/routes/applications/[id]/storage/index.svelte new file mode 100644 index 000000000..1d64bbf6c --- /dev/null +++ b/src/routes/applications/[id]/storage/index.svelte @@ -0,0 +1,61 @@ + + + + +
+ +
+ + + + + + + + {#each persistentStorages as storage} + {#key storage.id} + + + + {/key} + {/each} + + + + +
Path
+