diff --git a/README.md b/README.md index 5360d1114..aba19e517 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,64 @@ -# About -https://andrasbacsai.com/farewell-netlify-and-heroku-after-3-days-of-coding +# Coolify -# Features -- Deploy your Node.js, static sites, PHP or any custom application (with custom Dockerfile) just by pushing code to git. -- Hassle-free installation and upgrade process. -- One-click MongoDB, MySQL, PostgreSQL, CouchDB deployments! +An open-source, hassle-free, self-hostable Heroku & Netlify alternative. -# Upcoming features -- Backups & monitoring. -- User analytics with privacy in mind. -- And much more (see [Roadmap](https://github.com/coollabsio/coolify/projects/1)). +## Demo +[Small video](https://cdn.coollabs.io/assets/coolify/video/coolify.webm) -# FAQ -Q: What is a buildpack? - -A: It defines your application's final form. -`Static` means that it will be hosted as a static site. -`NodeJs` means that it will be started as a node application. - -# Screenshots - -[Login](https://coolify.io/login.jpg) - -[Applications](https://coolify.io/applications.jpg) - -[Databases](https://coolify.io/databases.jpg) - -[Configuration](https://coolify.io/configuration.jpg) - -[Settings](https://coolify.io/settings.jpg) - -[Logs](https://coolify.io/logs.jpg) - -# Getting Started - -Automatically: `/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"` - -Manually: -### Requirements before installation -- [Docker](https://docs.docker.com/engine/install/) version 20+ -- Docker in [swarm mode enabled](https://docs.docker.com/engine/reference/commandline/swarm_init/) (should be set manually before installation) -- A [MongoDB](https://docs.mongodb.com/manual/installation/) instance. - - We have a [simple installation](https://github.com/coollabsio/infrastructure/tree/main/mongo) if you need one -- A configured DNS entry (see `.env.template`) -- [Github App](https://docs.github.com/en/developers/apps/creating-a-github-app) - - - GitHub App name: could be anything weird - - Homepage URL: https://yourdomain - - Identifying and authorizing users: - - Callback URL: https://yourdomain/api/v1/login/github/app - - Request user authorization (OAuth) during installation -> Check! - - Webhook: - - Active -> Check! - - Webhook URL: https://yourdomain/api/v1/webhooks/deploy - - Webhook Secret: it should be super secret - - Repository permissions: - - Contents: Read-only - - Metadata: Read-only - User permissions: - - Email: Read-only +## Installation - Subscribe to events: - - Push -> Check! +Installation is automated with the following command: -### Installation -- Clone this repository: `git clone git@github.com:coollabsio/coolify.git` -- Set `.env` (see `.env.template`) -- Installation: `bash install.sh all` +```bash +/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)" +``` -## Manual updating process (You probably never need to do this!) -### Update everything (proxy+coolify) -- `bash install.sh all` + +## Features +You can deploy any of the following applications, databases and services easily. -### Update coolify only -- `bash install.sh coolify` +(constantly growing lists) -### Update proxy only -- `bash install.sh proxy` +### Applications +With Github integration + +- Static sites +- NodeJS +- VueJS +- NuxtJS +- React/Preact +- NextJS +- Gatsby +- Svelte +- PHP +- Rust +- or any custom dockerfile + +### Databases +- MongoDB +- MySQL +- PostgreSQL +- CouchDB + +### Services +- [Plausible Analytics](https://plausible.io) + + +## Support -# Contact - Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai) - Telegram: [@andrasbacsai](https://t.me/andrasbacsai) - Email: [andras@coollabs.io](mailto:andras@coollabs.io) +- Discord: [Invitation](https://discord.com/invite/bvS3WhR) + +## Roadmap +[See the Roadmap here](https://github.com/coollabsio/coolify/projects/1) + +## License -# License This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Please see the [LICENSE](/LICENSE) file in our repository for the full text. + + \ No newline at end of file diff --git a/api/buildPacks/custom/index.js b/api/buildPacks/docker/index.js similarity index 100% rename from api/buildPacks/custom/index.js rename to api/buildPacks/docker/index.js diff --git a/api/buildPacks/gatsby/index.js b/api/buildPacks/gatsby/index.js new file mode 100644 index 000000000..46f1b26a7 --- /dev/null +++ b/api/buildPacks/gatsby/index.js @@ -0,0 +1,25 @@ +const fs = require('fs').promises +const { buildImage } = require('../helpers') +const { streamEvents, docker } = require('../../libs/docker') + +// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1', +const publishStaticDocker = (configuration) => { + return [ + 'FROM nginx:stable-alpine', + 'COPY nginx.conf /etc/nginx/nginx.conf', + 'WORKDIR /usr/share/nginx/html', + `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`, + 'EXPOSE 80', + 'CMD ["nginx", "-g", "daemon off;"]' + ].join('\n') +} + +module.exports = async function (configuration) { + await buildImage(configuration, true) + await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishStaticDocker(configuration)) + const stream = await docker.engine.buildImage( + { src: ['.'], context: configuration.general.workdir }, + { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } + ) + await streamEvents(stream, configuration) +} diff --git a/api/buildPacks/helpers.js b/api/buildPacks/helpers.js index 8918f4147..d6acfc61b 100644 --- a/api/buildPacks/helpers.js +++ b/api/buildPacks/helpers.js @@ -10,11 +10,11 @@ const buildImageNodeDocker = (configuration) => { `RUN ${configuration.build.command.build}` ].join('\n') } -async function buildImage (configuration) { +async function buildImage (configuration, cacheBuild) { await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, buildImageNodeDocker(configuration)) const stream = await docker.engine.buildImage( { src: ['.'], context: configuration.general.workdir }, - { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } + { t: `${configuration.build.container.name}:${cacheBuild ? `${configuration.build.container.tag}-cache` : configuration.build.container.tag}` } ) await streamEvents(stream, configuration) } diff --git a/api/buildPacks/index.js b/api/buildPacks/index.js index a4a8876e2..9ca02a9a5 100644 --- a/api/buildPacks/index.js +++ b/api/buildPacks/index.js @@ -1,7 +1,13 @@ -const static = require('./static') +const Static = require('./static') +const react = require('./react') +const nextjs = require('./nextjs') +const nuxtjs = require('./nuxtjs') +const gatsby = require('./gatsby') +const vuejs = require('./vuejs') +const svelte = require('./svelte') const nodejs = require('./nodejs') const php = require('./php') -const custom = require('./custom') +const docker = require('./docker') const rust = require('./rust') -module.exports = { static, nodejs, php, custom, rust } +module.exports = { static: Static, nodejs, php, docker, rust, react, vuejs, nextjs, nuxtjs, svelte, gatsby } diff --git a/api/buildPacks/nextjs/index.js b/api/buildPacks/nextjs/index.js new file mode 100644 index 000000000..2449875d7 --- /dev/null +++ b/api/buildPacks/nextjs/index.js @@ -0,0 +1,28 @@ +const fs = require('fs').promises +const { buildImage } = require('../helpers') +const { streamEvents, docker } = require('../../libs/docker') +// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`, +const publishNodejsDocker = (configuration) => { + return [ + 'FROM node:lts', + 'WORKDIR /usr/src/app', + configuration.build.command.build + ? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./` + : ` + COPY ${configuration.build.directory}/package*.json ./ + RUN ${configuration.build.command.installation} + COPY ./${configuration.build.directory} ./`, + `EXPOSE ${configuration.publish.port}`, + 'CMD [ "yarn", "start" ]' + ].join('\n') +} + +module.exports = async function (configuration) { + await buildImage(configuration) + await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishNodejsDocker(configuration)) + const stream = await docker.engine.buildImage( + { src: ['.'], context: configuration.general.workdir }, + { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } + ) + await streamEvents(stream, configuration) +} diff --git a/api/buildPacks/nuxtjs/index.js b/api/buildPacks/nuxtjs/index.js new file mode 100644 index 000000000..2449875d7 --- /dev/null +++ b/api/buildPacks/nuxtjs/index.js @@ -0,0 +1,28 @@ +const fs = require('fs').promises +const { buildImage } = require('../helpers') +const { streamEvents, docker } = require('../../libs/docker') +// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`, +const publishNodejsDocker = (configuration) => { + return [ + 'FROM node:lts', + 'WORKDIR /usr/src/app', + configuration.build.command.build + ? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./` + : ` + COPY ${configuration.build.directory}/package*.json ./ + RUN ${configuration.build.command.installation} + COPY ./${configuration.build.directory} ./`, + `EXPOSE ${configuration.publish.port}`, + 'CMD [ "yarn", "start" ]' + ].join('\n') +} + +module.exports = async function (configuration) { + await buildImage(configuration) + await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishNodejsDocker(configuration)) + const stream = await docker.engine.buildImage( + { src: ['.'], context: configuration.general.workdir }, + { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } + ) + await streamEvents(stream, configuration) +} diff --git a/api/buildPacks/react/index.js b/api/buildPacks/react/index.js new file mode 100644 index 000000000..46f1b26a7 --- /dev/null +++ b/api/buildPacks/react/index.js @@ -0,0 +1,25 @@ +const fs = require('fs').promises +const { buildImage } = require('../helpers') +const { streamEvents, docker } = require('../../libs/docker') + +// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1', +const publishStaticDocker = (configuration) => { + return [ + 'FROM nginx:stable-alpine', + 'COPY nginx.conf /etc/nginx/nginx.conf', + 'WORKDIR /usr/share/nginx/html', + `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`, + 'EXPOSE 80', + 'CMD ["nginx", "-g", "daemon off;"]' + ].join('\n') +} + +module.exports = async function (configuration) { + await buildImage(configuration, true) + await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishStaticDocker(configuration)) + const stream = await docker.engine.buildImage( + { src: ['.'], context: configuration.general.workdir }, + { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } + ) + await streamEvents(stream, configuration) +} diff --git a/api/buildPacks/static/index.js b/api/buildPacks/static/index.js index 36ef30b02..65a382d79 100644 --- a/api/buildPacks/static/index.js +++ b/api/buildPacks/static/index.js @@ -9,7 +9,7 @@ const publishStaticDocker = (configuration) => { 'COPY nginx.conf /etc/nginx/nginx.conf', 'WORKDIR /usr/share/nginx/html', configuration.build.command.build - ? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./` + ? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./` : `COPY ./${configuration.build.directory} ./`, 'EXPOSE 80', 'CMD ["nginx", "-g", "daemon off;"]' @@ -17,9 +17,8 @@ const publishStaticDocker = (configuration) => { } module.exports = async function (configuration) { - if (configuration.build.command.build) await buildImage(configuration) + if (configuration.build.command.build) await buildImage(configuration, true) await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishStaticDocker(configuration)) - const stream = await docker.engine.buildImage( { src: ['.'], context: configuration.general.workdir }, { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } diff --git a/api/buildPacks/svelte/index.js b/api/buildPacks/svelte/index.js new file mode 100644 index 000000000..46f1b26a7 --- /dev/null +++ b/api/buildPacks/svelte/index.js @@ -0,0 +1,25 @@ +const fs = require('fs').promises +const { buildImage } = require('../helpers') +const { streamEvents, docker } = require('../../libs/docker') + +// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1', +const publishStaticDocker = (configuration) => { + return [ + 'FROM nginx:stable-alpine', + 'COPY nginx.conf /etc/nginx/nginx.conf', + 'WORKDIR /usr/share/nginx/html', + `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`, + 'EXPOSE 80', + 'CMD ["nginx", "-g", "daemon off;"]' + ].join('\n') +} + +module.exports = async function (configuration) { + await buildImage(configuration, true) + await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishStaticDocker(configuration)) + const stream = await docker.engine.buildImage( + { src: ['.'], context: configuration.general.workdir }, + { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } + ) + await streamEvents(stream, configuration) +} diff --git a/api/buildPacks/vuejs/index.js b/api/buildPacks/vuejs/index.js new file mode 100644 index 000000000..46f1b26a7 --- /dev/null +++ b/api/buildPacks/vuejs/index.js @@ -0,0 +1,25 @@ +const fs = require('fs').promises +const { buildImage } = require('../helpers') +const { streamEvents, docker } = require('../../libs/docker') + +// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1', +const publishStaticDocker = (configuration) => { + return [ + 'FROM nginx:stable-alpine', + 'COPY nginx.conf /etc/nginx/nginx.conf', + 'WORKDIR /usr/share/nginx/html', + `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`, + 'EXPOSE 80', + 'CMD ["nginx", "-g", "daemon off;"]' + ].join('\n') +} + +module.exports = async function (configuration) { + await buildImage(configuration, true) + await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishStaticDocker(configuration)) + const stream = await docker.engine.buildImage( + { src: ['.'], context: configuration.general.workdir }, + { t: `${configuration.build.container.name}:${configuration.build.container.tag}` } + ) + await streamEvents(stream, configuration) +} diff --git a/api/libs/applications/cleanup/index.js b/api/libs/applications/cleanup/index.js index 9e78a7cae..71b5fca47 100644 --- a/api/libs/applications/cleanup/index.js +++ b/api/libs/applications/cleanup/index.js @@ -2,11 +2,16 @@ const { docker } = require('../../docker') const { execShellAsync } = require('../../common') const Deployment = require('../../../models/Deployment') -async function purgeImagesContainers (configuration) { +async function purgeImagesContainers (configuration, deleteAll = false) { const { name, tag } = configuration.build.container await execShellAsync('docker container prune -f') - const IDsToDelete = (await execShellAsync(`docker images ls --filter=reference='${name}' --filter=before='${name}:${tag}' --format '{{json .ID }}'`)).trim().replace(/"/g, '').split('\n') - if (IDsToDelete.length !== 0) for (const id of IDsToDelete) await execShellAsync(`docker rmi -f ${id}`) + if (deleteAll) { + const IDsToDelete = (await execShellAsync(`docker images ls --filter=reference='${name}' --format '{{json .ID }}'`)).trim().replace(/"/g, '').split('\n') + if (IDsToDelete.length > 0) await execShellAsync(`docker rmi -f ${IDsToDelete.toString().replace(',', ' ')}`) + } else { + const IDsToDelete = (await execShellAsync(`docker images ls --filter=reference='${name}' --filter=before='${name}:${tag}' --format '{{json .ID }}'`)).trim().replace(/"/g, '').split('\n') + if (IDsToDelete.length > 1) await execShellAsync(`docker rmi -f ${IDsToDelete.toString().replace(',', ' ')}`) + } await execShellAsync('docker image prune -f') } diff --git a/api/libs/applications/configuration.js b/api/libs/applications/configuration.js index 134599a76..74024d847 100644 --- a/api/libs/applications/configuration.js +++ b/api/libs/applications/configuration.js @@ -23,14 +23,10 @@ function setDefaultConfiguration (configuration) { if (!configuration.publish.path) configuration.publish.path = '/' if (!configuration.publish.port) { - if (configuration.build.pack === 'php') { - configuration.publish.port = 80 - } else if (configuration.build.pack === 'static') { - configuration.publish.port = 80 - } else if (configuration.build.pack === 'nodejs') { - configuration.publish.port = 3000 - } else if (configuration.build.pack === 'rust') { + if (configuration.build.pack === 'nodejs' && configuration.build.pack === 'vuejs' && configuration.build.pack === 'nuxtjs' && configuration.build.pack === 'rust' && configuration.build.pack === 'nextjs') { configuration.publish.port = 3000 + } else { + configuration.publish.port = 80 } } diff --git a/api/libs/applications/deploy/copyFiles.js b/api/libs/applications/deploy/copyFiles.js index 9d9c8212a..41cec0e5d 100644 --- a/api/libs/applications/deploy/copyFiles.js +++ b/api/libs/applications/deploy/copyFiles.js @@ -1,5 +1,6 @@ const fs = require('fs').promises module.exports = async function (configuration) { + const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby'] try { // TODO: Write full .dockerignore for all deployments!! if (configuration.build.pack === 'php') { @@ -12,7 +13,7 @@ module.exports = async function (configuration) { `) } // await fs.writeFile(`${configuration.general.workdir}/.dockerignore`, 'node_modules') - if (configuration.build.pack === 'static') { + if (staticDeployments.includes(configuration.build.pack)) { await fs.writeFile( `${configuration.general.workdir}/nginx.conf`, `user nginx; diff --git a/api/libs/applications/github/cloneRepository.js b/api/libs/applications/github/cloneRepository.js index c670ea592..bf0448c52 100644 --- a/api/libs/applications/github/cloneRepository.js +++ b/api/libs/applications/github/cloneRepository.js @@ -1,38 +1,44 @@ const jwt = require('jsonwebtoken') const axios = require('axios') -const { execShellAsync, cleanupTmp } = require('../../common') +const { execShellAsync } = require('../../common') module.exports = async function (configuration) { - const { workdir } = configuration.general - const { organization, name, branch } = configuration.repository - const github = configuration.github - - const githubPrivateKey = process.env.GITHUB_APP_PRIVATE_KEY.replace(/\\n/g, '\n').replace(/"/g, '') - - const payload = { - iat: Math.round(new Date().getTime() / 1000), - exp: Math.round(new Date().getTime() / 1000 + 60), - iss: parseInt(github.app.id) - } - - const jwtToken = jwt.sign(payload, githubPrivateKey, { - algorithm: 'RS256' - }) - const accessToken = await axios({ - method: 'POST', - url: `https://api.github.com/app/installations/${github.installation.id}/access_tokens`, - data: {}, - headers: { - Authorization: 'Bearer ' + jwtToken, - Accept: 'application/vnd.github.machine-man-preview+json' + try { + const { workdir } = configuration.general + const { organization, name, branch } = configuration.repository + const github = configuration.github + if (!github.installation.id || !github.app.id) { + throw new Error('Github installation ID is invalid.') } - }) - await execShellAsync( - `mkdir -p ${workdir} && git clone -q -b ${branch} https://x-access-token:${accessToken.data.token}@github.com/${organization}/${name}.git ${workdir}/` - ) - configuration.build.container.tag = ( - await execShellAsync(`cd ${configuration.general.workdir}/ && git rev-parse HEAD`) - ) - .replace('\n', '') - .slice(0, 7) + const githubPrivateKey = process.env.GITHUB_APP_PRIVATE_KEY.replace(/\\n/g, '\n').replace(/"/g, '') + + const payload = { + iat: Math.round(new Date().getTime() / 1000), + exp: Math.round(new Date().getTime() / 1000 + 60), + iss: parseInt(github.app.id) + } + + const jwtToken = jwt.sign(payload, githubPrivateKey, { + algorithm: 'RS256' + }) + const accessToken = await axios({ + method: 'POST', + url: `https://api.github.com/app/installations/${github.installation.id}/access_tokens`, + data: {}, + headers: { + Authorization: 'Bearer ' + jwtToken, + Accept: 'application/vnd.github.machine-man-preview+json' + } + }) + await execShellAsync( + `mkdir -p ${workdir} && git clone -q -b ${branch} https://x-access-token:${accessToken.data.token}@github.com/${organization}/${name}.git ${workdir}/` + ) + configuration.build.container.tag = ( + await execShellAsync(`cd ${configuration.general.workdir}/ && git rev-parse HEAD`) + ) + .replace('\n', '') + .slice(0, 7) + } catch (error) { + throw new Error(error) + } } diff --git a/api/routes/v1/application/remove.js b/api/routes/v1/application/remove.js index 0130825c3..90d7d2122 100644 --- a/api/routes/v1/application/remove.js +++ b/api/routes/v1/application/remove.js @@ -1,7 +1,8 @@ const { docker } = require('../../../libs/docker') -const { execShellAsync } = require('../../../libs/common') +const { execShellAsync, delay } = require('../../../libs/common') const ApplicationLog = require('../../../models/Logs/Application') const Deployment = require('../../../models/Deployment') +const { purgeImagesContainers } = require('../../../libs/applications/cleanup') module.exports = async function (fastify) { fastify.post('/', async (request, reply) => { @@ -25,6 +26,8 @@ module.exports = async function (fastify) { } await execShellAsync(`docker stack rm ${found.build.container.name}`) reply.code(200).send({ organization, name, branch }) + await delay(10000) + await purgeImagesContainers(found, true) } else { reply.code(500).send({ message: 'Nothing to do.' }) } diff --git a/api/routes/v1/dashboard/index.js b/api/routes/v1/dashboard/index.js index acc5cf6ab..7cb783031 100644 --- a/api/routes/v1/dashboard/index.js +++ b/api/routes/v1/dashboard/index.js @@ -1,27 +1,10 @@ const { docker } = require('../../../libs/docker') -const Deployment = require('../../../models/Deployment') const ServerLog = require('../../../models/Logs/Server') const { saveServerLog } = require('../../../libs/logging') module.exports = async function (fastify) { fastify.get('/', async (request, reply) => { try { - const latestDeployments = await Deployment.aggregate([ - { - $sort: { createdAt: -1 } - }, - { - $group: - { - _id: { - repoId: '$repoId', - branch: '$branch' - }, - createdAt: { $last: '$createdAt' }, - progress: { $first: '$progress' } - } - } - ]) const serverLogs = await ServerLog.find() const dockerServices = await docker.engine.listServices() let applications = dockerServices.filter(r => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application' && r.Spec.Labels.configuration) @@ -29,25 +12,31 @@ module.exports = async function (fastify) { let services = dockerServices.filter(r => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'service' && r.Spec.Labels.configuration) applications = applications.map(r => { if (JSON.parse(r.Spec.Labels.configuration)) { - const configuration = JSON.parse(r.Spec.Labels.configuration) - const status = latestDeployments.find(l => configuration.repository.id === l._id.repoId && configuration.repository.branch === l._id.branch) - if (status && status.progress) r.progress = status.progress - r.Spec.Labels.configuration = configuration - return r + return { + configuration: JSON.parse(r.Spec.Labels.configuration), + UpdatedAt: r.UpdatedAt + } } return {} }) databases = databases.map(r => { - const configuration = r.Spec.Labels.configuration ? JSON.parse(r.Spec.Labels.configuration) : null - r.Spec.Labels.configuration = configuration - return r + if (JSON.parse(r.Spec.Labels.configuration)) { + return { + configuration: JSON.parse(r.Spec.Labels.configuration) + } + } + return {} }) services = services.map(r => { - const configuration = r.Spec.Labels.configuration ? JSON.parse(r.Spec.Labels.configuration) : null - r.Spec.Labels.configuration = configuration - return r + if (JSON.parse(r.Spec.Labels.configuration)) { + return { + serviceName: r.Spec.Labels.serviceName, + configuration: JSON.parse(r.Spec.Labels.configuration) + } + } + return {} }) - applications = [...new Map(applications.map(item => [item.Spec.Labels.configuration.publish.domain + item.Spec.Labels.configuration.publish.path, item])).values()] + applications = [...new Map(applications.map(item => [item.configuration.publish.domain + item.configuration.publish.path, item])).values()] return { serverLogs, applications: { diff --git a/api/routes/v1/databases/index.js b/api/routes/v1/databases/index.js index da0f34506..01c0a212c 100644 --- a/api/routes/v1/databases/index.js +++ b/api/routes/v1/databases/index.js @@ -66,7 +66,6 @@ module.exports = async function (fastify) { if (!defaultDatabaseName) defaultDatabaseName = nickname reply.code(201).send({ message: 'Deploying.' }) - // TODO: Persistent volume, custom inputs const deployId = cuid() const configuration = { general: { diff --git a/api/routes/v1/webhooks/deploy.js b/api/routes/v1/webhooks/deploy.js index 358991738..d3bcdb982 100644 --- a/api/routes/v1/webhooks/deploy.js +++ b/api/routes/v1/webhooks/deploy.js @@ -12,7 +12,6 @@ const cloneRepository = require('../../../libs/applications/github/cloneReposito const { purgeImagesContainers } = require('../../../libs/applications/cleanup') module.exports = async function (fastify) { - // TODO: Add this to fastify plugin const postSchema = { body: { type: 'object', diff --git a/api/server.js b/api/server.js index 12503dada..c1254faef 100644 --- a/api/server.js +++ b/api/server.js @@ -1,12 +1,11 @@ require('dotenv').config() const fs = require('fs') const util = require('util') -const axios = require('axios') const mongoose = require('mongoose') const path = require('path') const { saveServerLog } = require('./libs/logging') const { execShellAsync } = require('./libs/common') -const { purgeImagesContainers, cleanupStuckedDeploymentsInDB } = require('./libs/applications/cleanup') +const { cleanupStuckedDeploymentsInDB } = require('./libs/applications/cleanup') const fastify = require('fastify')({ trustProxy: true, logger: { diff --git a/index.html b/index.html index 3b3cf82f2..a9c71b143 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ - coolify: Heroku & Netlify alternative + Coolify diff --git a/package.json b/package.json index 596c64346..11363802a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source, hassle-free, self-hostable Heroku & Netlify alternative.", - "version": "1.0.10", + "version": "1.0.11", "license": "AGPL-3.0", "scripts": { "lint": "standard", diff --git a/src/components/Application/Configuration/ActiveTab/BuildStep.svelte b/src/components/Application/Configuration/ActiveTab/BuildStep.svelte deleted file mode 100644 index a1cc90034..000000000 --- a/src/components/Application/Configuration/ActiveTab/BuildStep.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - -
- - - - - -
diff --git a/src/components/Application/Configuration/ActiveTab/General.svelte b/src/components/Application/Configuration/ActiveTab/General.svelte index 22b62aaf2..fa2d894e8 100644 --- a/src/components/Application/Configuration/ActiveTab/General.svelte +++ b/src/components/Application/Configuration/ActiveTab/General.svelte @@ -1,56 +1,213 @@ + +
- - +
Build Packs
+
+
+ Static +
+
+ NodeJS +
+
+ VueJS +
+
+ NuxtJS +
+
+ React/Preact +
+
+ NextJS +
+
+ Gatsby +
+
+ Svelte +
+
+ PHP +
+
+ Rust +
+
+ Docker +
+
+
General settings
- {#if showPorts.includes($application.build.pack)} - + - {/if} -
+
+
+ Commands +
+
+
+
+ + + + + +
+
+
diff --git a/src/components/Application/Configuration/ActiveTab/Secrets.svelte b/src/components/Application/Configuration/ActiveTab/Secrets.svelte index 1e3d88ef4..5e267f8f3 100644 --- a/src/components/Application/Configuration/ActiveTab/Secrets.svelte +++ b/src/components/Application/Configuration/ActiveTab/Secrets.svelte @@ -36,40 +36,43 @@ ]; } - -
+
Secrets
+
New Secret
-
- - - +
+ + +
{#if $application.publish.secrets.length > 0}
{#each $application.publish.secrets as s} -
+
- + +
{/each}
diff --git a/src/components/Application/Configuration/Branches.svelte b/src/components/Application/Configuration/Branches.svelte index 8a9c98b7a..3843d68a5 100644 --- a/src/components/Application/Configuration/Branches.svelte +++ b/src/components/Application/Configuration/Branches.svelte @@ -1,11 +1,10 @@ -{#if !$isActive("/application/new")} +{#if !$activePage.new}
+ {$application.publish.domain + ? `${$application.publish.domain}${ + $application.publish.path !== "/" ? $application.publish.path : "" + }` + : "example.com"} {$application.publish.domain - ? `${$application.publish.domain}${$application.publish.path !== '/' ? $application.publish.path : ''}` - : ""} + + + +
-{:else if $isActive("/application/new")} +{:else if $activePage.new}
- {#if $application.repository.organization !== "new"} + {#if $application.repository.organization} {/if} diff --git a/src/components/Application/Configuration/Repositories.svelte b/src/components/Application/Configuration/Repositories.svelte index f3adef7e3..9f9c73db2 100644 --- a/src/components/Application/Configuration/Repositories.svelte +++ b/src/components/Application/Configuration/Repositories.svelte @@ -1,43 +1,42 @@ -
- {#if repositories.length !== 0} +
+ {#if $githubRepositories.length !== 0}
+
+ + + - - - - -
- - - - - - - - -
- -
+ +
+ +
{/await} diff --git a/src/pages/dashboard/_layout.svelte b/src/pages/dashboard/_layout.svelte index dd1a2819a..e64ba7223 100644 --- a/src/pages/dashboard/_layout.svelte +++ b/src/pages/dashboard/_layout.svelte @@ -1,8 +1,6 @@ - + import { params, goto, isActive, redirect } from "@roxi/routify"; + import { fetch } from "@store"; + import { toast } from "@zerodevx/svelte-toast"; + import Tooltip from "../../../components/Tooltip/Tooltip.svelte"; - -
- -
- \ No newline at end of file +
+ +
diff --git a/src/pages/service/[name]/configuration.svelte b/src/pages/service/[name]/configuration.svelte index b9f313a2c..c68702106 100644 --- a/src/pages/service/[name]/configuration.svelte +++ b/src/pages/service/[name]/configuration.svelte @@ -61,7 +61,7 @@
{#if name === "plausible"} - + {/if}
diff --git a/src/pages/service/new/[type]/_layout.svelte b/src/pages/service/new/[type]/_layout.svelte index 58c456b7d..21c220482 100644 --- a/src/pages/service/new/[type]/_layout.svelte +++ b/src/pages/service/new/[type]/_layout.svelte @@ -1,8 +1,7 @@ diff --git a/src/pages/settings/index.svelte b/src/pages/settings/index.svelte index 914c4e9da..bcd707d54 100644 --- a/src/pages/settings/index.svelte +++ b/src/pages/settings/index.svelte @@ -38,10 +38,10 @@
-
General
-
+
General
+
-
    +
    • diff --git a/src/store.js b/src/store.js index ac722fbea..c73b01977 100644 --- a/src/store.js +++ b/src/store.js @@ -43,7 +43,8 @@ export const fetch = writable( if (body) { config.body = JSON.stringify(body) } - const response = await waitAtLeast(350, window.fetch(url, config)) + // const response = await waitAtLeast(350, window.fetch(url, config)) + const response = await window.fetch(url, config) if (response.status >= 200 && response.status <= 299) { if (response.headers.get('content-type').match(/application\/json/)) { return await response.json() @@ -77,10 +78,17 @@ export const fetch = writable( } } ) +export const activePage = writable({ + application: null, + new: false, + mainmenu: null +}) export const session = writable(sessionStore) export const loggedIn = derived(session, ($session) => { return $session.token }) +export const githubRepositories = writable([]) +export const githubInstallations = writable({}) export const savedBranch = writable() export const dateOptions = readable({ @@ -93,7 +101,7 @@ export const dateOptions = readable({ hour12: false }) -export const deployments = writable({}) +export const deployments = writable([]) export const initConf = writable({}) export const application = writable({ @@ -149,8 +157,8 @@ export const initialApplication = { }, repository: { id: null, - organization: 'new', - name: 'start', + organization: null, + name: null, branch: null }, general: { diff --git a/src/utils/templates.js b/src/utils/templates.js index de0a9014f..f49e3f566 100644 --- a/src/utils/templates.js +++ b/src/utils/templates.js @@ -4,23 +4,29 @@ const defaultBuildAndDeploy = { } const templates = { + svelte: { + pack: 'svelte', + ...defaultBuildAndDeploy, + directory: 'public', + name: 'Svelte' + }, next: { - pack: 'nodejs', + pack: 'nextjs', ...defaultBuildAndDeploy, port: 3000, - name: 'Next.js' + name: 'NextJS' }, nuxt: { - pack: 'nodejs', + pack: 'nuxtjs', ...defaultBuildAndDeploy, port: 3000, - name: 'Nuxt' + name: 'NuxtJS' }, 'react-scripts': { - pack: 'static', + pack: 'react', ...defaultBuildAndDeploy, directory: 'build', - name: 'Create React' + name: 'React' }, 'parcel-bundler': { pack: 'static', @@ -29,22 +35,22 @@ const templates = { name: 'Parcel' }, '@vue/cli-service': { - pack: 'static', + pack: 'vuejs', ...defaultBuildAndDeploy, directory: 'dist', - name: 'Vue CLI' + name: 'Vue' }, gatsby: { - pack: 'static', + pack: 'gatsby', ...defaultBuildAndDeploy, directory: 'public', name: 'Gatsby' }, 'preact-cli': { - pack: 'static', + pack: 'react', ...defaultBuildAndDeploy, directory: 'build', - name: 'Preact CLI' + name: 'Preact' } } diff --git a/tailwind.config.js b/tailwind.config.js index ee9a9a72a..31732265a 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -16,7 +16,7 @@ module.exports = { ], preserveHtmlElements: true, options: { - safelist: [/svelte-/, 'border-green-500', 'border-yellow-300', 'border-red-500', 'hover:border-green-500', 'hover:border-red-200', 'hover:bg-red-200'], + safelist: [/svelte-/, 'border-green-500', 'border-yellow-300', 'border-red-500', 'hover:border-green-500', 'hover:border-red-200', 'hover:bg-red-200', 'hover:bg-warmGray-900', 'hover:bg-transparent'], defaultExtractor: (content) => { // WARNING: tailwindExtractor is internal tailwind api // if this breaks after a tailwind update, report to svite repo