diff --git a/.dockerignore b/.dockerignore index 62248479b..7e5eecf8e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,5 @@ package !.env.example dist client -apps/api/db/*.db \ No newline at end of file +apps/api/db/*.db +local-serve \ No newline at end of file diff --git a/.gitignore b/.gitignore index 62248479b..9e73cbebf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ package !.env.example dist client -apps/api/db/*.db \ No newline at end of file +apps/api/db/*.db +local-serve +apps/api/db/migration.db-journal \ No newline at end of file diff --git a/.gitpod.yml b/.gitpod.yml index ea449728d..87e5b29f3 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,9 +1,12 @@ # This configuration file was automatically generated by Gitpod. # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) # and commit this file to your remote git repository to share the goodness with others. - +image: node:18 tasks: - - init: npm install && npm run build - command: npm run start - + - before: curl -sL https://unpkg.com/@pnpm/self-installer | node + - init: pnpm install && pnpm db:push && pnpm db:seed + command: pnpm dev +ports: + - port: 3001 + visibility: public \ 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/migrations/20220712083523_custom_port_git_sources/migration.sql b/apps/api/prisma/migrations/20220712083523_custom_port_git_sources/migration.sql new file mode 100644 index 000000000..f702291b2 --- /dev/null +++ b/apps/api/prisma/migrations/20220712083523_custom_port_git_sources/migration.sql @@ -0,0 +1,24 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_GitSource" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "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", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "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"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 7cef6adda..862c34e14 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? @@ -217,6 +218,7 @@ model GitSource { type String? apiUrl String? htmlUrl String? + customPort Int @default(22) organization String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 793504c90..073732c5f 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -68,7 +68,8 @@ const schema = { }; const options = { - schema + schema, + dotenv: true }; fastify.register(env, options); if (!isDev) { @@ -76,7 +77,7 @@ if (!isDev) { root: path.join(__dirname, './public'), preCompressed: true }); - fastify.setNotFoundHandler(function (request, reply) { + fastify.setNotFoundHandler({}, function (request, reply) { if (request.raw.url && request.raw.url.startsWith('/api')) { return reply.status(404).send({ success: false @@ -105,23 +106,30 @@ fastify.listen({ port, host }, async (err: any, address: any) => { await scheduler.start('cleanupStorage'); await scheduler.start('checkProxies') - // Check if no build is running, try to autoupdate. + // Check if no build is running + + // Check for update setInterval(async () => { const { isAutoUpdateEnabled } = await prisma.setting.findFirst(); if (isAutoUpdateEnabled) { if (scheduler.workers.has('deployApplication')) { - scheduler.workers.get('deployApplication').postMessage("status"); + scheduler.workers.get('deployApplication').postMessage("status:autoUpdater"); } } - }, 30000 * 10) + }, 60000 * 15) + + // Cleanup storage + setInterval(async () => { + if (scheduler.workers.has('deployApplication')) { + scheduler.workers.get('deployApplication').postMessage("status:cleanupStorage"); + } + }, 60000 * 10) scheduler.on('worker deleted', async (name) => { - if (name === 'autoUpdater') { - await scheduler.start('deployApplication'); + if (name === 'autoUpdater' || name === 'cleanupStorage') { + if (!scheduler.workers.has('deployApplication')) await scheduler.start('deployApplication'); } - }); - }); async function initServer() { diff --git a/apps/api/src/jobs/deployApplication.ts b/apps/api/src/jobs/deployApplication.ts index 10703a105..d553e69c9 100644 --- a/apps/api/src/jobs/deployApplication.ts +++ b/apps/api/src/jobs/deployApplication.ts @@ -4,7 +4,7 @@ import fs from 'fs/promises'; import yaml from 'js-yaml'; import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common'; -import { asyncExecShell, createDirectories, decrypt, getDomain, prisma } from '../lib/common'; +import { asyncExecShell, createDirectories, decrypt, getDomain, prisma } from '../lib/common'; import { dockerInstance, getEngine } from '../lib/docker'; import * as importers from '../lib/importers'; import * as buildpacks from '../lib/buildPacks'; @@ -21,8 +21,12 @@ import * as buildpacks from '../lib/buildPacks'; parentPort.postMessage('cancelled'); return; } - if (message === 'status') { - parentPort.postMessage({ size: queue.size, pending: queue.pending }); + if (message === 'status:autoUpdater') { + parentPort.postMessage({ size: queue.size, pending: queue.pending, caller: 'autoUpdater' }); + return; + } + if (message === 'status:cleanupStorage') { + parentPort.postMessage({ size: queue.size, pending: queue.pending, caller: 'cleanupStorage' }); return; } @@ -51,7 +55,8 @@ import * as buildpacks from '../lib/buildPacks'; denoOptions, exposePort, baseImage, - baseBuildImage + baseBuildImage, + deploymentType, } = message let { branch, @@ -122,6 +127,7 @@ import * as buildpacks from '../lib/buildPacks'; repodir, githubAppId: gitSource.githubApp?.id, gitlabAppId: gitSource.gitlabApp?.id, + customPort: gitSource.customPort, repository, branch, buildId, @@ -221,7 +227,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..1feb33822 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 = 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/deno.ts b/apps/api/src/lib/buildPacks/deno.ts index 3b35641bc..6dafa55ed 100644 --- a/apps/api/src/lib/buildPacks/deno.ts +++ b/apps/api/src/lib/buildPacks/deno.ts @@ -42,9 +42,8 @@ const createDockerfile = async (data, image): Promise => { Dockerfile.push(`COPY .${baseDirectory || ''}/deps.ts /app`); Dockerfile.push(`RUN deno cache deps.ts`); } - Dockerfile.push(`COPY ${denoMainFile} /app`); - Dockerfile.push(`RUN deno cache ${denoMainFile}`); Dockerfile.push(`COPY .${baseDirectory || ''} ./`); + Dockerfile.push(`RUN deno cache ${denoMainFile}`); Dockerfile.push(`ENV NO_COLOR true`); Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`CMD deno run ${denoOptions ? denoOptions.split(' ') : ''} ${denoMainFile}`); diff --git a/apps/api/src/lib/buildPacks/gatsby.ts b/apps/api/src/lib/buildPacks/gatsby.ts index c0b76b2a5..fbb0a933f 100644 --- a/apps/api/src/lib/buildPacks/gatsby.ts +++ b/apps/api/src/lib/buildPacks/gatsby.ts @@ -9,7 +9,7 @@ const createDockerfile = async (data, imageforBuild): Promise => { Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); - if (baseImage.includes('nginx')) { + if (baseImage?.includes('nginx')) { Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); } Dockerfile.push(`EXPOSE ${port}`); diff --git a/apps/api/src/lib/buildPacks/nextjs.ts b/apps/api/src/lib/buildPacks/nextjs.ts index e7cc69290..90d80449e 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,34 @@ 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/lib/buildPacks/nuxtjs.ts b/apps/api/src/lib/buildPacks/nuxtjs.ts index e7b4ee0b7..90d80449e 100644 --- a/apps/api/src/lib/buildPacks/nuxtjs.ts +++ b/apps/api/src/lib/buildPacks/nuxtjs.ts @@ -1,9 +1,13 @@ 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, @@ -11,7 +15,8 @@ const createDockerfile = async (data, image): Promise => { baseDirectory, secrets, pullmergeRequestId, - buildId + deploymentType, + baseImage } = data; const Dockerfile: Array = []; const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); @@ -36,21 +41,34 @@ 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/lib/buildPacks/react.ts b/apps/api/src/lib/buildPacks/react.ts index 169af33cc..e85704d3f 100644 --- a/apps/api/src/lib/buildPacks/react.ts +++ b/apps/api/src/lib/buildPacks/react.ts @@ -9,7 +9,7 @@ const createDockerfile = async (data, image): Promise => { Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push('WORKDIR /app'); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); - if (baseImage.includes('nginx')) { + if (baseImage?.includes('nginx')) { Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); } Dockerfile.push(`EXPOSE ${port}`); diff --git a/apps/api/src/lib/buildPacks/static.ts b/apps/api/src/lib/buildPacks/static.ts index 67f85b4f4..47fa0dfa4 100644 --- a/apps/api/src/lib/buildPacks/static.ts +++ b/apps/api/src/lib/buildPacks/static.ts @@ -40,7 +40,7 @@ const createDockerfile = async (data, image): Promise => { } else { Dockerfile.push(`COPY .${baseDirectory || ''} ./`); } - if (baseImage.includes('nginx')) { + if (baseImage?.includes('nginx')) { Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); } Dockerfile.push(`EXPOSE ${port}`); diff --git a/apps/api/src/lib/buildPacks/svelte.ts b/apps/api/src/lib/buildPacks/svelte.ts index 4933d10ff..56fc12d7a 100644 --- a/apps/api/src/lib/buildPacks/svelte.ts +++ b/apps/api/src/lib/buildPacks/svelte.ts @@ -9,7 +9,7 @@ const createDockerfile = async (data, image): Promise => { Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); - if (baseImage.includes('nginx')) { + if (baseImage?.includes('nginx')) { Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); } Dockerfile.push(`EXPOSE ${port}`); diff --git a/apps/api/src/lib/buildPacks/vuejs.ts b/apps/api/src/lib/buildPacks/vuejs.ts index 4933d10ff..56fc12d7a 100644 --- a/apps/api/src/lib/buildPacks/vuejs.ts +++ b/apps/api/src/lib/buildPacks/vuejs.ts @@ -9,7 +9,7 @@ const createDockerfile = async (data, image): Promise => { Dockerfile.push('WORKDIR /app'); Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); - if (baseImage.includes('nginx')) { + if (baseImage?.includes('nginx')) { Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); } Dockerfile.push(`EXPOSE ${port}`); diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index b1150ef36..741d33452 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -15,6 +15,9 @@ import { checkContainer, getEngine, removeContainer } from './docker'; import { day } from './dayjs'; import * as serviceFields from './serviceFields' +export const version = '3.1.0'; +export const isDev = process.env.NODE_ENV === 'development'; + const algorithm = 'aes-256-ctr'; const customConfig: Config = { dictionaries: [adjectives, colors, animals], @@ -22,8 +25,6 @@ const customConfig: Config = { separator: ' ', length: 3 }; -export const isDev = process.env.NODE_ENV === 'development'; -export const version = '3.0.3'; export const defaultProxyImage = `coolify-haproxy-alpine:latest`; export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`; diff --git a/apps/api/src/lib/importers/github.ts b/apps/api/src/lib/importers/github.ts index 76aee1398..bac460eca 100644 --- a/apps/api/src/lib/importers/github.ts +++ b/apps/api/src/lib/importers/github.ts @@ -11,7 +11,8 @@ export default async function ({ apiUrl, htmlUrl, branch, - buildId + buildId, + customPort }: { applicationId: string; workdir: string; @@ -21,6 +22,7 @@ export default async function ({ htmlUrl: string; branch: string; buildId: string; + customPort: number; }): Promise { const { default: got } = await import('got') const url = htmlUrl.replace('https://', '').replace('http://', ''); @@ -54,7 +56,7 @@ export default async function ({ applicationId }); await asyncExecShell( - `git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. ` + `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', ''); diff --git a/apps/api/src/lib/importers/gitlab.ts b/apps/api/src/lib/importers/gitlab.ts index d5366303e..b1784b6b9 100644 --- a/apps/api/src/lib/importers/gitlab.ts +++ b/apps/api/src/lib/importers/gitlab.ts @@ -9,7 +9,8 @@ export default async function ({ repository, branch, buildId, - privateSshKey + privateSshKey, + customPort }: { applicationId: string; workdir: string; @@ -19,6 +20,7 @@ export default async function ({ buildId: string; repodir: string; privateSshKey: string; + customPort: number; }): Promise { const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, ''); await saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId }); @@ -32,7 +34,7 @@ export default async function ({ }); await asyncExecShell( - `git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && git lfs pull && cd .. ` + `git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${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', ''); diff --git a/apps/api/src/lib/scheduler.ts b/apps/api/src/lib/scheduler.ts index 929016570..c32226bc9 100644 --- a/apps/api/src/lib/scheduler.ts +++ b/apps/api/src/lib/scheduler.ts @@ -11,11 +11,20 @@ const options: any = { logger: false, workerMessageHandler: async ({ name, message }) => { if (name === 'deployApplication') { - if (message.pending === 0) { - if (!scheduler.workers.has('autoUpdater')) { - await scheduler.stop('deployApplication'); - await scheduler.run('autoUpdater') + if (message.pending === 0 && message.size === 0) { + if (message.caller === 'autoUpdater') { + if (!scheduler.workers.has('autoUpdater')) { + await scheduler.stop('deployApplication'); + await scheduler.run('autoUpdater') + } } + if (message.caller === 'cleanupStorage') { + if (!scheduler.workers.has('cleanupStorage')) { + await scheduler.stop('deployApplication'); + await scheduler.run('cleanupStorage') + } + } + } } }, @@ -25,7 +34,6 @@ const options: any = { }, { name: 'cleanupStorage', - interval: '10m' }, { name: 'checkProxies', diff --git a/apps/api/src/lib/serviceFields.ts b/apps/api/src/lib/serviceFields.ts index 01f628be2..dadbc6713 100644 --- a/apps/api/src/lib/serviceFields.ts +++ b/apps/api/src/lib/serviceFields.ts @@ -173,6 +173,14 @@ export const wordpress = [{ isNumber: false, isBoolean: false, isEncrypted: false +}, +{ + name: 'ftpPassword', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true }] export const ghost = [{ name: 'defaultEmail', diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index 9465d33ca..858bedd5b 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -31,6 +31,40 @@ 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' + } + } + if (buildPack === 'nuxtjs') { + if (deploymentType === 'static') { + publishDirectory = 'dist' + 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 +218,8 @@ export async function saveApplication(request: FastifyRequest, denoMainFile, denoOptions, baseImage, - baseBuildImage + baseBuildImage, + deploymentType } = request.body if (port) port = Number(port); @@ -215,6 +250,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/api/src/routes/api/v1/base/index.ts b/apps/api/src/routes/api/v1/base/index.ts new file mode 100644 index 000000000..37972a1a7 --- /dev/null +++ b/apps/api/src/routes/api/v1/base/index.ts @@ -0,0 +1,19 @@ +import { FastifyPluginAsync } from 'fastify'; +import { errorHandler, version } from '../../../../lib/common'; + +const root: FastifyPluginAsync = async (fastify, opts): Promise => { + fastify.get('/', async (request) => { + try { + return { + version, + whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true', + whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON, + } + } catch ({ status, message }) { + return errorHandler({ status, message }) + } + }); + +}; + +export default root; diff --git a/apps/api/src/routes/api/v1/handlers.ts b/apps/api/src/routes/api/v1/handlers.ts index b998fd4b5..7ba24c461 100644 --- a/apps/api/src/routes/api/v1/handlers.ts +++ b/apps/api/src/routes/api/v1/handlers.ts @@ -258,11 +258,12 @@ export async function getCurrentUser(request: FastifyRequest, fastify) { include: { teams: true, permission: true } }) if (user) { + const permission = user.permission.find(p => p.teamId === request.query.teamId).permission const payload = { ...request.user, teamId: request.query.teamId, - permission: user.permission.find(p => p.teamId === request.query.teamId).permission || null, - isAdmin: user.permission.find(p => p.teamId === request.query.teamId).permission === 'owner' + permission: permission || null, + isAdmin: permission === 'owner' || permission === 'admin' } token = fastify.jwt.sign(payload) diff --git a/apps/api/src/routes/api/v1/services/handlers.ts b/apps/api/src/routes/api/v1/services/handlers.ts index 832e2b630..02943b18f 100644 --- a/apps/api/src/routes/api/v1/services/handlers.ts +++ b/apps/api/src/routes/api/v1/services/handlers.ts @@ -2,9 +2,10 @@ 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, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, supportedServiceTypesAndVersions } from '../../../../lib/common'; +import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, supportedServiceTypesAndVersions, generatePassword, isDev, stopTcpHttpProxy } from '../../../../lib/common'; import { day } from '../../../../lib/dayjs'; import { checkContainer, dockerInstance, getEngine, removeContainer } from '../../../../lib/docker'; +import cuid from 'cuid'; export async function listServices(request: FastifyRequest) { try { @@ -42,7 +43,7 @@ export async function getService(request: FastifyRequest) { const teamId = request.user.teamId; const { id } = request.params; const service = await getServiceFromDB({ id, teamId }); - + if (!service) { throw { status: 404, message: 'Service not found.' } } @@ -237,18 +238,20 @@ export async function checkService(request: FastifyRequest) { try { const { id } = request.params; let { fqdn, exposePort, otherFqdns } = request.body; + if (fqdn) fqdn = fqdn.toLowerCase(); if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase()); if (exposePort) exposePort = Number(exposePort); + let found = await isDomainConfigured({ id, fqdn }); if (found) { - throw `Domain already configured.` + 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, checkOwn: true }); if (found) { - throw "Domain already configured." + throw { status: 500, message: `Domain ${getDomain(ofqdn).replace('www.', '')} is already in use!` } } } } @@ -257,12 +260,12 @@ export async function checkService(request: FastifyRequest) { exposePort = Number(exposePort); if (exposePort < 1024 || exposePort > 65535) { - throw `Exposed Port needs to be between 1024 and 65535.` + throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` } } const publicPort = await getPort({ port: exposePort }); if (publicPort !== exposePort) { - throw `Port ${exposePort} is already in use.` + throw { status: 500, message: `Port ${exposePort} is already in use.` } } } return {} @@ -2414,4 +2417,168 @@ export async function activatePlausibleUsers(request: FastifyRequest, reply: Fas } catch ({ status, message }) { return errorHandler({ status, message }) } -} \ No newline at end of file +} +export async function activateWordpressFtp(request: FastifyRequest, reply: FastifyReply) { + const { id } = request.params + const teamId = request.user.teamId; + + const { ftpEnabled } = request.body; + + const publicPort = await getFreePort(); + let ftpUser = cuid(); + let ftpPassword = generatePassword(); + + const hostkeyDir = isDev ? '/tmp/hostkeys' : '/app/ssl/hostkeys'; + try { + const data = await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpEnabled }, + include: { service: { include: { destinationDocker: true } } } + }); + const { + service: { destinationDockerId, destinationDocker }, + ftpPublicPort, + ftpUser: user, + ftpPassword: savedPassword, + ftpHostKey, + ftpHostKeyPrivate + } = data; + const { network, engine } = destinationDocker; + const host = getEngine(engine); + if (ftpEnabled) { + if (user) ftpUser = user; + if (savedPassword) ftpPassword = decrypt(savedPassword); + + const { stdout: password } = await asyncExecShell( + `echo ${ftpPassword} | openssl passwd -1 -stdin` + ); + if (destinationDockerId) { + try { + await fs.stat(hostkeyDir); + } catch (error) { + await asyncExecShell(`mkdir -p ${hostkeyDir}`); + } + if (!ftpHostKey) { + await asyncExecShell( + `ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519` + ); + const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`); + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKey: encrypt(ftpHostKey) } + }); + } else { + await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`); + } + if (!ftpHostKeyPrivate) { + await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`); + const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`); + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) } + }); + } else { + await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`); + } + + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { + ftpPublicPort: publicPort, + ftpUser: user ? undefined : ftpUser, + ftpPassword: savedPassword ? undefined : encrypt(ftpPassword) + } + }); + + try { + const isRunning = await checkContainer(engine, `${id}-ftp`); + if (isRunning) { + await asyncExecShell( + `DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` + ); + } + } catch (error) { + console.log(error); + // + } + const volumes = [ + `${id}-wordpress-data:/home/${ftpUser}/wordpress`, + `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`, + `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.rsa:/etc/ssh/ssh_host_rsa_key`, + `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.sh:/etc/sftp.d/chmod.sh` + ]; + + const compose: ComposeFile = { + version: '3.8', + services: { + [`${id}-ftp`]: { + image: `atmoz/sftp:alpine`, + command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`, + extra_hosts: ['host.docker.internal:host-gateway'], + container_name: `${id}-ftp`, + volumes, + networks: [network], + depends_on: [], + restart: 'always' + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [`${id}-wordpress-data`]: { + external: true, + name: `${id}-wordpress-data` + } + } + }; + await fs.writeFile( + `${hostkeyDir}/${id}.sh`, + `#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key\nuserdel -f xfs\nchown -R 33:33 /home/${ftpUser}/wordpress/` + ); + await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`); + await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose)); + await asyncExecShell( + `DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` + ); + } + return reply.code(201).send({ + publicPort, + ftpUser, + ftpPassword + }) + } else { + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpPublicPort: null } + }); + try { + await asyncExecShell( + `DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` + ); + } catch (error) { + // + } + await stopTcpHttpProxy(id, destinationDocker, ftpPublicPort); + return { + }; + } + } catch ({ status, message }) { + return errorHandler({ status, message }) + } finally { + try { + await asyncExecShell( + `rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh` + ); + } catch (error) { + console.log(error) + } + + } + +} diff --git a/apps/api/src/routes/api/v1/services/index.ts b/apps/api/src/routes/api/v1/services/index.ts index 2a6d8dff7..be4b7964e 100644 --- a/apps/api/src/routes/api/v1/services/index.ts +++ b/apps/api/src/routes/api/v1/services/index.ts @@ -1,6 +1,7 @@ import { FastifyPluginAsync } from 'fastify'; import { activatePlausibleUsers, + activateWordpressFtp, checkService, deleteService, deleteServiceSecret, @@ -65,6 +66,7 @@ const root: FastifyPluginAsync = async (fastify, opts): Promise => { fastify.post('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply)); fastify.post('/:id/plausibleanalytics/activate', async (request, reply) => await activatePlausibleUsers(request, reply)); + fastify.post('/:id/wordpress/ftp', async (request, reply) => await activateWordpressFtp(request, reply)); }; export default root; diff --git a/apps/api/src/routes/api/v1/sources/handlers.ts b/apps/api/src/routes/api/v1/sources/handlers.ts index d81a27d1e..9bed930d7 100644 --- a/apps/api/src/routes/api/v1/sources/handlers.ts +++ b/apps/api/src/routes/api/v1/sources/handlers.ts @@ -20,10 +20,11 @@ export async function listSources(request: FastifyRequest) { export async function saveSource(request, reply) { try { const { id } = request.params - const { name, htmlUrl, apiUrl } = request.body + let { name, htmlUrl, apiUrl, customPort } = request.body + if (customPort) customPort = Number(customPort) await prisma.gitSource.update({ where: { id }, - data: { name, htmlUrl, apiUrl } + data: { name, htmlUrl, apiUrl, customPort } }); return reply.code(201).send() } catch ({ status, message }) { @@ -45,7 +46,8 @@ export async function getSource(request: FastifyRequest) { type: null, htmlUrl: null, apiUrl: null, - organization: null + organization: null, + customPort: 22, }, settings } @@ -58,7 +60,7 @@ export async function getSource(request: FastifyRequest) { if (!source) { throw { status: 404, message: 'Source not found.' } } - + if (source?.githubApp?.clientSecret) source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret); if (source?.githubApp?.webhookSecret) @@ -97,9 +99,12 @@ export async function deleteSource(request) { } export async function saveGitHubSource(request: FastifyRequest, reply: FastifyReply) { try { - const { id } = request.params - const { name, type, htmlUrl, apiUrl, organization } = request.body const { teamId } = request.user + + const { id } = request.params + let { name, type, htmlUrl, apiUrl, organization, customPort } = request.body + + if (customPort) customPort = Number(customPort) if (id === 'new') { const newId = cuid() await prisma.gitSource.create({ @@ -109,6 +114,7 @@ export async function saveGitHubSource(request: FastifyRequest, reply: FastifyRe htmlUrl, apiUrl, organization, + customPort, type: 'github', teams: { connect: { id: teamId } } } @@ -126,15 +132,16 @@ export async function saveGitLabSource(request: FastifyRequest, reply: FastifyRe try { const { id } = request.params const { teamId } = request.user - let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName } = + let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort } = request.body - oauthId = Number(oauthId); + if (oauthId) oauthId = Number(oauthId); + if (customPort) customPort = Number(customPort) const encryptedAppSecret = encrypt(appSecret); if (id === 'new') { const newId = cuid() - await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, teams: { connect: { id: teamId } } } }); + await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, teams: { connect: { id: teamId } } } }); await prisma.gitlabApp.create({ data: { teams: { connect: { id: teamId } }, @@ -150,7 +157,7 @@ export async function saveGitLabSource(request: FastifyRequest, reply: FastifyRe id: newId } } else { - await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name } }); + await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort } }); await prisma.gitlabApp.update({ where: { id }, data: { diff --git a/apps/ui/src/app.d.ts b/apps/ui/src/app.d.ts index c743bfaf6..ed5f4e197 100644 --- a/apps/ui/src/app.d.ts +++ b/apps/ui/src/app.d.ts @@ -19,3 +19,6 @@ declare namespace App { privatePort: string; } } + +declare const GITPOD_WORKSPACE_URL: string + \ No newline at end of file diff --git a/apps/ui/src/lib/api.ts b/apps/ui/src/lib/api.ts index 2244d79d3..84c4279a4 100644 --- a/apps/ui/src/lib/api.ts +++ b/apps/ui/src/lib/api.ts @@ -1,8 +1,12 @@ import { browser, dev } from '$app/env'; import Cookies from 'js-cookie'; -import { toast } from '@zerodevx/svelte-toast'; export function getAPIUrl() { + if (GITPOD_WORKSPACE_URL) { + const {href} = new URL(GITPOD_WORKSPACE_URL) + const newURL = href.replace('https://','https://3001-').replace(/\/$/,'') + return newURL + } return dev ? 'http://localhost:3001' : 'http://localhost:3000'; } async function send({ @@ -52,7 +56,7 @@ async function send({ } if (dev && !path.startsWith('https://')) { - path = `http://localhost:3001${path}`; + path = `${getAPIUrl()}${path}`; } const response = await fetch(`${path}`, opts); @@ -74,7 +78,7 @@ async function send({ return {}; } if (!response.ok) { - if (response.status === 401 && !path.startsWith('https://api.github')) { + if (response.status === 401 && !path.startsWith('https://api.github') && !path.includes('/v4/user')) { Cookies.remove('token'); } diff --git a/apps/ui/src/lib/common.ts b/apps/ui/src/lib/common.ts index 288dab986..4b0ae7fb4 100644 --- a/apps/ui/src/lib/common.ts +++ b/apps/ui/src/lib/common.ts @@ -4,6 +4,10 @@ export const asyncSleep = (delay: number) => export function errorNotification(error: any): void { if (error.message) { + if (error.message === 'Cannot read properties of undefined (reading \'postMessage\')') { + toast.push('Currently there is background process in progress. Please try again later.'); + return; + } toast.push(error.message); } else { toast.push('Ooops, something is not okay, are you okay?'); diff --git a/apps/ui/src/lib/locales/en.json b/apps/ui/src/lib/locales/en.json index 2805130d2..cab9ecc80 100644 --- a/apps/ui/src/lib/locales/en.json +++ b/apps/ui/src/lib/locales/en.json @@ -180,7 +180,7 @@ "domain_already_in_use": "Domain {{domain}} is already used.", "dns_not_set_error": "DNS not set correctly or propogated for {{domain}}.

Please check your DNS settings.", "domain_required": "Domain is required.", - "settings_saved": "Settings saved.", + "settings_saved": "Configuration saved.", "dns_not_set_partial_error": "DNS not set", "domain_not_valid": "Could not resolve domain or it's not pointing to the server IP address.

Please check your DNS configuration and try again.", "git_source": "Git Source", diff --git a/apps/ui/src/lib/store.ts b/apps/ui/src/lib/store.ts index f6528a261..668477fa8 100644 --- a/apps/ui/src/lib/store.ts +++ b/apps/ui/src/lib/store.ts @@ -1,8 +1,7 @@ -import { browser } from '$app/env'; import { writable, readable, type Writable, type Readable } from 'svelte/store'; -// import { version as currentVersion } from '../../package.json'; + interface AppSession { - version: string + version: string | null, userId: string | null, teamId: string | null, permission: string, @@ -18,7 +17,7 @@ interface AppSession { } export const loginEmail: Writable = writable() export const appSession: Writable = writable({ - version: '3.0.3', + version: null, userId: null, teamId: null, permission: 'read', diff --git a/apps/ui/src/lib/templates.ts b/apps/ui/src/lib/templates.ts index 7b81dbadd..b2e27ad1c 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') { @@ -94,7 +95,8 @@ export function findBuildPack(pack: string, packageManager = 'npm') { ...metaData, ...defaultBuildAndDeploy(packageManager), publishDirectory: null, - port: 3000 + port: 3000, + deploymentType: 'node' }; } if (pack === 'preact') { diff --git a/apps/ui/src/routes/__layout.svelte b/apps/ui/src/routes/__layout.svelte index 68b624cae..a6932eae0 100644 --- a/apps/ui/src/routes/__layout.svelte +++ b/apps/ui/src/routes/__layout.svelte @@ -1,12 +1,14 @@