diff --git a/README.md b/README.md index e7a6175da..afa8eca48 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,109 @@ # Coolify -An open-source & self-hostable Heroku / Netlify alternative -(ARM support is in beta). +An open-source & self-hostable Heroku / Netlify alternative. + +## Live Demo + +https://demo.coolify.io/ + +(If it is unresponsive, that means someone overloaded the server. 😄) + +## Feedback + +If you have a new service / build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community! + +--- + +## How to install + +For more details goto the [docs](https://docs.coollabs.io/coolify/installation.html). + +Installation is automated with the following command: + +```bash +wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh +``` + +If you would like no questions during installation: + +```bash +wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh -f +``` + +--- + +## Features + +### Git Sources + +Self-hosted versions also! + + + + +### Destinations + +Deploy your resource to: + +- Local Docker Engine +- Remote Docker Engine + +### Applications + + + + + + + + + + + + + + + + + + +### Databases + + + + + + + + +### Services +- [Appwrite](https://appwrite.io) +- [WordPress](https://docs.coollabs.io/coolify/services/wordpress) +- [Ghost](https://ghost.org) +- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics) +- [NocoDB](https://nocodb.com) +- [VSCode Server](https://github.com/cdr/code-server) +- [MinIO](https://min.io) +- [VaultWarden](https://github.com/dani-garcia/vaultwarden) +- [LanguageTool](https://languagetool.org) +- [n8n](https://n8n.io) +- [Uptime Kuma](https://github.com/louislam/uptime-kuma) +- [MeiliSearch](https://github.com/meilisearch/meilisearch) +- [Umami](https://github.com/mikecao/umami) +- [Fider](https://fider.io) +- [Hasura](https://hasura.io) +- [GlitchTip](https://glitchtip.com) + +## Migration from v1 + +A fresh installation is necessary. v2 and v3 are not compatible with v1. + +## Support + +- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai) +- Telegram: [@andrasbacsai](https://t.me/andrasbacsai) +- Email: [andras@coollabs.io](mailto:andras@coollabs.io) +- Discord: [Invitation](https://discord.gg/6rDM4fkymF) ## Financial Contributors @@ -24,128 +126,4 @@ ### Organizations - - ---- - -## Live Demo - -https://demo.coolify.io/ - -(If it is unresponsive, that means someone overloaded the server. 😄) - -## Feedback - -If you have a new service / build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community! - ---- - -## How to install - -Installation is automated with the following command: - -```bash -wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh -``` - -If you would like no questions during installation: - -```bash -wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh -f -``` - -For more details goto the [docs](https://docs.coollabs.io/coolify/installation). - ---- - -## Features - -### Git Sources - -You can use the following Git Sources to be auto-deployed to your Coolify instance! (Self-hosted versions are also supported.) - - - - -### Destinations - -You can deploy your applications to the following destinations: - -- Local Docker Engine -- Remote Docker Engine - -### Applications - -Predefined build packs to cover the basic needs to deploy applications. - -If you have an advanced use case, you can use the Docker build pack that allows you to deploy your application based on your custom Dockerfile. - - - - - - - - - - - - - - - - - -If you have a new build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community! - -### Databases - -One-click database is ready to be used internally or shared over the internet: - - - - - - - - -If you have a new database you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community! - - -### Services - -You quickly need to host a self-hostable, open-source service? You can do it with a few clicks! -- [WordPress](https://docs.coollabs.io/coolify/services/wordpress) -- [Ghost](https://ghost.org) -- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics) -- [NocoDB](https://nocodb.com) -- [VSCode Server](https://github.com/cdr/code-server) -- [MinIO](https://min.io) -- [VaultWarden](https://github.com/dani-garcia/vaultwarden) -- [LanguageTool](https://languagetool.org) -- [n8n](https://n8n.io) -- [Uptime Kuma](https://github.com/louislam/uptime-kuma) -- [MeiliSearch](https://github.com/meilisearch/meilisearch) -- [Umami](https://github.com/mikecao/umami) -- [Fider](https://fider.io) -- [Hasura](https://hasura.io) -- [GlitchTip](https://glitchtip.com) - - -If you have a new service you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community! - -## Migration from v1 - -A fresh installation is necessary. v2 and v3 are not compatible with v1. - -## Support - -- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai) -- Telegram: [@andrasbacsai](https://t.me/andrasbacsai) -- Email: [andras@coollabs.io](mailto:andras@coollabs.io) -- Discord: [Invitation](https://discord.gg/xhBCC7eGKw) - - -## 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. + diff --git a/apps/api/prisma/migrations/20220816133447_bot_deployments/migration.sql b/apps/api/prisma/migrations/20220816133447_bot_deployments/migration.sql new file mode 100644 index 000000000..3d6d92d0e --- /dev/null +++ b/apps/api/prisma/migrations/20220816133447_bot_deployments/migration.sql @@ -0,0 +1,20 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_ApplicationSettings" ( + "id" TEXT NOT NULL PRIMARY KEY, + "applicationId" TEXT NOT NULL, + "dualCerts" BOOLEAN NOT NULL DEFAULT false, + "debug" BOOLEAN NOT NULL DEFAULT false, + "previews" BOOLEAN NOT NULL DEFAULT false, + "autodeploy" BOOLEAN NOT NULL DEFAULT true, + "isBot" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt" FROM "ApplicationSettings"; +DROP TABLE "ApplicationSettings"; +ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings"; +CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index ec8ad8e68..b48d5c52c 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -124,6 +124,7 @@ model ApplicationSettings { debug Boolean @default(false) previews Boolean @default(false) autodeploy Boolean @default(true) + isBot Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt application Application @relation(fields: [applicationId], references: [id]) diff --git a/apps/api/src/jobs/deployApplication.ts b/apps/api/src/jobs/deployApplication.ts index 9782067d1..a8c46d594 100644 --- a/apps/api/src/jobs/deployApplication.ts +++ b/apps/api/src/jobs/deployApplication.ts @@ -152,6 +152,13 @@ import * as buildpacks from '../lib/buildPacks'; .createHash('sha256') .update( JSON.stringify({ + pythonWSGI, + pythonModule, + pythonVariable, + deploymentType, + denoOptions, + baseImage, + baseBuildImage, buildPack, port, exposePort, diff --git a/apps/api/src/lib/buildPacks/common.ts b/apps/api/src/lib/buildPacks/common.ts index 62aa271c1..6684664d6 100644 --- a/apps/api/src/lib/buildPacks/common.ts +++ b/apps/api/src/lib/buildPacks/common.ts @@ -252,6 +252,20 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st label: 'python:3.7-slim-bullseye' } ]; + const herokuVersions = [ + { + value: 'heroku/builder:22', + label: 'heroku/builder:22' + }, + { + value: 'heroku/buildpacks:20', + label: 'heroku/buildpacks:20' + }, + { + value: 'heroku/builder-classic:22', + label: 'heroku/builder-classic:22' + }, + ] let payload: any = { baseImage: null, baseBuildImage: null, @@ -299,6 +313,11 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st payload.baseBuildImage = 'node:18'; payload.baseBuildImages = nodeVersions; } + if (buildPack === 'heroku') { + payload.baseImage = 'heroku/buildpacks:20'; + payload.baseImages = herokuVersions; + + } return payload; } diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index a00ef9394..a2eb41dae 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -17,7 +17,7 @@ import { checkContainer, removeContainer } from './docker'; import { day } from './dayjs'; import * as serviceFields from './serviceFields' -export const version = '3.4.0'; +export const version = '3.5.0'; export const isDev = process.env.NODE_ENV === 'development'; const algorithm = 'aes-256-ctr'; @@ -261,8 +261,8 @@ export const supportedServiceTypesAndVersions = [ fancyName: 'Hasura', baseImage: 'hasura/graphql-engine', images: ['postgres:12-alpine'], - versions: ['latest', 'v2.8.4', 'v2.5.1'], - recommendedVersion: 'v2.8.4', + versions: ['latest', 'v2.10.0', 'v2.5.1'], + recommendedVersion: 'v2.10.0', ports: { main: 8080 } diff --git a/apps/api/src/lib/docker.ts b/apps/api/src/lib/docker.ts index 3244c2475..aa6a29cfd 100644 --- a/apps/api/src/lib/docker.ts +++ b/apps/api/src/lib/docker.ts @@ -16,7 +16,6 @@ export function formatLabelsOnDocker(data) { export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise { let containerFound = false; try { - console.log('checking ', container) const { stdout } = await executeDockerCmd({ dockerId, command: diff --git a/apps/api/src/lib/services.ts b/apps/api/src/lib/services.ts index 22c8081a5..c5d315ff0 100644 --- a/apps/api/src/lib/services.ts +++ b/apps/api/src/lib/services.ts @@ -13,13 +13,13 @@ export async function defaultServiceConfigurations({ id, teamId }) { let secrets = []; if (serviceSecret.length > 0) { serviceSecret.forEach((secret) => { - secrets.push([secret.name]=secret.value); + secrets.push(`${secret.name}=${secret.value}`); }); } return { ...service, network, port, workdir, image, secrets } } -export function defaultServiceComposeConfiguration(network: string) { +export function defaultServiceComposeConfiguration(network: string): any { return { networks: [network], restart: 'always', diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index b3ef49133..cea2ca294 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -5,7 +5,7 @@ import axios from 'axios'; import { FastifyReply } from 'fastify'; import { day } from '../../../../lib/dayjs'; import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common'; -import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common'; +import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common'; import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker'; import { scheduler } from '../../../../lib/scheduler'; @@ -90,10 +90,11 @@ export async function getApplication(request: FastifyRequest) { const { teamId } = request.user const appId = process.env['COOLIFY_APP_ID']; const application: any = await getApplicationFromDB(id, teamId); - + const settings = await listSettings(); return { application, - appId + appId, + settings }; } catch ({ status, message }) { @@ -275,7 +276,7 @@ export async function saveApplication(request: FastifyRequest, export async function saveApplicationSettings(request: FastifyRequest, reply: FastifyReply) { try { const { id } = request.params - const { debug, previews, dualCerts, autodeploy, branch, projectId } = request.body + const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot } = request.body const isDouble = await checkDoubleBranch(branch, projectId); if (isDouble && autodeploy) { await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } }) @@ -283,7 +284,7 @@ export async function saveApplicationSettings(request: FastifyRequest) { } export async function stopService(request: FastifyRequest) { try { - const { type } = request.params - if (type === 'plausibleanalytics') { - return await stopPlausibleAnalyticsService(request) - } - if (type === 'nocodb') { - return await stopNocodbService(request) - } - if (type === 'minio') { - return await stopMinioService(request) - } - if (type === 'vscodeserver') { - return await stopVscodeService(request) - } - if (type === 'wordpress') { - return await stopWordpressService(request) - } - if (type === 'vaultwarden') { - return await stopVaultwardenService(request) - } - if (type === 'languagetool') { - return await stopLanguageToolService(request) - } - if (type === 'n8n') { - return await stopN8nService(request) - } - if (type === 'uptimekuma') { - return await stopUptimekumaService(request) - } - if (type === 'ghost') { - return await stopGhostService(request) - } - if (type === 'meilisearch') { - return await stopMeilisearchService(request) - } - if (type === 'umami') { - return await stopUmamiService(request) - } - if (type === 'hasura') { - return await stopHasuraService(request) - } - if (type === 'fider') { - return await stopFiderService(request) - } - if (type === 'appwrite') { - return await stopAppWriteService(request) - } - if (type === 'moodle') { - return await stopMoodleService(request) - } - if (type === 'glitchTip') { - return await stopGlitchTipService(request) - } - throw `Service type ${type} not supported.` + return await stopServiceContainers(request) + // const { type } = request.params + // if (type === 'plausibleanalytics') { + // return await stopPlausibleAnalyticsService(request) + // } + // if (type === 'nocodb') { + // return await stopNocodbService(request) + // } + // if (type === 'minio') { + // return await stopMinioService(request) + // } + // if (type === 'vscodeserver') { + // return await stopVscodeService(request) + // } + // if (type === 'wordpress') { + // return await stopWordpressService(request) + // } + // if (type === 'vaultwarden') { + // return await stopVaultwardenService(request) + // } + // if (type === 'languagetool') { + // return await stopLanguageToolService(request) + // } + // if (type === 'n8n') { + // return await stopN8nService(request) + // } + // if (type === 'uptimekuma') { + // return await stopUptimekumaService(request) + // } + // if (type === 'ghost') { + // return await stopGhostService(request) + // } + // if (type === 'meilisearch') { + // return await stopMeilisearchService(request) + // } + // if (type === 'umami') { + // return await stopUmamiService(request) + // } + // if (type === 'hasura') { + // return await stopHasuraService(request) + // } + // if (type === 'fider') { + // return await stopFiderService(request) + // } + // if (type === 'moodle') { + // return await stopMoodleService(request) + // } + // if (type === 'glitchTip') { + // return await stopGlitchTipService(request) + // } + // throw `Service type ${type} not supported.` } catch (error) { throw { status: 500, message: error?.message || error } } @@ -811,52 +808,25 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`; volumes, command: 'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run"', - networks: [network], environment: config.plausibleAnalytics.environmentVariables, - restart: 'always', ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), depends_on: [`${id}-postgresql`, `${id}-clickhouse`], labels: makeLabelForServices('plausibleAnalytics'), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '10s', - max_attempts: 5, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), }, [`${id}-postgresql`]: { container_name: `${id}-postgresql`, image: config.postgresql.image, - networks: [network], environment: config.postgresql.environmentVariables, volumes: [config.postgresql.volume], - restart: 'always', - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '10s', - max_attempts: 5, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), }, [`${id}-clickhouse`]: { build: workdir, container_name: `${id}-clickhouse`, - networks: [network], environment: config.clickhouse.environmentVariables, volumes: [config.clickhouse.volume], - restart: 'always', - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '10s', - max_attempts: 5, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -876,36 +846,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`; }; const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} -async function stopPlausibleAnalyticsService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; - if (destinationDockerId) { - const engine = destinationDocker.engine; - - let found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` }); - if (found) { - await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id }); - } - found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-clickhouse` }); - if (found) { - await removeContainer({ id: `${id}-clickhouse`, dockerId: destinationDocker.id }); - } - } - + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -942,20 +883,11 @@ async function startNocodbService(request: FastifyRequest) { [id]: { container_name: id, image: config.image, - networks: [network], volumes, environment: config.environmentVariables, - restart: 'always', ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), labels: makeLabelForServices('nocodb'), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -967,25 +899,7 @@ async function startNocodbService(request: FastifyRequest) { }; const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} -async function stopNocodbService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; - if (destinationDockerId) { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -1042,19 +956,10 @@ async function startMinioService(request: FastifyRequest) { image: config.image, command: `server /data --console-address ":${consolePort}"`, environment: config.environmentVariables, - networks: [network], volumes, - restart: 'always', ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), labels: makeLabelForServices('minio'), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -1066,32 +971,13 @@ async function startMinioService(request: FastifyRequest) { }; const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + await startServiceContainers(destinationDocker.id, composeFileDestination) await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } }); return {} } catch ({ status, message }) { return errorHandler({ status, message }) } } -async function stopMinioService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - await prisma.minio.update({ where: { serviceId: id }, data: { publicPort: null } }) - if (destinationDockerId) { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} async function startVscodeService(request: FastifyRequest) { try { @@ -1136,19 +1022,10 @@ async function startVscodeService(request: FastifyRequest) { container_name: id, image: config.image, environment: config.environmentVariables, - networks: [network], volumes, - restart: 'always', ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), labels: makeLabelForServices('vscodeServer'), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -1161,8 +1038,7 @@ async function startVscodeService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + await startServiceContainers(destinationDocker.id, composeFileDestination) const changePermissionOn = persistentStorage.map((p) => p.path); if (changePermissionOn.length > 0) { @@ -1177,23 +1053,6 @@ async function startVscodeService(request: FastifyRequest) { return errorHandler({ status, message }) } } -async function stopVscodeService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - if (destinationDockerId) { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} async function startWordpressService(request: FastifyRequest) { try { @@ -1201,6 +1060,7 @@ async function startWordpressService(request: FastifyRequest) const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); const { + arch, type, version, destinationDockerId, @@ -1250,6 +1110,10 @@ async function startWordpressService(request: FastifyRequest) } } }; + if (isARM(arch)) { + config.mysql.image = 'mysql:5.7' + config.mysql.volume = `${id}-mysql-data:/var/lib/mysql` + } if (serviceSecret.length > 0) { serviceSecret.forEach((secret) => { config.wordpress.environmentVariables[secret.name] = secret.value; @@ -1266,18 +1130,9 @@ async function startWordpressService(request: FastifyRequest) image: config.wordpress.image, environment: config.wordpress.environmentVariables, volumes, - networks: [network], - restart: 'always', ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), labels: makeLabelForServices('wordpress'), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -1294,16 +1149,7 @@ async function startWordpressService(request: FastifyRequest) image: config.mysql.image, volumes: [config.mysql.volume], environment: config.mysql.environmentVariables, - networks: [network], - restart: 'always', - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), }; composeFile.volumes[config.mysql.volume.split(':')[0]] = { @@ -1313,61 +1159,13 @@ async function startWordpressService(request: FastifyRequest) const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) } } -async function stopWordpressService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { - destinationDockerId, - destinationDocker, - wordpress: { ftpEnabled } - } = service; - if (destinationDockerId) { - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-mysql` }); - if (found) { - await removeContainer({ id: `${id}-mysql`, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - try { - if (ftpEnabled) { - const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` }); - if (found) { - await removeContainer({ id: `${id}-ftp`, dockerId: destinationDocker.id }); - } - await prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpEnabled: false } - }); - } - } catch (error) { - console.error(error); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} async function startVaultwardenService(request: FastifyRequest) { try { @@ -1401,19 +1199,10 @@ async function startVaultwardenService(request: FastifyRequest container_name: id, image: config.image, environment: config.environmentVariables, - networks: [network], volumes, - restart: 'always', ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), labels: makeLabelForServices('vaultWarden'), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -1426,35 +1215,13 @@ async function startVaultwardenService(request: FastifyRequest const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) } } -async function stopVaultwardenService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - if (destinationDockerId) { - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} async function startLanguageToolService(request: FastifyRequest) { try { @@ -1487,20 +1254,11 @@ async function startLanguageToolService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - if (destinationDockerId) { - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} async function startN8nService(request: FastifyRequest) { try { @@ -1575,20 +1311,11 @@ async function startN8nService(request: FastifyRequest) { [id]: { container_name: id, image: config.image, - networks: [network], volumes, environment: config.environmentVariables, - restart: 'always', labels: makeLabelForServices('n8n'), ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -1601,35 +1328,13 @@ async function startN8nService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) } } -async function stopN8nService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - if (destinationDockerId) { - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} async function startUptimekumaService(request: FastifyRequest) { try { @@ -1661,20 +1366,11 @@ async function startUptimekumaService(request: FastifyRequest) [id]: { container_name: id, image: config.image, - networks: [network], volumes, environment: config.environmentVariables, - restart: 'always', ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), labels: makeLabelForServices('uptimekuma'), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -1687,35 +1383,13 @@ async function startUptimekumaService(request: FastifyRequest) const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) } } -async function stopUptimekumaService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - if (destinationDockerId) { - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} async function startGhostService(request: FastifyRequest) { try { @@ -1790,37 +1464,19 @@ async function startGhostService(request: FastifyRequest) { [id]: { container_name: id, image: config.ghost.image, - networks: [network], volumes, environment: config.ghost.environmentVariables, - restart: 'always', ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), labels: makeLabelForServices('ghost'), depends_on: [`${id}-mariadb`], - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), }, [`${id}-mariadb`]: { container_name: `${id}-mariadb`, image: config.mariadb.image, - networks: [network], volumes: [config.mariadb.volume], environment: config.mariadb.environmentVariables, - restart: 'always', - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -1838,39 +1494,13 @@ async function startGhostService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) } } -async function stopGhostService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - if (destinationDockerId) { - try { - let found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-mariadb` }); - if (found) { - await removeContainer({ id: `${id}-mariadb`, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} async function startMeilisearchService(request: FastifyRequest) { try { @@ -1908,20 +1538,11 @@ async function startMeilisearchService(request: FastifyRequest [id]: { container_name: id, image: config.image, - networks: [network], environment: config.environmentVariables, - restart: 'always', ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), volumes, labels: makeLabelForServices('meilisearch'), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -1933,29 +1554,7 @@ async function startMeilisearchService(request: FastifyRequest }; const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} -async function stopMeilisearchService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - if (destinationDockerId) { - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - } + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -2105,36 +1704,18 @@ async function startUmamiService(request: FastifyRequest) { container_name: id, image: config.umami.image, environment: config.umami.environmentVariables, - networks: [network], volumes, - restart: 'always', ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), labels: makeLabelForServices('umami'), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - }, - depends_on: [`${id}-postgresql`] + depends_on: [`${id}-postgresql`], + ...defaultServiceComposeConfiguration(network), }, [`${id}-postgresql`]: { build: workdir, container_name: `${id}-postgresql`, environment: config.postgresql.environmentVariables, - networks: [network], volumes: [config.postgresql.volume], - restart: 'always', - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -2151,37 +1732,7 @@ async function startUmamiService(request: FastifyRequest) { }; const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} -async function stopUmamiService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - if (destinationDockerId) { - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` }); - if (found) { - await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - } + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -2240,36 +1791,18 @@ async function startHasuraService(request: FastifyRequest) { container_name: id, image: config.hasura.image, environment: config.hasura.environmentVariables, - networks: [network], volumes, - restart: 'always', labels: makeLabelForServices('hasura'), ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - }, - depends_on: [`${id}-postgresql`] + depends_on: [`${id}-postgresql`], + ...defaultServiceComposeConfiguration(network), }, [`${id}-postgresql`]: { image: config.postgresql.image, container_name: `${id}-postgresql`, environment: config.postgresql.environmentVariables, - networks: [network], volumes: [config.postgresql.volume], - restart: 'always', - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -2287,43 +1820,13 @@ async function startHasuraService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) } } -async function stopHasuraService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - if (destinationDockerId) { - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` }); - if (found) { - await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} async function startFiderService(request: FastifyRequest) { try { @@ -2401,36 +1904,18 @@ async function startFiderService(request: FastifyRequest) { container_name: id, image: config.fider.image, environment: config.fider.environmentVariables, - networks: [network], volumes, - restart: 'always', labels: makeLabelForServices('fider'), ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - }, - depends_on: [`${id}-postgresql`] + depends_on: [`${id}-postgresql`], + ...defaultServiceComposeConfiguration(network), }, [`${id}-postgresql`]: { image: config.postgresql.image, container_name: `${id}-postgresql`, environment: config.postgresql.environmentVariables, - networks: [network], volumes: [config.postgresql.volume], - restart: 'always', - deploy: { - restart_policy: { - condition: 'on-failure', - delay: '5s', - max_attempts: 3, - window: '120s' - } - } + ...defaultServiceComposeConfiguration(network), } }, networks: { @@ -2448,43 +1933,14 @@ async function startFiderService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) } } -async function stopFiderService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - if (destinationDockerId) { - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` }); - if (found) { - await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} + async function startAppWriteService(request: FastifyRequest) { try { const { id } = request.params; @@ -2492,7 +1948,7 @@ async function startAppWriteService(request: FastifyRequest) { const { version, fqdn, destinationDocker, secrets, exposePort, network, port, workdir, image, appwrite } = await defaultServiceConfigurations({ id, teamId }) let isStatsEnabled = false - if (secrets._APP_USAGE_STATS) { + if (secrets.find(s => s === '_APP_USAGE_STATS=enabled')) { isStatsEnabled = true } const { @@ -2509,7 +1965,6 @@ async function startAppWriteService(request: FastifyRequest) { const dockerCompose = { [id]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: id, labels: makeLabelForServices('appwrite'), @@ -2539,16 +1994,16 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_DB_USER=${mariadbUser}`, `_APP_DB_PASS=${mariadbPassword}`, `_APP_INFLUXDB_HOST=${id}-influxdb`, - "_APP_INFLUXDB_PORT=8806", + "_APP_INFLUXDB_PORT=8086", `_APP_EXECUTOR_SECRET=${executorSecret}`, `_APP_EXECUTOR_HOST=http://${id}-executor/v1`, `_APP_STATSD_HOST=${id}-telegraf`, "_APP_STATSD_PORT=8125", ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-realtime`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-realtime`, entrypoint: "realtime", @@ -2568,10 +2023,11 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_DB_USER=${mariadbUser}`, `_APP_DB_PASS=${mariadbPassword}`, ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-worker-audits`]: { - ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, container_name: `${id}-worker-audits`, labels: makeLabelForServices('appwrite'), @@ -2591,10 +2047,10 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_DB_USER=${mariadbUser}`, `_APP_DB_PASS=${mariadbPassword}`, ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-worker-webhooks`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-worker-webhooks`, labels: makeLabelForServices('appwrite'), @@ -2609,10 +2065,10 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_REDIS_HOST=${id}-redis`, "_APP_REDIS_PORT=6379", ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-worker-deletes`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-worker-deletes`, labels: makeLabelForServices('appwrite'), @@ -2642,10 +2098,10 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_EXECUTOR_SECRET=${executorSecret}`, `_APP_EXECUTOR_HOST=http://${id}-executor/v1`, ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-worker-databases`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-worker-databases`, labels: makeLabelForServices('appwrite'), @@ -2665,10 +2121,10 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_DB_USER=${mariadbUser}`, `_APP_DB_PASS=${mariadbPassword}`, ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-worker-builds`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-worker-builds`, labels: makeLabelForServices('appwrite'), @@ -2690,10 +2146,10 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_DB_USER=${mariadbUser}`, `_APP_DB_PASS=${mariadbPassword}`, ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-worker-certificates`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-worker-certificates`, labels: makeLabelForServices('appwrite'), @@ -2719,10 +2175,10 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_DB_USER=${mariadbUser}`, `_APP_DB_PASS=${mariadbPassword}`, ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-worker-functions`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-worker-functions`, labels: makeLabelForServices('appwrite'), @@ -2745,10 +2201,10 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_EXECUTOR_SECRET=${executorSecret}`, `_APP_EXECUTOR_HOST=http://${id}-executor/v1`, ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-executor`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-executor`, labels: makeLabelForServices('appwrite'), @@ -2769,10 +2225,10 @@ async function startAppWriteService(request: FastifyRequest) { "_APP_ENV=production", `_APP_EXECUTOR_SECRET=${executorSecret}`, ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-worker-mails`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-worker-mails`, labels: makeLabelForServices('appwrite'), @@ -2786,10 +2242,10 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_REDIS_HOST=${id}-redis`, "_APP_REDIS_PORT=6379", ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-worker-messaging`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-worker-messaging`, labels: makeLabelForServices('appwrite'), @@ -2802,10 +2258,10 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_REDIS_HOST=${id}-redis`, "_APP_REDIS_PORT=6379", ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-maintenance`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-maintenance`, labels: makeLabelForServices('appwrite'), @@ -2826,10 +2282,10 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_DB_USER=${mariadbUser}`, `_APP_DB_PASS=${mariadbPassword}`, ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-schedule`]: { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-schedule`, labels: makeLabelForServices('appwrite'), @@ -2842,10 +2298,10 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_REDIS_HOST=${id}-redis`, "_APP_REDIS_PORT=6379", ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), }, [`${id}-mariadb`]: { - ...defaultServiceComposeConfiguration(network), "image": "mariadb:10.7", container_name: `${id}-mariadb`, labels: makeLabelForServices('appwrite'), @@ -2859,23 +2315,23 @@ async function startAppWriteService(request: FastifyRequest) { `MYSQL_PASSWORD=${mariadbPassword}`, `MYSQL_DATABASE=${mariadbDatabase}` ], - "command": "mysqld --innodb-flush-method=fsync" + "command": "mysqld --innodb-flush-method=fsync", + ...defaultServiceComposeConfiguration(network), }, [`${id}-redis`]: { - ...defaultServiceComposeConfiguration(network), "image": "redis:6.2-alpine", container_name: `${id}-redis`, "command": `redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru --maxmemory-samples 5\n`, "volumes": [ `${id}-redis:/data:rw` - ] + ], + ...defaultServiceComposeConfiguration(network), }, }; if (isStatsEnabled) { - dockerCompose.id.depends_on.push(`${id}-influxdb`); + dockerCompose[id].depends_on.push(`${id}-influxdb`); dockerCompose[`${id}-usage`] = { - ...defaultServiceComposeConfiguration(network), image: `${image}:${version}`, container_name: `${id}-usage`, labels: makeLabelForServices('appwrite'), @@ -2893,28 +2349,29 @@ async function startAppWriteService(request: FastifyRequest) { `_APP_DB_USER=${mariadbUser}`, `_APP_DB_PASS=${mariadbPassword}`, `_APP_INFLUXDB_HOST=${id}-influxdb`, - "_APP_INFLUXDB_PORT=8806", + "_APP_INFLUXDB_PORT=8086", `_APP_REDIS_HOST=${id}-redis`, "_APP_REDIS_PORT=6379", ...secrets - ] + ], + ...defaultServiceComposeConfiguration(network), } dockerCompose[`${id}-influxdb`] = { - ...defaultServiceComposeConfiguration(network), "image": "appwrite/influxdb:1.5.0", container_name: `${id}-influxdb`, "volumes": [ `${id}-influxdb:/var/lib/influxdb:rw` - ] + ], + ...defaultServiceComposeConfiguration(network), } dockerCompose[`${id}-telegraf`] = { - ...defaultServiceComposeConfiguration(network), "image": "appwrite/telegraf:1.4.0", container_name: `${id}-telegraf`, "environment": [ `_APP_INFLUXDB_HOST=${id}-influxdb`, - "_APP_INFLUXDB_PORT=8806", - ] + "_APP_INFLUXDB_PORT=8086", + ], + ...defaultServiceComposeConfiguration(network), } } @@ -2960,32 +2417,37 @@ async function startAppWriteService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) } } -async function stopAppWriteService(request: FastifyRequest) { +async function startServiceContainers(dockerId, composeFileDestination) { + await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} create` }) + await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} start` }) + await asyncSleep(1000); + await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} up -d` }) +} +async function stopServiceContainers(request: FastifyRequest) { try { - // TODO: Fix async for of const { id } = request.params; const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - const containers = [`${id}-mariadb`, `${id}-redis`, `${id}-influxdb`, `${id}-telegraf`, id, `${id}-realtime`, `${id}-worker-audits`, `${id}worker-webhooks`, `${id}-worker-deletes`, `${id}-worker-databases`, `${id}-worker-builds`, `${id}-worker-certificates`, `${id}-worker-functions`, `${id}-worker-mails`, `${id}-worker-messaging`, `${id}-maintenance`, `${id}-schedule`, `${id}-executor`, `${id}-usage`] + const { destinationDockerId } = await getServiceFromDB({ id, teamId }); if (destinationDockerId) { - for (const container of containers) { - const found = await checkContainer({ dockerId: destinationDocker.id, container }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - - } + await executeDockerCmd({ + dockerId: destinationDockerId, + command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}|xargs -n 1 docker stop -t 0` + }) + await executeDockerCmd({ + dockerId: destinationDockerId, + command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}|xargs -n 1 docker rm --force` + }) + return {} } - return {} + throw { status: 500, message: 'Could not stop containers.' } } catch ({ status, message }) { return errorHandler({ status, message }) } @@ -3110,43 +2572,13 @@ async function startMoodleService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) - await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + await startServiceContainers(destinationDocker.id, composeFileDestination) return {} } catch ({ status, message }) { return errorHandler({ status, message }) } } -async function stopMoodleService(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker } = service; - if (destinationDockerId) { - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); - if (found) { - await removeContainer({ id, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - try { - const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-mariadb` }); - if (found) { - await removeContainer({ id: `${id}-mariadb`, dockerId: destinationDocker.id }); - } - } catch (error) { - console.error(error); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } -} async function startGlitchTipService(request: FastifyRequest) { try { @@ -3394,7 +2826,6 @@ async function stopGlitchTipService(request: FastifyRequest) { } } - export async function activatePlausibleUsers(request: FastifyRequest, reply: FastifyReply) { try { const { id } = request.params diff --git a/apps/ui/src/lib/common.ts b/apps/ui/src/lib/common.ts index 7aae8c3e3..5c0e87ed4 100644 --- a/apps/ui/src/lib/common.ts +++ b/apps/ui/src/lib/common.ts @@ -131,8 +131,8 @@ export const supportedServiceTypesAndVersions = [ fancyName: 'Hasura', baseImage: 'hasura/graphql-engine', images: ['postgres:12-alpine'], - versions: ['latest', 'v2.5.1'], - recommendedVersion: 'v2.5.1', + versions: ['latest', 'v2.10.0', 'v2.5.1'], + recommendedVersion: 'v2.10.0', ports: { main: 8080 } @@ -240,7 +240,7 @@ export const staticDeployments = [ 'astro', 'eleventy' ]; -export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel']; +export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel', 'heroku']; export function generateRemoteEngine(destination: any) { diff --git a/apps/ui/src/lib/components/Toast.svelte b/apps/ui/src/lib/components/Toast.svelte index 282bc159f..b0586a51f 100644 --- a/apps/ui/src/lib/components/Toast.svelte +++ b/apps/ui/src/lib/components/Toast.svelte @@ -5,16 +5,17 @@
dispatch('click')} on:mouseover={() => dispatch('pause')} on:focus={() => dispatch('pause')} on:mouseout={() => dispatch('resume')} on:blur={() => dispatch('resume')} - class="alert shadow-lg text-white rounded" + class="alert shadow-lg text-white rounded hover:scale-105 transition-all duration-100 cursor-pointer" class:bg-coollabs={type === 'success'} class:alert-error={type === 'error'} class:alert-info={type === 'info'} > - + {/if}
diff --git a/apps/ui/src/lib/components/Toasts.svelte b/apps/ui/src/lib/components/Toasts.svelte index 2e53c6287..1c7c8cbc0 100644 --- a/apps/ui/src/lib/components/Toasts.svelte +++ b/apps/ui/src/lib/components/Toasts.svelte @@ -2,7 +2,7 @@ import { fade } from 'svelte/transition'; import Toast from './Toast.svelte'; - import { pauseToast, resumeToast, toasts } from '$lib/store'; + import { dismissToast, pauseToast, resumeToast, toasts } from '$lib/store'; {#if $toasts} @@ -12,7 +12,8 @@ resumeToast(toast.id)} - on:pause={() => pauseToast(toast.id)}>{@html toast.message} pauseToast(toast.id)} + on:click={() => dismissToast(toast.id)}>{@html toast.message} {/each} diff --git a/apps/ui/src/lib/components/svg/applications/ApplicationIcons.svelte b/apps/ui/src/lib/components/svg/applications/ApplicationIcons.svelte index 379b775ce..0980063c6 100644 --- a/apps/ui/src/lib/components/svg/applications/ApplicationIcons.svelte +++ b/apps/ui/src/lib/components/svg/applications/ApplicationIcons.svelte @@ -38,4 +38,6 @@ {:else if application.buildPack?.toLowerCase() === 'laravel'} +{:else if application.buildPack?.toLowerCase() === 'heroku'} + {/if} diff --git a/apps/ui/src/lib/components/svg/applications/Heroku.svelte b/apps/ui/src/lib/components/svg/applications/Heroku.svelte new file mode 100644 index 000000000..dff845bc2 --- /dev/null +++ b/apps/ui/src/lib/components/svg/applications/Heroku.svelte @@ -0,0 +1,15 @@ + + + + + diff --git a/apps/ui/src/lib/components/svg/applications/index.ts b/apps/ui/src/lib/components/svg/applications/index.ts index 12b712f4c..ee2236357 100644 --- a/apps/ui/src/lib/components/svg/applications/index.ts +++ b/apps/ui/src/lib/components/svg/applications/index.ts @@ -16,4 +16,4 @@ export { default as Astro } from './Astro.svelte'; export { default as Eleventy } from './Eleventy.svelte'; export { default as Deno } from './Deno.svelte'; export { default as Laravel } from './Laravel.svelte'; - +export { default as Heroku } from './Heroku.svelte'; diff --git a/apps/ui/src/lib/components/svg/services/Appwrite.svelte b/apps/ui/src/lib/components/svg/services/Appwrite.svelte new file mode 100644 index 000000000..fbabd5168 --- /dev/null +++ b/apps/ui/src/lib/components/svg/services/Appwrite.svelte @@ -0,0 +1,21 @@ + + + + + + diff --git a/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte b/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte index 856d7f785..fb89bf898 100644 --- a/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte +++ b/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte @@ -32,6 +32,8 @@ {:else if type === 'fider'} +{:else if type === 'appwrite'} + {:else if type === 'moodle'} {:else if type === 'glitchTip'} diff --git a/apps/ui/src/lib/components/svg/services/index.ts b/apps/ui/src/lib/components/svg/services/index.ts index 14a7bf05c..30b37922a 100644 --- a/apps/ui/src/lib/components/svg/services/index.ts +++ b/apps/ui/src/lib/components/svg/services/index.ts @@ -1,18 +1,18 @@ //@ts-nocheck export { default as PlausibleAnalytics } from './PlausibleAnalytics.svelte'; -export { default as NocoDb } from './NocoDB.svelte'; -export { default as MinIo } from './MinIO.svelte'; -export { default as VsCodeServer } from './VSCodeServer.svelte'; -export { default as Wordpress } from './Wordpress.svelte'; -export { default as VaultWarden } from './VaultWarden.svelte'; -export { default as LanguageTool } from './LanguageTool.svelte'; -export { default as N8n } from './N8n.svelte'; -export { default as UptimeKuma } from './UptimeKuma.svelte'; -export { default as Ghost } from './Ghost.svelte'; -export { default as MeiliSearch } from './MeiliSearch.svelte'; -export { default as Umami } from './Umami.svelte'; -export { default as Hasura } from './Hasura.svelte'; -export { default as Fider } from './Fider.svelte'; -export { default as Appwrite } from './Moodle.svelte'; -export { default as Moodle } from './Moodle.svelte'; +export { default as NocoDb } from './NocoDB.svelte'; +export { default as MinIo } from './MinIO.svelte'; +export { default as VsCodeServer } from './VSCodeServer.svelte'; +export { default as Wordpress } from './Wordpress.svelte'; +export { default as VaultWarden } from './VaultWarden.svelte'; +export { default as LanguageTool } from './LanguageTool.svelte'; +export { default as N8n } from './N8n.svelte'; +export { default as UptimeKuma } from './UptimeKuma.svelte'; +export { default as Ghost } from './Ghost.svelte'; +export { default as MeiliSearch } from './MeiliSearch.svelte'; +export { default as Umami } from './Umami.svelte'; +export { default as Hasura } from './Hasura.svelte'; +export { default as Fider } from './Fider.svelte'; +export { default as Appwrite } from './Appwrite.svelte'; +export { default as Moodle } from './Moodle.svelte'; export { default as GlitchTip } from './GlitchTip.svelte'; diff --git a/apps/ui/src/lib/locales/en.json b/apps/ui/src/lib/locales/en.json index 809d38851..4060dcc8a 100644 --- a/apps/ui/src/lib/locales/en.json +++ b/apps/ui/src/lib/locales/en.json @@ -144,8 +144,8 @@ }, "preview": { "need_during_buildtime": "Need during buildtime?", - "setup_secret_app_first": "You can add secrets to PR/MR deployments. Please add secrets to the application first.
Useful for creating staging environments.", - "values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating staging environments.", + "setup_secret_app_first": "You can add secrets to PR/MR deployments. Please add secrets to the application first.
Useful for creating staging environments.", + "values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating staging environments.", "redeploy": "Redeploy", "no_previews_available": "No previews available" }, @@ -194,14 +194,14 @@ "application": "Application", "url_fqdn": "URL (FQDN)", "domain_fqdn": "Domain (FQDN)", - "https_explainer": "If you specify https, the application will be accessible only over https. SSL certificate will be generated for you.
If you specify www, the application will be redirected (302) from non-www and vice versa.

To modify the domain, you must first stop the application.

You must set your DNS to point to the server IP in advance.", + "https_explainer": "If you specify https, the application will be accessible only over https. SSL certificate will be generated for you.
If you specify www, the application will be redirected (302) from non-www and vice versa.

To modify the domain, you must first stop the application.

You must set your DNS to point to the server IP in advance.", "ssl_www_and_non_www": "Generate SSL for www and non-www?", - "ssl_explainer": "It will generate certificates for both www and non-www.
You need to have both DNS entries set in advance.

Useful if you expect to have visitors on both.", + "ssl_explainer": "It will generate certificates for both www and non-www.
You need to have both DNS entries set in advance.

Useful if you expect to have visitors on both.", "install_command": "Install Command", "build_command": "Build Command", "start_command": "Start Command", - "directory_to_use_explainer": "Directory to use as the base for all commands.
Could be useful with monorepos.", - "publish_directory_explainer": "Directory containing all the assets for deployment.
For example: dist,_site or public.", + "directory_to_use_explainer": "Directory to use as the base for all commands.
Could be useful with monorepos.", + "publish_directory_explainer": "Directory containing all the assets for deployment.
For example: dist,_site or public.", "features": "Features", "enable_automatic_deployment": "Enable Automatic Deployment", "enable_auto_deploy_webhooks": "Enable automatic deployment through webhooks.", diff --git a/apps/ui/src/lib/locales/fr.json b/apps/ui/src/lib/locales/fr.json index 418f72526..9dc5aa8c4 100644 --- a/apps/ui/src/lib/locales/fr.json +++ b/apps/ui/src/lib/locales/fr.json @@ -65,7 +65,7 @@ "features": "Caractéristiques", "git_repository": "DépÎt Git", "git_source": "Source Git", - "https_explainer": "Si vous spécifiez https, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.
Si vous spécifiez www, l'application sera redirigée (302) à partir de non-www et vice versa \n.

Pour modifier le domaine, vous devez d'abord arrĂȘter l'application.

Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.", + "https_explainer": "Si vous spécifiez https, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.
Si vous spécifiez www, l'application sera redirigée (302) à partir de non-www et vice versa \n.

Pour modifier le domaine, vous devez d'abord arrĂȘter l'application.

Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.", "install_command": "Commande d'installation", "logs": "Journaux des applications", "no_applications_found": "Aucune application trouvée", @@ -78,11 +78,11 @@ "need_during_buildtime": "Besoin pendant la build ?", "no_previews_available": "Aucun aperçu disponible", "redeploy": "Redéployer", - "setup_secret_app_first": "Vous pouvez ajouter des secrets aux déploiements PR/MR. \nVeuillez d'abord ajouter des secrets à l'application. \n
Utile pour créer des environnements de mise en scÚne.", - "values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements de mise en scÚne." + "setup_secret_app_first": "Vous pouvez ajouter des secrets aux déploiements PR/MR. \nVeuillez d'abord ajouter des secrets à l'application. \n
Utile pour créer des environnements de mise en scÚne.", + "values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements de mise en scÚne." }, "previews": "Aperçus", - "publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n
Par exemple : dist,_site ou public.", + "publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n
Par exemple : dist,_site ou public.", "rebuild_application": "Re-build l'application", "secret": "secrets", "secrets": { @@ -91,7 +91,7 @@ "use_isbuildsecret": "Utiliser isBuildSecret" }, "settings_saved": "ParamÚtres sauvegardés.", - "ssl_explainer": "Il générera des certificats pour www et non-www. \n
Vous devez avoir les deux entrées DNS définies à l'avance.

Utile si vous prévoyez d'avoir des visiteurs sur les deux.", + "ssl_explainer": "Il générera des certificats pour www et non-www. \n
Vous devez avoir les deux entrées DNS définies à l'avance.

Utile si vous prĂ©voyez d'avoir des visiteurs sur les deux.", "ssl_www_and_non_www": "GĂ©nĂ©rer SSL pour www et non-www ?", "start_command": "DĂ©marrer la commande", "stop_application": "ArrĂȘter l'application", diff --git a/apps/ui/src/lib/store.ts b/apps/ui/src/lib/store.ts index 8834782a4..0656ada54 100644 --- a/apps/ui/src/lib/store.ts +++ b/apps/ui/src/lib/store.ts @@ -70,7 +70,10 @@ export const features = readable({ }); export const location: Writable = writable(null) -export const setLocation = (resource: any) => { +export const setLocation = (resource: any, settings?: any) => { + if (resource.settings.isBot) { + return location.set(`http://${settings.ipv4}:${resource.exposePort}`) + } if (GITPOD_WORKSPACE_URL && resource.exposePort) { const { href } = new URL(GITPOD_WORKSPACE_URL); const newURL = href diff --git a/apps/ui/src/lib/templates.ts b/apps/ui/src/lib/templates.ts index ecd669e8e..c4ec05ad6 100644 --- a/apps/ui/src/lib/templates.ts +++ b/apps/ui/src/lib/templates.ts @@ -170,6 +170,16 @@ export function findBuildPack(pack: string, packageManager = 'npm') { port: 80 }; } + if (pack === 'heroku') { + return { + ...metaData, + installCommand: null, + buildCommand: null, + startCommand: null, + publishDirectory: null, + port: 5000 + }; + } return { name: 'node', fancyName: 'Node.js', @@ -187,118 +197,137 @@ export const buildPacks = [ name: 'node', fancyName: 'Node.js', hoverColor: 'hover:bg-green-700', - color: 'bg-green-700' + color: 'bg-green-700', + isCoolifyBuildPack: true, }, { name: 'static', fancyName: 'Static', hoverColor: 'hover:bg-orange-700', - color: 'bg-orange-700' + color: 'bg-orange-700', + isCoolifyBuildPack: true, }, { name: 'php', fancyName: 'PHP', hoverColor: 'hover:bg-indigo-700', - color: 'bg-indigo-700' + color: 'bg-indigo-700', + isCoolifyBuildPack: true, }, { name: 'laravel', fancyName: 'Laravel', hoverColor: 'hover:bg-indigo-700', - color: 'bg-indigo-700' + color: 'bg-indigo-700', + isCoolifyBuildPack: true, }, { name: 'docker', fancyName: 'Docker', hoverColor: 'hover:bg-sky-700', - color: 'bg-sky-700' + color: 'bg-sky-700', + isCoolifyBuildPack: true, }, { name: 'svelte', fancyName: 'Svelte', hoverColor: 'hover:bg-orange-700', - color: 'bg-orange-700' + color: 'bg-orange-700', + isCoolifyBuildPack: true, }, { name: 'vuejs', fancyName: 'VueJS', hoverColor: 'hover:bg-green-700', - color: 'bg-green-700' + color: 'bg-green-700', + isCoolifyBuildPack: true, }, { name: 'nuxtjs', fancyName: 'NuxtJS', hoverColor: 'hover:bg-green-700', - color: 'bg-green-700' + color: 'bg-green-700', + isCoolifyBuildPack: true, }, { name: 'gatsby', fancyName: 'Gatsby', hoverColor: 'hover:bg-blue-700', - color: 'bg-blue-700' + color: 'bg-blue-700', + isCoolifyBuildPack: true, }, { name: 'astro', fancyName: 'Astro', hoverColor: 'hover:bg-pink-700', - color: 'bg-pink-700' + color: 'bg-pink-700', + isCoolifyBuildPack: true, }, { name: 'eleventy', fancyName: 'Eleventy', hoverColor: 'hover:bg-red-700', - color: 'bg-red-700' + color: 'bg-red-700', + isCoolifyBuildPack: true, }, { name: 'react', fancyName: 'React', hoverColor: 'hover:bg-blue-700', - color: 'bg-blue-700' + color: 'bg-blue-700', + isCoolifyBuildPack: true, }, { name: 'preact', fancyName: 'Preact', hoverColor: 'hover:bg-blue-700', - color: 'bg-blue-700' + color: 'bg-blue-700', + isCoolifyBuildPack: true, }, { name: 'nextjs', fancyName: 'NextJS', hoverColor: 'hover:bg-blue-700', - color: 'bg-blue-700' + color: 'bg-blue-700', + isCoolifyBuildPack: true, }, { name: 'nestjs', fancyName: 'NestJS', hoverColor: 'hover:bg-red-700', - color: 'bg-red-700' + color: 'bg-red-700', + isCoolifyBuildPack: true, }, { name: 'rust', fancyName: 'Rust', hoverColor: 'hover:bg-pink-700', - color: 'bg-pink-700' + color: 'bg-pink-700', + isCoolifyBuildPack: true, }, { name: 'python', fancyName: 'Python', hoverColor: 'hover:bg-green-700', - color: 'bg-green-700' + color: 'bg-green-700', + isCoolifyBuildPack: true, }, { name: 'deno', fancyName: 'Deno', hoverColor: 'hover:bg-green-700', - color: 'bg-green-700' + color: 'bg-green-700', + isCoolifyBuildPack: true, }, - // { - // name: 'heroku', - // fancyName: 'Heroku Buildpack', - // hoverColor: 'hover:bg-indigo-700', - // color: 'bg-indigo-700' - // } + { + name: 'heroku', + fancyName: 'Heroku', + hoverColor: 'hover:bg-purple-700', + color: 'bg-purple-700', + isHerokuBuildPack: true, + } ]; export const scanningTemplates = { '@sveltejs/kit': { diff --git a/apps/ui/src/routes/applications/[id]/__layout.svelte b/apps/ui/src/routes/applications/[id]/__layout.svelte index 4cb126eda..85f8001d2 100644 --- a/apps/ui/src/routes/applications/[id]/__layout.svelte +++ b/apps/ui/src/routes/applications/[id]/__layout.svelte @@ -16,7 +16,7 @@ export const load: Load = async ({ fetch, url, params }) => { try { const response = await get(`/applications/${params.id}`); - let { application, appId, settings, isQueueActive } = response; + let { application, appId, settings } = response; if (!application || Object.entries(application).length === 0) { return { status: 302, @@ -36,7 +36,8 @@ return { props: { - application + application, + settings }, stuff: { application, @@ -52,7 +53,7 @@
@@ -65,7 +71,9 @@
{#if !destinations || ownDestinations.length === 0}
-
{$t('application.configuration.no_configurable_destination')}
+
+ {$t('application.configuration.no_configurable_destination')} +
export let application: any; + export let settings: any; import { page } from '$app/stores'; import { onDestroy, onMount } from 'svelte'; import Select from 'svelte-select'; @@ -60,6 +62,7 @@ let previews = application.settings.previews; let dualCerts = application.settings.dualCerts; let autodeploy = application.settings.autodeploy; + let isBot = application.settings.isBot; let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, ''); let isNonWWWDomainOK = false; @@ -99,7 +102,7 @@ application.fqdn = `http://${cuid()}.demo.coolify.io`; await handleSubmit(); } - domainEl.focus(); + // !isBot && domainEl.focus(); await getUsage(); usageInterval = setInterval(async () => { await getUsage(); @@ -129,11 +132,16 @@ if (name === 'autodeploy') { autodeploy = !autodeploy; } + if (name === 'isBot') { + isBot = !isBot; + setLocation(application, settings); + } try { await post(`/applications/${id}/settings`, { previews, debug, dualCerts, + isBot, autodeploy, branch: application.branch, projectId: application.projectId @@ -155,24 +163,28 @@ if (name === 'autodeploy') { autodeploy = !autodeploy; } + if (name === 'isBot') { + isBot = !isBot; + } return errorNotification(error); } } async function handleSubmit() { - if (loading || !application.fqdn) return; + if (loading || (!application.fqdn && !isBot)) return; loading = true; try { nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, ''); if (application.deploymentType) application.deploymentType = application.deploymentType.toLowerCase(); - await post(`/applications/${id}/check`, { - fqdn: application.fqdn, - forceSave, - dualCerts, - exposePort: application.exposePort - }); + !isBot && + (await post(`/applications/${id}/check`, { + fqdn: application.fqdn, + forceSave, + dualCerts, + exposePort: application.exposePort + })); await post(`/applications/${id}`, { ...application }); - setLocation(application); + setLocation(application, settings); $disabledButton = false; forceSave = false; addToast({ @@ -332,53 +344,56 @@ - + {#if isDisabled} + + {:else} + + {/if}
- + {#if isDisabled} + + {:else} + + {/if}
- - + {#if isDisabled} + + {:else} + + + {/if}
-
-
- - {#if browser && window.location.hostname === 'demo.coolify.io'} - + changeSettings('isBot')} + title="Is your application a bot?" + description="You can deploy applications without domains.
They will listen on IP:PORT instead.

Useful for example bots." + /> +
+ {#if !isBot} +
+
+ + {#if browser && window.location.hostname === 'demo.coolify.io'} + + {/if} + +
+
+ - {/if} - -
-
- - {#if forceSave} -
- {#if isNonWWWDomainOK} - - {:else} - - {/if} - {#if dualCerts} - {#if isWWWDomainOK} + {#if forceSave} +
+ {#if isNonWWWDomainOK} isDNSValid(getDomain(nonWWWDomain), false)} + >DNS settings for {nonWWWDomain} is OK, click to recheck. {:else} isDNSValid(getDomain(nonWWWDomain), false)} + >DNS settings for {nonWWWDomain} is invalid, click to recheck. {/if} - {/if} -
- {/if} + {#if dualCerts} + {#if isWWWDomainOK} + + {:else} + + {/if} + {/if} +
+ {/if} +
-
-
- !$status.application.isRunning && changeSettings('dualCerts')} - /> -
+
+ !$status.application.isRunning && changeSettings('dualCerts')} + /> +
+ {/if} {#if application.buildPack === 'python'}
@@ -585,7 +611,7 @@
{/if} {/if} - {#if !staticDeployments.includes(application.buildPack)} + {#if !staticDeployments.includes(application.buildPack) && !isBot}
{/if} - {#if application.buildPack !== 'laravel'} + {#if application.buildPack !== 'laravel' && application.buildPack !== 'heroku'}