From 3e0d4ebbd6d174626e070d3f72291765f95e431e Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Tue, 17 Sep 2024 11:23:40 +0000 Subject: [PATCH 001/243] feat: added mailserver template --- apps/dokploy/public/templates/mailserver.svg | 1 + .../templates/mailserver/docker-compose.yml | 54 +++++++++++++++++++ apps/dokploy/templates/mailserver/index.ts | 20 +++++++ apps/dokploy/templates/templates.ts | 14 +++++ 4 files changed, 89 insertions(+) create mode 100644 apps/dokploy/public/templates/mailserver.svg create mode 100644 apps/dokploy/templates/mailserver/docker-compose.yml create mode 100644 apps/dokploy/templates/mailserver/index.ts diff --git a/apps/dokploy/public/templates/mailserver.svg b/apps/dokploy/public/templates/mailserver.svg new file mode 100644 index 000000000..7ec0dbb32 --- /dev/null +++ b/apps/dokploy/public/templates/mailserver.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/dokploy/templates/mailserver/docker-compose.yml b/apps/dokploy/templates/mailserver/docker-compose.yml new file mode 100644 index 000000000..8ab044a86 --- /dev/null +++ b/apps/dokploy/templates/mailserver/docker-compose.yml @@ -0,0 +1,54 @@ +services: + mailserver: + image: ghcr.io/docker-mailserver/docker-mailserver:latest + hostname: ${DMS_HOSTNAME} + ports: + - "25:25" # SMTP (STARTTLS) + - "465:465" # SMTP (Implicit TLS) + - "587:587" # SMTP (STARTTLS) + - "143:143" # IMAP (STARTTLS) + - "993:993" # IMAP (Implicit TLS) + volumes: + - dms-mail-data:/var/mail/ + - dms-mail-state:/var/mail-state/ + - dms-mail-logs:/var/log/mail/ + - dms-mail-config:/tmp/docker-mailserver/ + - /etc/dokploy/traefik/dynamic/acme.json:/etc/letsencrypt/acme.json:ro + - /etc/localtime:/etc/localtime:ro + environment: + - ENABLE_FAIL2BAN=${DMS_ENABLE_FAIL2BAN} + - PERMIT_DOCKER=${DMS_PERMIT_DOCKER} + - SPOOF_PROTECTION=${DMS_SPOOF_PROTECTION} + - SSL_TYPE=${DMS_SSL_TYPE} + - SSL_DOMAIN=${DMS_SSL_DOMAIN} + - POSTMASTER_ADDRESS=${DMS_POSTMASTER_ADDRESS} + cap_add: + - NET_ADMIN + restart: always + stop_grace_period: 1m + healthcheck: + test: ${DMS_HEALTHCHECK_CMD} + timeout: ${DMS_HEALTHCHECK_TIMEOUT} + retries: ${DMS_HEALTHCHECK_RETRIES} + command: > + sh -c ' + if [ ! -s /tmp/docker-mailserver/postfix-accounts.cf ]; then + echo "File does not exist or is empty. Running setup command..."; + setup email add "${DMS_DEFAULT_USER}" "${DMS_DEFAULT_USER_PASS}"; + else + echo "File exists and is not empty. Skipping setup command."; + fi + exec supervisord -c /etc/supervisor/supervisord.conf + ' + networks: + - dokploy-network + +networks: + dokploy-network: + external: true + +volumes: + dms-mail-data: + dms-mail-state: + dms-mail-logs: + dms-mail-config: \ No newline at end of file diff --git a/apps/dokploy/templates/mailserver/index.ts b/apps/dokploy/templates/mailserver/index.ts new file mode 100644 index 000000000..6796800e4 --- /dev/null +++ b/apps/dokploy/templates/mailserver/index.ts @@ -0,0 +1,20 @@ +import type { Schema, Template } from "../utils"; + +export async function generate(schema: Schema): Template { + const envs = [ + "DMS_HOSTNAME=mail.example.com", + "DMS_HEALTHCHECK_CMD='ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1'", + "DMS_HEALTHCHECK_TIMEOUT=3s", + "DMS_HEALTHCHECK_RETRIES=0", + "DMS_POSTMASTER_ADDRESS=postmaster@example.com", + "DMS_DEFAULT_USER=admin@example.com", + "DMS_DEFAULT_USER_PASS=password", + "DMS_ENABLE_FAIL2BAN=1", + "DMS_PERMIT_DOCKER=network", + "DMS_SPOOF_PROTECTION=0", + "DMS_SSL_TYPE=letsencrypt", + "DMS_SSL_DOMAIN=example.com", + ]; + + return { envs }; +} diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 5603b188a..05f1ec3cd 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -497,4 +497,18 @@ export const templates: TemplateData[] = [ tags: ["self-hosted", "storage"], load: () => import("./gitea/index").then((m) => m.generate), }, + { + id: "mailserver", + name: "Mailserver", + version: "14.0", + description: "A fullstack but simple mail server with SMTP, IMAP, LDAP, Antispam, Antivirus, etc.", + logo: "mailserver.svg", + links: { + github: "https://github.com/docker-mailserver/docker-mailserver", + website: "https://docker-mailserver.github.io/docker-mailserver/v14.0/", + docs: "https://docker-mailserver.github.io/docker-mailserver/v14.0/config/environment/", + }, + tags: ["self-hosted", "email"], + load: () => import("./mailserver/index").then((m) => m.generate), + }, ]; From 0327334fcdef0cb5babb5dded3f517de3d20b081 Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Tue, 17 Sep 2024 11:30:14 +0000 Subject: [PATCH 002/243] fix: run pnpm check --- apps/dokploy/templates/templates.ts | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 05f1ec3cd..cca2eb765 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -497,18 +497,19 @@ export const templates: TemplateData[] = [ tags: ["self-hosted", "storage"], load: () => import("./gitea/index").then((m) => m.generate), }, - { - id: "mailserver", - name: "Mailserver", - version: "14.0", - description: "A fullstack but simple mail server with SMTP, IMAP, LDAP, Antispam, Antivirus, etc.", - logo: "mailserver.svg", - links: { - github: "https://github.com/docker-mailserver/docker-mailserver", - website: "https://docker-mailserver.github.io/docker-mailserver/v14.0/", - docs: "https://docker-mailserver.github.io/docker-mailserver/v14.0/config/environment/", - }, - tags: ["self-hosted", "email"], - load: () => import("./mailserver/index").then((m) => m.generate), - }, + { + id: "mailserver", + name: "Mailserver", + version: "14.0", + description: + "A fullstack but simple mail server with SMTP, IMAP, LDAP, Antispam, Antivirus, etc.", + logo: "mailserver.svg", + links: { + github: "https://github.com/docker-mailserver/docker-mailserver", + website: "https://docker-mailserver.github.io/docker-mailserver/v14.0/", + docs: "https://docker-mailserver.github.io/docker-mailserver/v14.0/config/environment/", + }, + tags: ["self-hosted", "email"], + load: () => import("./mailserver/index").then((m) => m.generate), + }, ]; From a39a7a276d1542bada3b34b645a24b52994ae0c2 Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Tue, 17 Sep 2024 11:32:48 +0000 Subject: [PATCH 003/243] fix: clean after mailcow tests --- apps/dokploy/templates/mailserver/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/templates/mailserver/index.ts b/apps/dokploy/templates/mailserver/index.ts index 6796800e4..47379780b 100644 --- a/apps/dokploy/templates/mailserver/index.ts +++ b/apps/dokploy/templates/mailserver/index.ts @@ -1,6 +1,6 @@ import type { Schema, Template } from "../utils"; -export async function generate(schema: Schema): Template { +export function generate(schema: Schema): Template { const envs = [ "DMS_HOSTNAME=mail.example.com", "DMS_HEALTHCHECK_CMD='ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1'", From 8b855d7ee41b8e767b016372fa5cfba4d1863f80 Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Fri, 20 Sep 2024 21:57:46 +0000 Subject: [PATCH 004/243] feat: added erpnext template --- apps/dokploy/public/templates/erpnext.png | Bin 0 -> 6396 bytes .../templates/erpnext/docker-compose.yml | 211 ++++++++++++++++++ apps/dokploy/templates/erpnext/index.ts | 22 ++ apps/dokploy/templates/templates.ts | 14 ++ 4 files changed, 247 insertions(+) create mode 100644 apps/dokploy/public/templates/erpnext.png create mode 100644 apps/dokploy/templates/erpnext/docker-compose.yml create mode 100644 apps/dokploy/templates/erpnext/index.ts diff --git a/apps/dokploy/public/templates/erpnext.png b/apps/dokploy/public/templates/erpnext.png new file mode 100644 index 0000000000000000000000000000000000000000..c826955172ca1d98fb9b18d76cbaa39eccd91c82 GIT binary patch literal 6396 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 KMNUMnLSTY}Lm3bN literal 0 HcmV?d00001 diff --git a/apps/dokploy/templates/erpnext/docker-compose.yml b/apps/dokploy/templates/erpnext/docker-compose.yml new file mode 100644 index 000000000..d3aa92a14 --- /dev/null +++ b/apps/dokploy/templates/erpnext/docker-compose.yml @@ -0,0 +1,211 @@ +services: + backend: + image: frappe/erpnext:v15.35.1 + deploy: + restart_policy: + condition: on-failure + networks: + - dokploy-network + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + configurator: + image: frappe/erpnext:v15.35.1 + deploy: + restart_policy: + condition: none + entrypoint: + - bash + - -c + # add redis_socketio for backward compatibility + command: + - > + ls -1 apps > sites/apps.txt; + bench set-config -g db_host $$DB_HOST; + bench set-config -gp db_port $$DB_PORT; + bench set-config -g redis_cache "redis://$$REDIS_CACHE"; + bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; + bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; + bench set-config -gp socketio_port $$SOCKETIO_PORT; + environment: + DB_HOST: db + DB_PORT: "3306" + REDIS_CACHE: redis-cache:6379 + REDIS_QUEUE: redis-queue:6379 + SOCKETIO_PORT: "9000" + networks: + - dokploy-network + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + create-site: + image: frappe/erpnext:v15.35.1 + deploy: + restart_policy: + condition: none + networks: + - dokploy-network + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + entrypoint: + - bash + - -c + command: + - > + wait-for-it -t 120 db:3306; + wait-for-it -t 120 redis-cache:6379; + wait-for-it -t 120 redis-queue:6379; + export start=`date +%s`; + until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \ + [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \ + [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]]; + do + echo "Waiting for sites/common_site_config.json to be created"; + sleep 5; + if (( `date +%s`-start > 120 )); then + echo "could not find sites/common_site_config.json with required keys"; + exit 1 + fi + done; + echo "sites/common_site_config.json found"; + bench new-site --no-mariadb-socket --admin-password=admin --db-root-password=admin --install-app erpnext --set-default frontend; + + db: + image: mariadb:10.6 + healthcheck: + test: mysqladmin ping -h localhost --password=admin + interval: 1s + retries: 15 + deploy: + restart_policy: + condition: on-failure + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --skip-character-set-client-handshake + - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 + environment: + MYSQL_ROOT_PASSWORD: admin + networks: + - dokploy-network + volumes: + - db-data:/var/lib/mysql + + frontend: + image: frappe/erpnext:v15.35.1 + depends_on: + - websocket + deploy: + restart_policy: + condition: on-failure + command: + - nginx-entrypoint.sh + environment: + BACKEND: backend:8000 + FRAPPE_SITE_NAME_HEADER: frontend + SOCKETIO: websocket:9000 + UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1 + UPSTREAM_REAL_IP_HEADER: X-Forwarded-For + UPSTREAM_REAL_IP_RECURSIVE: "off" + PROXY_READ_TIMEOUT: 120 + CLIENT_MAX_BODY_SIZE: 50m + networks: + - dokploy-network + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + ports: + - "8080:8080" + + queue-long: + image: frappe/erpnext:v15.35.1 + deploy: + restart_policy: + condition: on-failure + command: + - bench + - worker + - --queue + - long,default,short + networks: + - dokploy-network + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + queue-short: + image: frappe/erpnext:v15.35.1 + deploy: + restart_policy: + condition: on-failure + command: + - bench + - worker + - --queue + - short,default + networks: + - dokploy-network + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + redis-queue: + image: redis:6.2-alpine + deploy: + restart_policy: + condition: on-failure + networks: + - dokploy-network + volumes: + - redis-queue-data:/data + + redis-cache: + image: redis:6.2-alpine + deploy: + restart_policy: + condition: on-failure + networks: + - dokploy-network + volumes: + - redis-cache-data:/data + + scheduler: + image: frappe/erpnext:v15.35.1 + deploy: + restart_policy: + condition: on-failure + command: + - bench + - schedule + networks: + - dokploy-network + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + websocket: + image: frappe/erpnext:v15.35.1 + deploy: + restart_policy: + condition: on-failure + command: + - node + - /home/frappe/frappe-bench/apps/frappe/socketio.js + networks: + - dokploy-network + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + +volumes: + db-data: + redis-queue-data: + redis-cache-data: + sites: + logs: +networks: + dokploy-network: + external: true \ No newline at end of file diff --git a/apps/dokploy/templates/erpnext/index.ts b/apps/dokploy/templates/erpnext/index.ts new file mode 100644 index 000000000..2165c46eb --- /dev/null +++ b/apps/dokploy/templates/erpnext/index.ts @@ -0,0 +1,22 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const mainDomain = generateRandomDomain(schema); + + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 8080, + serviceName: "frontend", + }, + ]; + + return { + domains, + }; +} diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index cca2eb765..e3508d1b1 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -512,4 +512,18 @@ export const templates: TemplateData[] = [ tags: ["self-hosted", "email"], load: () => import("./mailserver/index").then((m) => m.generate), }, + { + id: "erpnext", + name: "ERPNext", + version: "15.35.1", + description: "Free and Open Source Enterprise Resource Planning (ERP) ", + logo: "erpnext.png", + links: { + github: "https://github.com/frappe/erpnext", + website: "https://erpnext.com/", + docs: "https://sashagoncharov19.github.io/dokploy-template-docs/erpnext", + }, + tags: ["self-hosted", "open-source", "analytics"], + load: () => import("./erpnext/index").then((m) => m.generate), + }, ]; From 15a76d263982cb1dec8c90d01848008209216ce8 Mon Sep 17 00:00:00 2001 From: vishalkadam47 Date: Thu, 17 Oct 2024 15:45:27 +0530 Subject: [PATCH 005/243] feat: Add server-level GPU support for Docker Swarm deployments and API endpoint for setup --- apps/dokploy/server/api/trpc.ts | 8 ++++++++ packages/server/src/constants/index.ts | 3 +++ packages/server/src/utils/gpu-setup.ts | 9 +++++++++ 3 files changed, 20 insertions(+) create mode 100644 packages/server/src/utils/gpu-setup.ts diff --git a/apps/dokploy/server/api/trpc.ts b/apps/dokploy/server/api/trpc.ts index d37315c33..8aec99ec1 100644 --- a/apps/dokploy/server/api/trpc.ts +++ b/apps/dokploy/server/api/trpc.ts @@ -21,6 +21,7 @@ import { import type { Session, User } from "lucia"; import superjson from "superjson"; import { ZodError } from "zod"; +import { setupGPUSupport } from '@dokploy/server/src/utils/gpu-setup'; /** * 1. CONTEXT @@ -208,3 +209,10 @@ export const adminProcedure = t.procedure.use(({ ctx, next }) => { }, }); }); + +const appRouter = t.router({ + setupGPU: t.procedure.mutation(async () => { + await setupGPUSupport(); + return { success: true }; + }), + }); \ No newline at end of file diff --git a/packages/server/src/constants/index.ts b/packages/server/src/constants/index.ts index f2f1a4d88..fd89a53da 100644 --- a/packages/server/src/constants/index.ts +++ b/packages/server/src/constants/index.ts @@ -37,3 +37,6 @@ export const paths = (isServer = false) => { REGISTRY_PATH: `${BASE_PATH}/registry`, }; }; + +export const GPU_ENABLED = process.env.GPU_ENABLED === 'true'; +export const GPU_RESOURCE_NAME = 'DOCKER_RESOURCE_GPU'; \ No newline at end of file diff --git a/packages/server/src/utils/gpu-setup.ts b/packages/server/src/utils/gpu-setup.ts new file mode 100644 index 000000000..459c3395d --- /dev/null +++ b/packages/server/src/utils/gpu-setup.ts @@ -0,0 +1,9 @@ +import { docker } from '../constants'; + +export async function setupGPUSupport() { + await docker.swarmUpdate({ + TaskDefaults: { + GenericResources: [{ DiscreteResourceSpec: { Kind: 'gpu', Value: 1 } }] + } + }); +} \ No newline at end of file From e52a0fc9d4785a881c974ab6d0d7ec35967f214e Mon Sep 17 00:00:00 2001 From: vishalkadam47 Date: Fri, 18 Oct 2024 04:55:37 +0530 Subject: [PATCH 006/243] feat: Added Blender template --- apps/dokploy/public/templates/blender.svg | 153 ++++++++++++++++++ .../templates/blender/docker-compose.yml | 37 +++++ apps/dokploy/templates/blender/index.ts | 34 ++++ apps/dokploy/templates/templates.ts | 14 ++ 4 files changed, 238 insertions(+) create mode 100644 apps/dokploy/public/templates/blender.svg create mode 100644 apps/dokploy/templates/blender/docker-compose.yml create mode 100644 apps/dokploy/templates/blender/index.ts diff --git a/apps/dokploy/public/templates/blender.svg b/apps/dokploy/public/templates/blender.svg new file mode 100644 index 000000000..e59079f5b --- /dev/null +++ b/apps/dokploy/public/templates/blender.svg @@ -0,0 +1,153 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/dokploy/templates/blender/docker-compose.yml b/apps/dokploy/templates/blender/docker-compose.yml new file mode 100644 index 000000000..bc3de4b7f --- /dev/null +++ b/apps/dokploy/templates/blender/docker-compose.yml @@ -0,0 +1,37 @@ +version: "3.8" + +services: + blender: + image: lscr.io/linuxserver/blender:latest + privileged: true + container_name: blender + security_opt: + - seccomp:unconfined #optional + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: + - compute + - video + - graphics + - utility + environment: + - NVIDIA_VISIBLE_DEVICES=all + - NVIDIA_DRIVER_CAPABILITIES=all + - PUID=1000 + - PGID=1000 + - TZ=Etc/UTC + - SUBFOLDER=/ #optional + volumes: + - blender:/config + ports: + - 3000:3000 + - 3001:3001 + restart: unless-stopped + shm_size: 1gb + +volumes: + blender: null diff --git a/apps/dokploy/templates/blender/index.ts b/apps/dokploy/templates/blender/index.ts new file mode 100644 index 000000000..088e6fcce --- /dev/null +++ b/apps/dokploy/templates/blender/index.ts @@ -0,0 +1,34 @@ +import { + generateHash, + generateRandomDomain, + type Template, + type Schema, + type DomainSchema, +} from "../utils"; + +export function generate(schema: Schema): Template { + const mainServiceHash = generateHash(schema.projectName); + const mainDomain = generateRandomDomain(schema); + + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 3000, + serviceName: "blender", + }, + ]; + + const envs = [ + `PUID=1000`, + `PGID=1000`, + `TZ=Etc/UTC`, + `SUBFOLDER=/`, + `NVIDIA_VISIBLE_DEVICES=all`, + `NVIDIA_DRIVER_CAPABILITIES=all`, + ]; + + return { + envs, + domains, + }; +} diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index afe9d1b69..e5acb3905 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -512,4 +512,18 @@ export const templates: TemplateData[] = [ tags: ["self-hosted", "email", "webmail"], load: () => import("./roundcube/index").then((m) => m.generate), }, + { + id: "blender", + name: "Blender", + version: "latest", + description: "Blender is a free and open-source 3D creation suite. It supports the entire 3D pipeline—modeling, rigging, animation, simulation, rendering, compositing and motion tracking, video editing and 2D animation pipeline.", + logo: "blender.svg", + links: { + github: "https://github.com/linuxserver/docker-blender", + website: "https://www.blender.org/", + docs: "https://docs.blender.org/", + }, + tags: ["3d", "rendering", "animation"], + load: () => import("./blender/index").then((m) => m.generate), + }, ]; From 4431f5601ae71240d9a15286b8ba6462af5fba5b Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Mon, 21 Oct 2024 19:05:51 +0000 Subject: [PATCH 007/243] fix: run biome check --- apps/dokploy/templates/templates.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 3f5f8c25f..e1a1aae89 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -525,8 +525,8 @@ export const templates: TemplateData[] = [ }, tags: ["self-hosted", "open-source", "analytics"], load: () => import("./erpnext/index").then((m) => m.generate), - }, - { + }, + { id: "roundcube", name: "Roundcube", version: "1.6.9", From 5a440d934d5ac1d5aae8940a3fb507ae761dacbb Mon Sep 17 00:00:00 2001 From: vishalkadam47 Date: Fri, 25 Oct 2024 02:32:50 +0530 Subject: [PATCH 008/243] fix: Remove privileged mode and seccomp option, update runtime to nvidia --- apps/dokploy/templates/blender/docker-compose.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/apps/dokploy/templates/blender/docker-compose.yml b/apps/dokploy/templates/blender/docker-compose.yml index bc3de4b7f..90fa8da85 100644 --- a/apps/dokploy/templates/blender/docker-compose.yml +++ b/apps/dokploy/templates/blender/docker-compose.yml @@ -3,10 +3,8 @@ version: "3.8" services: blender: image: lscr.io/linuxserver/blender:latest - privileged: true container_name: blender - security_opt: - - seccomp:unconfined #optional + runtime: nvidia deploy: resources: reservations: @@ -14,10 +12,7 @@ services: - driver: nvidia count: all capabilities: - - compute - - video - - graphics - - utility + - gpu environment: - NVIDIA_VISIBLE_DEVICES=all - NVIDIA_DRIVER_CAPABILITIES=all @@ -25,13 +20,8 @@ services: - PGID=1000 - TZ=Etc/UTC - SUBFOLDER=/ #optional - volumes: - - blender:/config ports: - 3000:3000 - 3001:3001 restart: unless-stopped shm_size: 1gb - -volumes: - blender: null From 3e467959c9232b332dbbaaafae66d8c0cd76097b Mon Sep 17 00:00:00 2001 From: vishalkadam47 Date: Sun, 27 Oct 2024 22:00:08 +0530 Subject: [PATCH 009/243] refactor: Update docker-compose.yml to remove port mapping and remove GPU constants from index.ts --- apps/dokploy/templates/blender/docker-compose.yml | 4 ++-- packages/server/src/constants/index.ts | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/dokploy/templates/blender/docker-compose.yml b/apps/dokploy/templates/blender/docker-compose.yml index 90fa8da85..da769c6bc 100644 --- a/apps/dokploy/templates/blender/docker-compose.yml +++ b/apps/dokploy/templates/blender/docker-compose.yml @@ -21,7 +21,7 @@ services: - TZ=Etc/UTC - SUBFOLDER=/ #optional ports: - - 3000:3000 - - 3001:3001 + - 3000 + - 3001 restart: unless-stopped shm_size: 1gb diff --git a/packages/server/src/constants/index.ts b/packages/server/src/constants/index.ts index fd89a53da..be2a72ded 100644 --- a/packages/server/src/constants/index.ts +++ b/packages/server/src/constants/index.ts @@ -36,7 +36,4 @@ export const paths = (isServer = false) => { MONITORING_PATH: `${BASE_PATH}/monitoring`, REGISTRY_PATH: `${BASE_PATH}/registry`, }; -}; - -export const GPU_ENABLED = process.env.GPU_ENABLED === 'true'; -export const GPU_RESOURCE_NAME = 'DOCKER_RESOURCE_GPU'; \ No newline at end of file +}; \ No newline at end of file From b7d45341bcc230e80175ca81a8df79a575ec8d9a Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Sun, 27 Oct 2024 18:58:38 +0000 Subject: [PATCH 010/243] feat: influxdb template --- apps/dokploy/public/templates/influxdb.png | Bin 0 -> 8774 bytes .../templates/influxdb/docker-compose.yml | 11 ++++++++++ apps/dokploy/templates/influxdb/index.ts | 19 ++++++++++++++++++ apps/dokploy/templates/templates.ts | 15 ++++++++++++++ 4 files changed, 45 insertions(+) create mode 100644 apps/dokploy/public/templates/influxdb.png create mode 100644 apps/dokploy/templates/influxdb/docker-compose.yml create mode 100644 apps/dokploy/templates/influxdb/index.ts diff --git a/apps/dokploy/public/templates/influxdb.png b/apps/dokploy/public/templates/influxdb.png new file mode 100644 index 0000000000000000000000000000000000000000..8fc62a7fb8280d7ec7089b26d74d19a71a968161 GIT binary patch literal 8774 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 I+BEck0G^-Jk^lez literal 0 HcmV?d00001 diff --git a/apps/dokploy/templates/influxdb/docker-compose.yml b/apps/dokploy/templates/influxdb/docker-compose.yml new file mode 100644 index 000000000..1327c6028 --- /dev/null +++ b/apps/dokploy/templates/influxdb/docker-compose.yml @@ -0,0 +1,11 @@ +services: + influxdb: + image: influxdb:2.7.10 + restart: unless-stopped + volumes: + - influxdb2-data:/var/lib/influxdb2 + - influxdb2-config:/etc/influxdb2 + +volumes: + influxdb2-data: + influxdb2-config: \ No newline at end of file diff --git a/apps/dokploy/templates/influxdb/index.ts b/apps/dokploy/templates/influxdb/index.ts new file mode 100644 index 000000000..550b680e7 --- /dev/null +++ b/apps/dokploy/templates/influxdb/index.ts @@ -0,0 +1,19 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const domains: DomainSchema[] = [ + { + host: generateRandomDomain(schema), + port: 8086, + serviceName: "influxdb", + }, + ]; + return { + domains, + }; +} diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 372af8bb4..62e8cbd74 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -557,4 +557,19 @@ export const templates: TemplateData[] = [ tags: ["cloud", "monitoring"], load: () => import("./portainer/index").then((m) => m.generate), }, + { + id: "influxdb", + name: "InfluxDB", + version: "2.7.10", + description: + "InfluxDB 2.7 is the platform purpose-built to collect, store, process and visualize time series data.", + logo: "influxdb.png", + links: { + github: "https://github.com/influxdata/influxdb", + website: "https://www.influxdata.com/", + docs: "https://docs.influxdata.com/influxdb/v2/", + }, + tags: ["self-hosted", "open-source", "storage", "database"], + load: () => import("./influxdb/index").then((m) => m.generate), + }, ]; From 15051a1bc2f4ecc009f8bd69c26d072fd75f2250 Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Sun, 27 Oct 2024 19:48:07 +0000 Subject: [PATCH 011/243] feat: infisical template --- apps/dokploy/public/templates/infisical.jpg | Bin 0 -> 3469 bytes .../templates/infisical/docker-compose.yml | 87 ++++++++++++++++ apps/dokploy/templates/infisical/index.ts | 93 ++++++++++++++++++ apps/dokploy/templates/templates.ts | 15 +++ 4 files changed, 195 insertions(+) create mode 100644 apps/dokploy/public/templates/infisical.jpg create mode 100644 apps/dokploy/templates/infisical/docker-compose.yml create mode 100644 apps/dokploy/templates/infisical/index.ts diff --git a/apps/dokploy/public/templates/infisical.jpg b/apps/dokploy/public/templates/infisical.jpg new file mode 100644 index 0000000000000000000000000000000000000000..404f5811924b4544af0b9674402fe75d4e0f1ea6 GIT binary patch literal 3469 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 literal 0 HcmV?d00001 diff --git a/apps/dokploy/templates/infisical/docker-compose.yml b/apps/dokploy/templates/infisical/docker-compose.yml new file mode 100644 index 000000000..3baca9265 --- /dev/null +++ b/apps/dokploy/templates/infisical/docker-compose.yml @@ -0,0 +1,87 @@ +services: + db-migration: + depends_on: + db: + condition: service_healthy + image: infisical/infisical:v0.90.1-postgres + environment: + - NODE_ENV=production + - ENCRYPTION_KEY + - AUTH_SECRET + - SITE_URL + - DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} + - REDIS_URL=redis://redis:6379 + - SMTP_HOST + - SMTP_PORT + - SMTP_FROM_NAME + - SMTP_USERNAME + - SMTP_PASSWORD + - SMTP_SECURE=true + command: npm run migration:latest + pull_policy: always + networks: + - dokploy-network + + backend: + restart: unless-stopped + depends_on: + db: + condition: service_healthy + redis: + condition: service_started + db-migration: + condition: service_completed_successfully + image: infisical/infisical:v0.90.1-postgres + pull_policy: always + environment: + - NODE_ENV=production + - ENCRYPTION_KEY + - AUTH_SECRET + - SITE_URL + - DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} + - REDIS_URL=redis://redis:6379 + - SMTP_HOST + - SMTP_PORT + - SMTP_FROM_NAME + - SMTP_USERNAME + - SMTP_PASSWORD + - SMTP_SECURE=true + networks: + - dokploy-network + + redis: + image: redis:7.4.1 + env_file: .env + restart: always + environment: + - ALLOW_EMPTY_PASSWORD=yes + networks: + - dokploy-network + volumes: + - redis_infisical_data:/data + + db: + image: postgres:14-alpine + restart: always + environment: + - POSTGRES_PASSWORD + - POSTGRES_USER + - POSTGRES_DB + volumes: + - pg_infisical_data:/var/lib/postgresql/data + networks: + - dokploy-network + healthcheck: + test: "pg_isready --username=${POSTGRES_USER} && psql --username=${POSTGRES_USER} --list" + interval: 5s + timeout: 10s + retries: 10 + +volumes: + pg_infisical_data: + redis_infisical_data: + +networks: + dokploy-network: + external: true + diff --git a/apps/dokploy/templates/infisical/index.ts b/apps/dokploy/templates/infisical/index.ts new file mode 100644 index 000000000..6d2127740 --- /dev/null +++ b/apps/dokploy/templates/infisical/index.ts @@ -0,0 +1,93 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const domains: DomainSchema[] = [ + { + host: generateRandomDomain(schema), + port: 8080, + serviceName: "backend", + }, + ]; + + const envs = [ + "# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION", + "ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218", + "", + "# THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION", + "AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE=", + "# Postgres creds", + "POSTGRES_PASSWORD=infisical", + "POSTGRES_USER=infisical", + "POSTGRES_DB=infisical", + "", + "# Website URL", + "# Required", + "SITE_URL=http://localhost:8080", + "", + "# Mail/SMTP", + "SMTP_HOST=", + "SMTP_PORT=", + "SMTP_NAME=", + "SMTP_USERNAME=", + "SMTP_PASSWORD=", + "", + "# Integration", + "# Optional only if integration is used", + "CLIENT_ID_HEROKU=", + "CLIENT_ID_VERCEL=", + "CLIENT_ID_NETLIFY=", + "CLIENT_ID_GITHUB=", + "CLIENT_ID_GITHUB_APP=", + "CLIENT_SLUG_GITHUB_APP=", + "CLIENT_ID_GITLAB=", + "CLIENT_ID_BITBUCKET=", + "CLIENT_SECRET_HEROKU=", + "CLIENT_SECRET_VERCEL=", + "CLIENT_SECRET_NETLIFY=", + "CLIENT_SECRET_GITHUB=", + "CLIENT_SECRET_GITHUB_APP=", + "CLIENT_SECRET_GITLAB=", + "CLIENT_SECRET_BITBUCKET=", + "CLIENT_SLUG_VERCEL=", + "", + "CLIENT_PRIVATE_KEY_GITHUB_APP=", + "CLIENT_APP_ID_GITHUB_APP=", + "", + "# Sentry (optional) for monitoring errors", + "SENTRY_DSN=", + "", + "# Infisical Cloud-specific configs", + "# Ignore - Not applicable for self-hosted version", + "POSTHOG_HOST=", + "POSTHOG_PROJECT_API_KEY=", + "", + "# SSO-specific variables", + "CLIENT_ID_GOOGLE_LOGIN=", + "CLIENT_SECRET_GOOGLE_LOGIN=", + "", + "CLIENT_ID_GITHUB_LOGIN=", + "CLIENT_SECRET_GITHUB_LOGIN=", + "", + "CLIENT_ID_GITLAB_LOGIN=", + "CLIENT_SECRET_GITLAB_LOGIN=", + "", + "CAPTCHA_SECRET=", + "", + "NEXT_PUBLIC_CAPTCHA_SITE_KEY=", + "", + "PLAIN_API_KEY=", + "PLAIN_WISH_LABEL_IDS=", + "", + "SSL_CLIENT_CERTIFICATE_HEADER_KEY=", + ]; + + return { + domains, + envs, + }; +} diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 62e8cbd74..2ebb5312f 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -572,4 +572,19 @@ export const templates: TemplateData[] = [ tags: ["self-hosted", "open-source", "storage", "database"], load: () => import("./influxdb/index").then((m) => m.generate), }, + { + id: "infisical", + name: "Infisical", + version: "0.90.1", + description: + "All-in-one platform to securely manage application configuration and secrets across your team and infrastructure.", + logo: "infisical.jpg", + links: { + github: "https://github.com/Infisical/infisical", + website: "https://infisical.com/", + docs: "https://infisical.com/docs/documentation/getting-started/introduction", + }, + tags: ["self-hosted", "open-source"], + load: () => import("./infisical/index").then((m) => m.generate), + }, ]; From 2a5a67e63ca6a8c5b3c9be55b9cc8335fa3b722c Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Sun, 27 Oct 2024 20:08:12 +0000 Subject: [PATCH 012/243] feat: docmost template --- .../templates/docmost/docker-compose.yml | 47 +++++++++++++++++++ apps/dokploy/templates/docmost/index.ts | 29 ++++++++++++ apps/dokploy/templates/templates.ts | 15 ++++++ 3 files changed, 91 insertions(+) create mode 100644 apps/dokploy/templates/docmost/docker-compose.yml create mode 100644 apps/dokploy/templates/docmost/index.ts diff --git a/apps/dokploy/templates/docmost/docker-compose.yml b/apps/dokploy/templates/docmost/docker-compose.yml new file mode 100644 index 000000000..a6ebbd4f4 --- /dev/null +++ b/apps/dokploy/templates/docmost/docker-compose.yml @@ -0,0 +1,47 @@ +version: "3" + +services: + docmost: + image: docmost/docmost:0.4.1 + depends_on: + - db + - redis + environment: + - APP_URL + - APP_SECRET + - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public + - REDIS_URL=redis://redis:6379 + restart: unless-stopped + networks: + - dokploy-network + volumes: + - docmost:/app/data/storage + + db: + image: postgres:16-alpine + environment: + - POSTGRES_DB + - POSTGRES_USER + - POSTGRES_PASSWORD + restart: unless-stopped + networks: + - dokploy-network + volumes: + - db_docmost_data:/var/lib/postgresql/data + + redis: + image: redis:7.2-alpine + restart: unless-stopped + networks: + - dokploy-network + volumes: + - redis_docmost_data:/data + +networks: + dokploy-network: + external: true + +volumes: + docmost: + db_docmost_data: + redis_docmost_data: \ No newline at end of file diff --git a/apps/dokploy/templates/docmost/index.ts b/apps/dokploy/templates/docmost/index.ts new file mode 100644 index 000000000..16f7afa66 --- /dev/null +++ b/apps/dokploy/templates/docmost/index.ts @@ -0,0 +1,29 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const domains: DomainSchema[] = [ + { + host: generateRandomDomain(schema), + port: 3000, + serviceName: "docmost", + }, + ]; + + const envs = [ + "POSTGRES_DB=docmost", + "POSTGRES_USER=docmost", + "POSTGRES_PASSWORD=STRONG_DB_PASSWORD", + "APP_URL=http://localhost:3000", + "APP_SECRET=VERY_STRONG_SECRET", + ]; + + return { + domains, + envs, + }; +} diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 2ebb5312f..78b9f67eb 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -587,4 +587,19 @@ export const templates: TemplateData[] = [ tags: ["self-hosted", "open-source"], load: () => import("./infisical/index").then((m) => m.generate), }, + { + id: "docmost", + name: "Docmost", + version: "0.4.1", + description: + "Docmost, an open-source collaborative wiki and documentation software.", + logo: "", + links: { + github: "https://github.com/docmost/docmost", + website: "https://docmost.com/", + docs: "https://docmost.com/docs/", + }, + tags: ["self-hosted", "open-source", "manager"], + load: () => import("./docmost/index").then((m) => m.generate), + }, ]; From 527c01e7dca36018e0555bec2191dd545cb314cf Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Sun, 27 Oct 2024 20:22:29 +0000 Subject: [PATCH 013/243] feat: vaultwarden template --- apps/dokploy/templates/templates.ts | 18 ++++++++++-- .../templates/vaultwarden/docker-compose.yml | 14 ++++++++++ apps/dokploy/templates/vaultwarden/index.ts | 28 +++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 apps/dokploy/templates/vaultwarden/docker-compose.yml create mode 100644 apps/dokploy/templates/vaultwarden/index.ts diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 78b9f67eb..c3a8d718d 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -218,7 +218,6 @@ export const templates: TemplateData[] = [ version: "v1.5.6", description: "Documenso is the open source alternative to DocuSign for signing documents digitally", - links: { github: "https://github.com/documenso/documenso", website: "https://documenso.com/", @@ -593,7 +592,7 @@ export const templates: TemplateData[] = [ version: "0.4.1", description: "Docmost, an open-source collaborative wiki and documentation software.", - logo: "", + logo: "docmost.png", links: { github: "https://github.com/docmost/docmost", website: "https://docmost.com/", @@ -602,4 +601,19 @@ export const templates: TemplateData[] = [ tags: ["self-hosted", "open-source", "manager"], load: () => import("./docmost/index").then((m) => m.generate), }, + { + id: "vaultwarden", + name: "Vaultwarden", + version: "1.32.3", + description: + "Unofficial Bitwarden compatible server written in Rust, formerly known as bitwarden_rs", + logo: "vaultwarden.png", + links: { + github: "https://github.com/dani-garcia/vaultwarden", + website: "", + docs: "https://github.com/dani-garcia/vaultwarden/wiki", + }, + tags: ["open-source"], + load: () => import("./vaultwarden/index").then((m) => m.generate), + }, ]; diff --git a/apps/dokploy/templates/vaultwarden/docker-compose.yml b/apps/dokploy/templates/vaultwarden/docker-compose.yml new file mode 100644 index 000000000..4ccc5cbf8 --- /dev/null +++ b/apps/dokploy/templates/vaultwarden/docker-compose.yml @@ -0,0 +1,14 @@ +services: + vaultwarden: + image: vaultwarden/server:1.32.3 + restart: always + environment: + DOMAIN: ${DOMAIN} + SIGNUPS_ALLOWED: ${SIGNUPS_ALLOWED} + volumes: + - vaultwarden:/data + ports: + - 11001:80 + +volumes: + vaultwarden: \ No newline at end of file diff --git a/apps/dokploy/templates/vaultwarden/index.ts b/apps/dokploy/templates/vaultwarden/index.ts new file mode 100644 index 000000000..66aa14657 --- /dev/null +++ b/apps/dokploy/templates/vaultwarden/index.ts @@ -0,0 +1,28 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const domains: DomainSchema[] = [ + { + host: generateRandomDomain(schema), + port: 80, + serviceName: "vaultwarden", + }, + ]; + + const envs = [ + "# Deactivate this with 'false' after you have created your account so that no strangers can register", + "SIGNUPS_ALLOWED=true", + "# required when using a reverse proxy; your domain; vaultwarden needs to know it's https to work properly with attachments", + "DOMAIN=https://vaultwarden.example.com", + ]; + + return { + domains, + envs, + }; +} From bbef99c3c258eb163157f39e96f4e6320404e90b Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Sun, 27 Oct 2024 20:49:29 +0000 Subject: [PATCH 014/243] feat: hi.events template --- apps/dokploy/public/templates/hi-events.svg | 39 ++++++++++++++++ .../templates/hi-events/docker-compose.yml | 45 +++++++++++++++++++ apps/dokploy/templates/hi-events/index.ts | 41 +++++++++++++++++ apps/dokploy/templates/templates.ts | 15 +++++++ 4 files changed, 140 insertions(+) create mode 100644 apps/dokploy/public/templates/hi-events.svg create mode 100644 apps/dokploy/templates/hi-events/docker-compose.yml create mode 100644 apps/dokploy/templates/hi-events/index.ts diff --git a/apps/dokploy/public/templates/hi-events.svg b/apps/dokploy/public/templates/hi-events.svg new file mode 100644 index 000000000..0e373509b --- /dev/null +++ b/apps/dokploy/public/templates/hi-events.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/dokploy/templates/hi-events/docker-compose.yml b/apps/dokploy/templates/hi-events/docker-compose.yml new file mode 100644 index 000000000..0ce5b7e75 --- /dev/null +++ b/apps/dokploy/templates/hi-events/docker-compose.yml @@ -0,0 +1,45 @@ +services: + all-in-one: + image: daveearley/hi.events-all-in-one:v0.8.0-beta.1 + restart: always + environment: + - VITE_FRONTEND_URL=https://${DOMAIN} + - APP_FRONTEND_URL=https://${DOMAIN} + - VITE_API_URL_CLIENT=https://${DOMAIN}/api + - VITE_API_URL_SERVER=http://localhost:80/api + - VITE_STRIPE_PUBLISHABLE_KEY + - LOG_CHANNEL=stderr + - QUEUE_CONNECTION=sync + - MAIL_MAILER=array + - APP_KEY + - JWT_SECRET + - FILESYSTEM_PUBLIC_DISK=public + - FILESYSTEM_PRIVATE_DISK=local + - APP_CDN_URL=https://${DOMAIN}/storage + - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} + - MAIL_MAILER + - MAIL_HOST + - MAIL_PORT + - MAIL_FROM_ADDRESS + - MAIL_FROM_NAME + depends_on: + - postgres + + postgres: + image: elestio/postgres:16 + restart: always + networks: + - dokploy-network + environment: + - POSTGRES_DB + - POSTGRES_USER + - POSTGRES_PASSWORD + volumes: + - pg_hi-events_data:/var/lib/postgresql/data + +networks: + dokploy-network: + external: true + +volumes: + pg_hi-events_data: \ No newline at end of file diff --git a/apps/dokploy/templates/hi-events/index.ts b/apps/dokploy/templates/hi-events/index.ts new file mode 100644 index 000000000..f799bb737 --- /dev/null +++ b/apps/dokploy/templates/hi-events/index.ts @@ -0,0 +1,41 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const domains: DomainSchema[] = [ + { + host: generateRandomDomain(schema), + port: 80, + serviceName: "all-in-one", + }, + ]; + + const envs = [ + "# change domain here", + "DOMAIN=my-events.com", + "", + "POSTGRES_DB=hievents", + "POSTGRES_USER=hievents", + "POSTGRES_PASSWORD=VERY_STRONG_PASSWORD", + "", + "VITE_STRIPE_PUBLISHABLE_KEY=", + "", + "APP_KEY=my-app-key", + "JWT_SECRET=STRONG_JWT_SECRET", + "", + "MAIL_MAILER=", + "MAIL_HOST=", + "MAIL_PORT=", + "MAIL_FROM_ADDRESS=", + "MAIL_FROM_NAME=", + ]; + + return { + domains, + envs, + }; +} diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index c3a8d718d..7f024d6b5 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -616,4 +616,19 @@ export const templates: TemplateData[] = [ tags: ["open-source"], load: () => import("./vaultwarden/index").then((m) => m.generate), }, + { + id: "hi-events", + name: "Hi.events", + version: "0.8.0-beta.1", + description: + "Hi.Events is a self-hosted event management and ticket selling platform that allows you to create, manage and promote events easily.", + logo: "hi-events.svg", + links: { + github: "https://github.com/HiEventsDev/hi.events", + website: "https://hi.events/", + docs: "https://hi.events/docs", + }, + tags: ["self-hosted", "open-source", "manager"], + load: () => import("./hi-events/index").then((m) => m.generate), + }, ]; From adea44093128615af6cf80e0ba5c8d1f44622dbf Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Sun, 27 Oct 2024 21:12:04 +0000 Subject: [PATCH 015/243] feat: windows os template --- apps/dokploy/public/templates/windows.png | Bin 0 -> 252973 bytes apps/dokploy/templates/templates.ts | 14 +++++++ .../templates/windows/docker-compose.yml | 19 +++++++++ apps/dokploy/templates/windows/index.ts | 39 ++++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 apps/dokploy/public/templates/windows.png create mode 100644 apps/dokploy/templates/windows/docker-compose.yml create mode 100644 apps/dokploy/templates/windows/index.ts diff --git a/apps/dokploy/public/templates/windows.png b/apps/dokploy/public/templates/windows.png new file mode 100644 index 0000000000000000000000000000000000000000..61ced4559512d3eeb3766c30cb7410dc18e6720b GIT binary patch literal 252973 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

GB^I?z0S@%7#pq}`PqYn2wT}j5 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

d2 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�!@758H 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 literal 0 HcmV?d00001 diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 7f024d6b5..c4e5e0950 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -631,4 +631,18 @@ export const templates: TemplateData[] = [ tags: ["self-hosted", "open-source", "manager"], load: () => import("./hi-events/index").then((m) => m.generate), }, + { + id: "windows", + name: "Windows (dockerized)", + version: "4.00", + description: "Windows inside a Docker container.", + logo: "windows.png", + links: { + github: "https://github.com/dockur/windows", + website: "", + docs: "https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-use-it", + }, + tags: ["self-hosted", "open-source", "os"], + load: () => import("./windows/index").then((m) => m.generate), + }, ]; diff --git a/apps/dokploy/templates/windows/docker-compose.yml b/apps/dokploy/templates/windows/docker-compose.yml new file mode 100644 index 000000000..6527163a2 --- /dev/null +++ b/apps/dokploy/templates/windows/docker-compose.yml @@ -0,0 +1,19 @@ +services: + windows: + image: dockurr/windows:4.00 + ports: + - "8006:8006" + volumes: + - win-storage:/storage + environment: + - VERSION + - KVM + devices: + # If in .env string 'KVM=N' is not commented, you need to comment line below + - /dev/kvm + cap_add: + - NET_ADMIN + stop_grace_period: 2m + +volumes: + win-storage: \ No newline at end of file diff --git a/apps/dokploy/templates/windows/index.ts b/apps/dokploy/templates/windows/index.ts new file mode 100644 index 000000000..a67de7481 --- /dev/null +++ b/apps/dokploy/templates/windows/index.ts @@ -0,0 +1,39 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const domains: DomainSchema[] = [ + { + host: generateRandomDomain(schema), + port: 8006, + serviceName: "windows", + }, + ]; + + const envs = [ + "# https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-select-the-windows-version", + "VERSION=win11", + "", + "# Uncomment this if your PC/VM or etc does not support virtualization technology", + "# KVM=N", + "", + "DISK_SIZE=64G", + "RAM_SIZE=4G", + "CPU_CORES=4", + "", + "USERNAME=Dokploy", + "PASSWORD=", + "", + "# https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-select-the-windows-language", + "LANGUAGE=English", + ]; + + return { + domains, + envs, + }; +} From 2821e43cdd32f8f71cdfb1273dde62f43cb2f242 Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Mon, 28 Oct 2024 00:23:56 +0000 Subject: [PATCH 016/243] feat: macos template --- apps/dokploy/public/templates/macos.png | Bin 0 -> 23554 bytes .../templates/macos/docker-compose.yml | 16 +++++++++ apps/dokploy/templates/macos/index.ts | 33 ++++++++++++++++++ apps/dokploy/templates/templates.ts | 16 ++++++++- .../templates/windows/docker-compose.yml | 2 -- apps/dokploy/templates/windows/index.ts | 2 +- 6 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 apps/dokploy/public/templates/macos.png create mode 100644 apps/dokploy/templates/macos/docker-compose.yml create mode 100644 apps/dokploy/templates/macos/index.ts diff --git a/apps/dokploy/public/templates/macos.png b/apps/dokploy/public/templates/macos.png new file mode 100644 index 0000000000000000000000000000000000000000..617122c87d1fb063097520c802c1e4428a0e689b GIT binary patch literal 23554 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 WW_>NhnJ(rwLwy-?vfUr^=YImVNvL`N literal 0 HcmV?d00001 diff --git a/apps/dokploy/templates/macos/docker-compose.yml b/apps/dokploy/templates/macos/docker-compose.yml new file mode 100644 index 000000000..585c1bf97 --- /dev/null +++ b/apps/dokploy/templates/macos/docker-compose.yml @@ -0,0 +1,16 @@ +services: + macos: + image: dockurr/macos:1.14 + volumes: + - macos-storage:/storage + environment: + - VERSION + devices: + # If in .env string 'KVM=N' is not commented, you need to comment line below + - /dev/kvm + cap_add: + - NET_ADMIN + stop_grace_period: 2m + +volumes: + macos-storage: \ No newline at end of file diff --git a/apps/dokploy/templates/macos/index.ts b/apps/dokploy/templates/macos/index.ts new file mode 100644 index 000000000..ebda41065 --- /dev/null +++ b/apps/dokploy/templates/macos/index.ts @@ -0,0 +1,33 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const domains: DomainSchema[] = [ + { + host: generateRandomDomain(schema), + port: 8006, + serviceName: "macos", + }, + ]; + + const envs = [ + "# https://github.com/dockur/macos?tab=readme-ov-file#how-do-i-select-the-macos-version", + "VERSION=15", + "", + "# Uncomment this if your PC/VM or etc does not support virtualization technology", + "# KVM=N", + "", + "DISK_SIZE=64G", + "RAM_SIZE=4G", + "CPU_CORES=2", + ]; + + return { + domains, + envs, + }; +} diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index c4e5e0950..ad42ccb15 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -635,7 +635,7 @@ export const templates: TemplateData[] = [ id: "windows", name: "Windows (dockerized)", version: "4.00", - description: "Windows inside a Docker container.", + description: "MacOS inside a Docker container.", logo: "windows.png", links: { github: "https://github.com/dockur/windows", @@ -645,4 +645,18 @@ export const templates: TemplateData[] = [ tags: ["self-hosted", "open-source", "os"], load: () => import("./windows/index").then((m) => m.generate), }, + { + id: "macos", + name: "MacOS (dockerized)", + version: "1.14", + description: "MacOS inside a Docker container.", + logo: "macos.png", + links: { + github: "https://github.com/dockur/macos", + website: "", + docs: "https://github.com/dockur/macos?tab=readme-ov-file#how-do-i-use-it", + }, + tags: ["self-hosted", "open-source", "os"], + load: () => import("./macos/index").then((m) => m.generate), + }, ]; diff --git a/apps/dokploy/templates/windows/docker-compose.yml b/apps/dokploy/templates/windows/docker-compose.yml index 6527163a2..8a0833702 100644 --- a/apps/dokploy/templates/windows/docker-compose.yml +++ b/apps/dokploy/templates/windows/docker-compose.yml @@ -1,8 +1,6 @@ services: windows: image: dockurr/windows:4.00 - ports: - - "8006:8006" volumes: - win-storage:/storage environment: diff --git a/apps/dokploy/templates/windows/index.ts b/apps/dokploy/templates/windows/index.ts index a67de7481..5e2728cb0 100644 --- a/apps/dokploy/templates/windows/index.ts +++ b/apps/dokploy/templates/windows/index.ts @@ -23,7 +23,7 @@ export function generate(schema: Schema): Template { "", "DISK_SIZE=64G", "RAM_SIZE=4G", - "CPU_CORES=4", + "CPU_CORES=2", "", "USERNAME=Dokploy", "PASSWORD=", From 02d52d63b94485bc3fc42431331cb472f67297b7 Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Mon, 28 Oct 2024 01:04:49 +0000 Subject: [PATCH 017/243] fix: uptime-kuma bump --- apps/dokploy/templates/gitea/docker-compose.yml | 4 ++-- apps/dokploy/templates/templates.ts | 2 +- apps/dokploy/templates/uptime-kuma/docker-compose.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/templates/gitea/docker-compose.yml b/apps/dokploy/templates/gitea/docker-compose.yml index 679936fb7..72e0754e2 100644 --- a/apps/dokploy/templates/gitea/docker-compose.yml +++ b/apps/dokploy/templates/gitea/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.8" services: gitea: - image: gitea/gitea:1.22.2 + image: gitea/gitea:1.22.3 environment: - USER_UID=${USER_UID} - USER_GID=${USER_GID} @@ -21,7 +21,7 @@ services: - db db: - image: postgres:16 + image: postgres:17 restart: always environment: - POSTGRES_USER=gitea diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index ad42ccb15..cf83d6936 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -125,7 +125,7 @@ export const templates: TemplateData[] = [ { id: "uptime-kuma", name: "Uptime Kuma", - version: "1.21.4", + version: "1.23.15", description: "Uptime Kuma is a free and open source monitoring tool that allows you to monitor your websites and applications.", logo: "uptime-kuma.png", diff --git a/apps/dokploy/templates/uptime-kuma/docker-compose.yml b/apps/dokploy/templates/uptime-kuma/docker-compose.yml index ccd775526..0d4ade73c 100644 --- a/apps/dokploy/templates/uptime-kuma/docker-compose.yml +++ b/apps/dokploy/templates/uptime-kuma/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.8" services: uptime-kuma: - image: louislam/uptime-kuma:1 + image: louislam/uptime-kuma:1.23.15 restart: always volumes: - uptime-kuma-data:/app/data From bad11f13f522280acc880713e725d7c574b7aa1a Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Mon, 28 Oct 2024 01:05:09 +0000 Subject: [PATCH 018/243] fix: gitea bump --- apps/dokploy/templates/templates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index cf83d6936..682b82b84 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -484,7 +484,7 @@ export const templates: TemplateData[] = [ { id: "gitea", name: "Gitea", - version: "1.22.2", + version: "1.22.3", description: "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD.", logo: "gitea.png", From a4eb5c07e6655f7be964b35332bf5684857f944a Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Mon, 28 Oct 2024 01:09:29 +0000 Subject: [PATCH 019/243] fix: soketi bump --- apps/dokploy/templates/soketi/docker-compose.yml | 3 +-- apps/dokploy/templates/templates.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/templates/soketi/docker-compose.yml b/apps/dokploy/templates/soketi/docker-compose.yml index 1784cdc79..d38cbb086 100644 --- a/apps/dokploy/templates/soketi/docker-compose.yml +++ b/apps/dokploy/templates/soketi/docker-compose.yml @@ -2,8 +2,7 @@ version: "3" services: soketi: - image: quay.io/soketi/soketi:1.4-16-debian - container_name: soketi + image: quay.io/soketi/soketi:1.6.1-16-debian environment: SOKETI_DEBUG: "1" SOKETI_HOST: "0.0.0.0" diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 682b82b84..09d1273b4 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -440,7 +440,7 @@ export const templates: TemplateData[] = [ { id: "soketi", name: "Soketi", - version: "v1.4-16", + version: "v1.6.1-16", description: "Soketi is your simple, fast, and resilient open-source WebSockets server.", logo: "soketi.png", From 25803f371cf0e5773546ef8ff5bb2ab4045f90d0 Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Mon, 28 Oct 2024 14:05:26 +0000 Subject: [PATCH 020/243] feat: coder template --- apps/dokploy/public/templates/coder.svg | 1 + .../templates/coder/docker-compose.yml | 41 +++++++++++++++++++ apps/dokploy/templates/coder/index.ts | 30 ++++++++++++++ apps/dokploy/templates/templates.ts | 15 +++++++ 4 files changed, 87 insertions(+) create mode 100644 apps/dokploy/public/templates/coder.svg create mode 100644 apps/dokploy/templates/coder/docker-compose.yml create mode 100644 apps/dokploy/templates/coder/index.ts diff --git a/apps/dokploy/public/templates/coder.svg b/apps/dokploy/public/templates/coder.svg new file mode 100644 index 000000000..56d2f77c3 --- /dev/null +++ b/apps/dokploy/public/templates/coder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/dokploy/templates/coder/docker-compose.yml b/apps/dokploy/templates/coder/docker-compose.yml new file mode 100644 index 000000000..184db31d3 --- /dev/null +++ b/apps/dokploy/templates/coder/docker-compose.yml @@ -0,0 +1,41 @@ +services: + coder: + image: ghcr.io/coder/coder:v2.15.3 + networks: + - dokploy-network + ports: + - "7080:7080" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + group_add: + - "998" + depends_on: + db: + condition: service_healthy + environment: + - CODER_ACCESS_URL + - CODER_HTTP_ADDRESS + - CODER_PG_CONNECTION_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db/${POSTGRES_DB}?sslmode=disable + + db: + image: postgres:17 + networks: + - dokploy-network + environment: + - POSTGRES_PASSWORD + - POSTGRES_USER + - POSTGRES_DB + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}", + ] + interval: 5s + timeout: 5s + retries: 5 + volumes: + - db_coder_data:/var/lib/postgresql/data + +volumes: + db_coder_data: \ No newline at end of file diff --git a/apps/dokploy/templates/coder/index.ts b/apps/dokploy/templates/coder/index.ts new file mode 100644 index 000000000..c3f066d63 --- /dev/null +++ b/apps/dokploy/templates/coder/index.ts @@ -0,0 +1,30 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const domains: DomainSchema[] = [ + { + host: generateRandomDomain(schema), + port: 7080, + serviceName: "coder", + }, + ]; + + const envs = [ + "CODER_ACCESS_URL=", + "CODER_HTTP_ADDRESS=0.0.0.0:7080", + "", + "POSTGRES_DB=coder", + "POSTGRES_USER=coder", + "POSTGRES_PASSWORD=VERY_STRONG_PASSWORD", + ]; + + return { + domains, + envs, + }; +} diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 09d1273b4..e5219de6a 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -659,4 +659,19 @@ export const templates: TemplateData[] = [ tags: ["self-hosted", "open-source", "os"], load: () => import("./macos/index").then((m) => m.generate), }, + { + id: "coder", + name: "Coder", + version: "2.15.3", + description: + "Coder is an open-source cloud development environment (CDE) that you host in your cloud or on-premises.", + logo: "coder.svg", + links: { + github: "https://github.com/coder/coder", + website: "https://coder.com/", + docs: "https://coder.com/docs", + }, + tags: ["self-hosted", "open-source", "builder"], + load: () => import("./coder/index").then((m) => m.generate), + }, ]; From de02a00ce93e3083df6a372da8ab7c708619efb1 Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Mon, 28 Oct 2024 14:11:39 +0000 Subject: [PATCH 021/243] fix: coder template --- apps/dokploy/templates/coder/docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/dokploy/templates/coder/docker-compose.yml b/apps/dokploy/templates/coder/docker-compose.yml index 184db31d3..27bb14bd2 100644 --- a/apps/dokploy/templates/coder/docker-compose.yml +++ b/apps/dokploy/templates/coder/docker-compose.yml @@ -3,8 +3,6 @@ services: image: ghcr.io/coder/coder:v2.15.3 networks: - dokploy-network - ports: - - "7080:7080" volumes: - /var/run/docker.sock:/var/run/docker.sock group_add: From bba7d0c8280bf1f6661b797d29c50399980ec773 Mon Sep 17 00:00:00 2001 From: Oleksandr Honcharov <0976053529@ukr.net> Date: Fri, 1 Nov 2024 05:14:24 +0200 Subject: [PATCH 022/243] Update apps/dokploy/templates/templates.ts Co-authored-by: Bartek <38581479+Rei-x@users.noreply.github.com> --- apps/dokploy/templates/templates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index e5219de6a..739bc7144 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -635,7 +635,7 @@ export const templates: TemplateData[] = [ id: "windows", name: "Windows (dockerized)", version: "4.00", - description: "MacOS inside a Docker container.", + description: "Windows inside a Docker container.", logo: "windows.png", links: { github: "https://github.com/dockur/windows", From 3b197f3624ca772822ce90e7458b5fd0c70c4b23 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:53:21 -0600 Subject: [PATCH 023/243] fix(dokploy): add missing mount path in update volume --- .../advanced/volumes/update-volume.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx index 2d847fbe2..91e9befed 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx @@ -119,7 +119,7 @@ export const UpdateVolume = ({ } else if (typeForm === "file") { form.reset({ content: data.content || "", - mountPath: data.mountPath, + mountPath: "/", filePath: data.filePath || "", type: "file", }); @@ -296,15 +296,13 @@ export const UpdateVolume = ({ )} - - - + From 996d449f0fb52b32b6bc4616b16e90a0d10ed13a Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:53:41 -0600 Subject: [PATCH 024/243] chore(version): bump version --- apps/dokploy/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index a87c9e8c7..e312e522a 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.10.7", + "version": "v0.10.8", "private": true, "license": "Apache-2.0", "type": "module", From 1b56a6b4005b9d110f111323ca8a19c6bcaad800 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Fri, 1 Nov 2024 10:54:45 -0600 Subject: [PATCH 025/243] refactor(dokploy): revert template content --- .../templates/soketi/docker-compose.yml | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/apps/dokploy/templates/soketi/docker-compose.yml b/apps/dokploy/templates/soketi/docker-compose.yml index 2df681652..1784cdc79 100644 --- a/apps/dokploy/templates/soketi/docker-compose.yml +++ b/apps/dokploy/templates/soketi/docker-compose.yml @@ -1,19 +1,12 @@ -version: '3.3' -services: - stirling-pdf: - image: frooodle/s-pdf:0.30.1 - ports: - - '8080' - volumes: - - training-data:/usr/share/tessdata #Required for extra OCR languages - - extra-configs:/configs - # - /location/of/customFiles:/customFiles/ - # - /location/of/logs:/logs/ - environment: - - DOCKER_ENABLE_SECURITY=false - - INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false - - LANGS=en_GB +version: "3" -volumes: - training-data: {} - extra-configs: {} \ No newline at end of file +services: + soketi: + image: quay.io/soketi/soketi:1.4-16-debian + container_name: soketi + environment: + SOKETI_DEBUG: "1" + SOKETI_HOST: "0.0.0.0" + SOKETI_PORT: "6001" + SOKETI_METRICS_SERVER_PORT: "9601" + restart: unless-stopped From 2f8b89c8a61af24a12cb2a7e75b0d8adf8a7e6e2 Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Fri, 1 Nov 2024 22:10:49 +0000 Subject: [PATCH 026/243] fix: nocodb bump --- apps/dokploy/templates/nocodb/docker-compose.yml | 4 ++-- apps/dokploy/templates/templates.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/templates/nocodb/docker-compose.yml b/apps/dokploy/templates/nocodb/docker-compose.yml index 726cf5e61..3d5c9ee7a 100644 --- a/apps/dokploy/templates/nocodb/docker-compose.yml +++ b/apps/dokploy/templates/nocodb/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.8" services: nocodb: - image: nocodb/nocodb:0.251.1 + image: nocodb/nocodb:0.257.2 restart: always environment: NC_DB: "pg://root_db?u=postgres&p=password&d=root_db" @@ -11,7 +11,7 @@ services: - nc_data:/usr/app/data root_db: - image: postgres:14.7 + image: postgres:17 restart: always networks: - dokploy-network diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 38c2e5ea6..747acdc1b 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -230,7 +230,7 @@ export const templates: TemplateData[] = [ { id: "nocodb", name: "NocoDB", - version: "0.251.1", + version: "0.257.2", description: "NocoDB is an opensource Airtable alternative that turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart spreadsheet.", From b2cc5e58a3e9d90e3002e6460196a318fab8b44c Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 2 Nov 2024 01:28:13 -0600 Subject: [PATCH 027/243] refactor(dokploy): add missing project --- apps/dokploy/server/api/routers/project.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts index 744589028..81b600044 100644 --- a/apps/dokploy/server/api/routers/project.ts +++ b/apps/dokploy/server/api/routers/project.ts @@ -132,16 +132,20 @@ export const projectRouter = createTRPCRouter({ if (accesedProjects.length === 0) { return []; } + const query = await db.query.projects.findMany({ - where: sql`${projects.projectId} IN (${sql.join( - accesedProjects.map((projectId) => sql`${projectId}`), - sql`, `, - )})`, + where: and( + sql`${projects.projectId} IN (${sql.join( + accesedProjects.map((projectId) => sql`${projectId}`), + sql`, `, + )})`, + eq(projects.adminId, ctx.user.adminId), + ), with: { applications: { - where: and( - buildServiceFilter(applications.applicationId, accesedServices), - eq(projects.adminId, ctx.user.adminId), + where: buildServiceFilter( + applications.applicationId, + accesedServices, ), with: { domains: true }, }, From 1b6d8d803b34482ab56c692034ace63a4fb15d80 Mon Sep 17 00:00:00 2001 From: vishalkadam47 Date: Sat, 2 Nov 2024 15:15:58 +0530 Subject: [PATCH 028/243] feat: Added GPU support feature for Remote Server with setup and status checks, including API endpoints and utility functions --- .../settings/servers/setup-server.tsx | 12 +- apps/dokploy/server/api/routers/settings.ts | 60 ++++ apps/dokploy/server/api/trpc.ts | 9 - apps/dokploy/templates/blender/index.ts | 52 ++-- apps/dokploy/templates/templates.ts | 3 +- packages/server/src/constants/index.ts | 2 +- packages/server/src/index.ts | 1 + packages/server/src/utils/gpu-setup.ts | 268 +++++++++++++++++- 8 files changed, 361 insertions(+), 46 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx index 8bfcf4da2..119d4d29d 100644 --- a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx @@ -32,6 +32,7 @@ import Link from "next/link"; import { useState } from "react"; import { toast } from "sonner"; import { ShowDeployment } from "../../application/deployments/show-deployment"; +import { GPUSupport } from "./gpu-support"; interface Props { serverId: string; @@ -89,9 +90,10 @@ export const SetupServer = ({ serverId }: Props) => { ) : (

- + SSH Keys Deployments + GPU Setup {
+ +
+ +
+
)} diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index e1e635799..4a0008893 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -52,6 +52,10 @@ import { writeMainConfig, writeTraefikConfigInPath, } from "@dokploy/server"; +import { + checkGPUStatus, + setupGPUSupport, +} from "@dokploy/server/src/utils/gpu-setup"; import { generateOpenApiDocument } from "@dokploy/trpc-openapi"; import { TRPCError } from "@trpc/server"; import { sql } from "drizzle-orm"; @@ -650,6 +654,62 @@ export const settingsRouter = createTRPCRouter({ } return { status: "not_cloud" }; }), + setupGPU: adminProcedure + .input( + z.object({ + serverId: z.string(), + }), + ) + .mutation(async ({ input }) => { + try { + if (IS_CLOUD) { + return { success: true }; + } + + if (!input.serverId) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Server ID is required", + }); + } + + await setupGPUSupport(input.serverId); + return { success: true }; + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: + error instanceof Error + ? error.message + : "Failed to enable GPU support", + cause: error, + }); + } + }), + checkGPUStatus: adminProcedure + .input( + z.object({ + serverId: z.string().optional(), + }), + ) + .query(async ({ input }) => { + if (IS_CLOUD) { + return { + driverInstalled: false, + driverVersion: undefined, + gpuModel: undefined, + runtimeInstalled: false, + runtimeConfigured: false, + cudaSupport: undefined, + cudaVersion: undefined, + memoryInfo: undefined, + availableGPUs: 0, + swarmEnabled: false, + gpuResources: 0, + }; + } + return await checkGPUStatus(input.serverId); + }), }); // { // "Parallelism": 1, diff --git a/apps/dokploy/server/api/trpc.ts b/apps/dokploy/server/api/trpc.ts index 8aec99ec1..db4f7adfe 100644 --- a/apps/dokploy/server/api/trpc.ts +++ b/apps/dokploy/server/api/trpc.ts @@ -21,8 +21,6 @@ import { import type { Session, User } from "lucia"; import superjson from "superjson"; import { ZodError } from "zod"; -import { setupGPUSupport } from '@dokploy/server/src/utils/gpu-setup'; - /** * 1. CONTEXT * @@ -209,10 +207,3 @@ export const adminProcedure = t.procedure.use(({ ctx, next }) => { }, }); }); - -const appRouter = t.router({ - setupGPU: t.procedure.mutation(async () => { - await setupGPUSupport(); - return { success: true }; - }), - }); \ No newline at end of file diff --git a/apps/dokploy/templates/blender/index.ts b/apps/dokploy/templates/blender/index.ts index 088e6fcce..baf243e08 100644 --- a/apps/dokploy/templates/blender/index.ts +++ b/apps/dokploy/templates/blender/index.ts @@ -1,34 +1,34 @@ import { - generateHash, - generateRandomDomain, - type Template, - type Schema, - type DomainSchema, + type DomainSchema, + type Schema, + type Template, + generateHash, + generateRandomDomain, } from "../utils"; export function generate(schema: Schema): Template { - const mainServiceHash = generateHash(schema.projectName); - const mainDomain = generateRandomDomain(schema); + const mainServiceHash = generateHash(schema.projectName); + const mainDomain = generateRandomDomain(schema); - const domains: DomainSchema[] = [ - { - host: mainDomain, - port: 3000, - serviceName: "blender", - }, - ]; + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 3000, + serviceName: "blender", + }, + ]; - const envs = [ - `PUID=1000`, - `PGID=1000`, - `TZ=Etc/UTC`, - `SUBFOLDER=/`, - `NVIDIA_VISIBLE_DEVICES=all`, - `NVIDIA_DRIVER_CAPABILITIES=all`, - ]; + const envs = [ + `PUID=1000`, + `PGID=1000`, + `TZ=Etc/UTC`, + `SUBFOLDER=/`, + `NVIDIA_VISIBLE_DEVICES=all`, + `NVIDIA_DRIVER_CAPABILITIES=all`, + ]; - return { - envs, - domains, - }; + return { + envs, + domains, + }; } diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 40d493e57..115a1ecf3 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -516,7 +516,8 @@ export const templates: TemplateData[] = [ id: "blender", name: "Blender", version: "latest", - description: "Blender is a free and open-source 3D creation suite. It supports the entire 3D pipeline—modeling, rigging, animation, simulation, rendering, compositing and motion tracking, video editing and 2D animation pipeline.", + description: + "Blender is a free and open-source 3D creation suite. It supports the entire 3D pipeline—modeling, rigging, animation, simulation, rendering, compositing and motion tracking, video editing and 2D animation pipeline.", logo: "blender.svg", links: { github: "https://github.com/linuxserver/docker-blender", diff --git a/packages/server/src/constants/index.ts b/packages/server/src/constants/index.ts index be2a72ded..f2f1a4d88 100644 --- a/packages/server/src/constants/index.ts +++ b/packages/server/src/constants/index.ts @@ -36,4 +36,4 @@ export const paths = (isServer = false) => { MONITORING_PATH: `${BASE_PATH}/monitoring`, REGISTRY_PATH: `${BASE_PATH}/registry`, }; -}; \ No newline at end of file +}; diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 06f2bc879..90daec2de 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -118,3 +118,4 @@ export * from "./monitoring/utilts"; export * from "./db/validations/domain"; export * from "./db/validations/index"; +export * from "./utils/gpu-setup"; diff --git a/packages/server/src/utils/gpu-setup.ts b/packages/server/src/utils/gpu-setup.ts index 459c3395d..71f3bf0ff 100644 --- a/packages/server/src/utils/gpu-setup.ts +++ b/packages/server/src/utils/gpu-setup.ts @@ -1,9 +1,261 @@ -import { docker } from '../constants'; +import { docker } from "../constants"; +import { execAsync } from "../utils/process/execAsync"; +import { execAsyncRemote } from "../utils/process/execAsync"; +import { getRemoteDocker } from "./servers/remote-docker"; -export async function setupGPUSupport() { - await docker.swarmUpdate({ - TaskDefaults: { - GenericResources: [{ DiscreteResourceSpec: { Kind: 'gpu', Value: 1 } }] - } - }); -} \ No newline at end of file +interface GPUInfo { + driverInstalled: boolean; + driverVersion?: string; + gpuModel?: string; + runtimeInstalled: boolean; + runtimeConfigured: boolean; + cudaSupport: boolean; + cudaVersion?: string; + memoryInfo?: string; + availableGPUs: number; + swarmEnabled: boolean; + gpuResources: number; +} + +interface DiscreteResourceSpec { + Kind: string; + Value: number; +} + +interface NamedGenericResource { + NamedResourceSpec?: { Kind: string; Value: string }; + DiscreteResourceSpec?: DiscreteResourceSpec; +} + +export async function checkGPUStatus(serverId?: string): Promise { + try { + // Check NVIDIA Driver + let driverInstalled = false; + let driverVersion: string | undefined; + let availableGPUs = 0; + + try { + const driverCommand = + "nvidia-smi --query-gpu=driver_version --format=csv,noheader"; + const { stdout: nvidiaSmi } = serverId + ? await execAsyncRemote(serverId, driverCommand) + : await execAsync(driverCommand); + + driverVersion = nvidiaSmi.trim(); + if (driverVersion) { + driverInstalled = true; + const countCommand = + "nvidia-smi --query-gpu=gpu_name --format=csv,noheader | wc -l"; + const { stdout: gpuCount } = serverId + ? await execAsyncRemote(serverId, countCommand) + : await execAsync(countCommand); + + availableGPUs = Number.parseInt(gpuCount.trim(), 10); + } + } catch (error) { + console.debug("GPU driver check:", error); + } + + // Check Runtime Configuration + let runtimeInstalled = false; + let runtimeConfigured = false; + try { + const runtimeCommand = 'docker info --format "{{json .Runtimes}}"'; + const { stdout: runtimeInfo } = serverId + ? await execAsyncRemote(serverId, runtimeCommand) + : await execAsync(runtimeCommand); + + const runtimes = JSON.parse(runtimeInfo); + runtimeInstalled = "nvidia" in runtimes; + + // Check if it's the default runtime + const defaultCommand = 'docker info --format "{{.DefaultRuntime}}"'; + const { stdout: defaultRuntime } = serverId + ? await execAsyncRemote(serverId, defaultCommand) + : await execAsync(defaultCommand); + + runtimeConfigured = defaultRuntime.trim() === "nvidia"; + } catch (error) { + console.debug("Runtime check:", error); + } + + // Check Swarm GPU Resources + let swarmEnabled = false; + let gpuResources = 0; + + try { + // Check node resources directly from inspect + const nodeCommand = + "docker node inspect self --format '{{json .Description.Resources.GenericResources}}'"; + const { stdout: resources } = serverId + ? await execAsyncRemote(serverId, nodeCommand) + : await execAsync(nodeCommand); + + if (resources && resources !== "null") { + const genericResources = JSON.parse(resources); + for (const resource of genericResources) { + if ( + resource.DiscreteResourceSpec && + (resource.DiscreteResourceSpec.Kind === "GPU" || + resource.DiscreteResourceSpec.Kind === "gpu") + ) { + gpuResources = resource.DiscreteResourceSpec.Value; + swarmEnabled = true; + break; + } + } + } + } catch (error) { + console.debug("Swarm resource check:", error); + } + + // Get GPU Model and Memory Info + const gpuInfoCommand = + "nvidia-smi --query-gpu=gpu_name,memory.total --format=csv,noheader"; + const { stdout: gpuInfo } = serverId + ? await execAsyncRemote(serverId, gpuInfoCommand) + : await execAsync(gpuInfoCommand); + + const [gpuModel, memoryTotal] = gpuInfo.split(",").map((s) => s.trim()); + + // Check CUDA Support + const cudaCommand = 'nvidia-smi -q | grep "CUDA Version"'; + const { stdout: cudaInfo } = serverId + ? await execAsyncRemote(serverId, cudaCommand) + : await execAsync(cudaCommand); + + const cudaMatch = cudaInfo.match(/CUDA Version\s*:\s*([\d\.]+)/); + const cudaVersion = cudaMatch ? cudaMatch[1] : undefined; + const cudaSupport = !!cudaVersion; + + return { + driverInstalled, + driverVersion, + runtimeInstalled, + runtimeConfigured, + availableGPUs, + swarmEnabled, + gpuResources, + gpuModel, + memoryInfo: memoryTotal, + cudaSupport, + cudaVersion, + }; + } catch (error) { + console.error("Error in checkGPUStatus:", error); + return { + driverInstalled: false, + driverVersion: undefined, + runtimeInstalled: false, + runtimeConfigured: false, + cudaSupport: false, + cudaVersion: undefined, + gpuModel: undefined, + memoryInfo: undefined, + availableGPUs: 0, + swarmEnabled: false, + gpuResources: 0, + }; + } +} + +export async function setupGPUSupport(serverId?: string): Promise { + try { + // 1. Check current GPU status first + const initialStatus = await checkGPUStatus(serverId); + + // If GPU is already configured, just verify and return quickly + if ( + initialStatus.swarmEnabled && + initialStatus.runtimeConfigured && + initialStatus.driverInstalled + ) { + console.log("GPU already configured, skipping setup"); + return; + } + + // 2. Verify GPU prerequisites + if (!initialStatus.driverInstalled || !initialStatus.runtimeInstalled) { + throw new Error( + "NVIDIA drivers or runtime not installed. Please install them first.", + ); + } + + // Get the node ID + const nodeIdCommand = 'docker info --format "{{.Swarm.NodeID}}"'; + const { stdout: nodeId } = serverId + ? await execAsyncRemote(serverId, nodeIdCommand) + : await execAsync(nodeIdCommand); + + if (!nodeId.trim()) { + throw new Error("Setup Server before enabling GPU support"); + } + + // 3. Configure NVIDIA runtime in daemon.json + const daemonConfig = { + runtimes: { + nvidia: { + path: "nvidia-container-runtime", + runtimeArgs: [], + }, + }, + "default-runtime": "nvidia", + "node-generic-resources": [`GPU=${initialStatus.availableGPUs}`], + }; + + const setupCommands = [ + "sudo -n true", + `echo '${JSON.stringify(daemonConfig, null, 2)}' | sudo tee /etc/docker/daemon.json`, + "sudo mkdir -p /etc/nvidia-container-runtime", + 'echo "swarm-resource = \\"DOCKER_RESOURCE_GPU\\"" | sudo tee -a /etc/nvidia-container-runtime/config.toml', + "sudo systemctl daemon-reload", + "sudo systemctl restart docker", + ].join(" && "); + + if (serverId) { + await execAsyncRemote(serverId, setupCommands); + } else { + await execAsync(setupCommands); + } + + // 4. Reduced wait time for Docker restart + await new Promise((resolve) => setTimeout(resolve, 10000)); + + // 5. Add GPU label to the node + const labelCommand = `docker node update --label-add gpu=true ${nodeId.trim()}`; + if (serverId) { + await execAsyncRemote(serverId, labelCommand); + } else { + await execAsync(labelCommand); + } + + // 6. Quick final verification + await new Promise((resolve) => setTimeout(resolve, 5000)); + const finalStatus = await checkGPUStatus(serverId); + + if (!finalStatus.swarmEnabled) { + const diagnosticCommands = [ + `docker node inspect ${nodeId.trim()}`, + 'nvidia-smi -a | grep "GPU UUID"', + "cat /etc/docker/daemon.json", + "cat /etc/nvidia-container-runtime/config.toml", + ].join(" && "); + + const { stdout: diagnostics } = serverId + ? await execAsyncRemote(serverId, diagnosticCommands) + : await execAsync(diagnosticCommands); + + console.error("Diagnostic Information:", diagnostics); + throw new Error("GPU support not detected in swarm after setup"); + } + + console.log("GPU setup completed successfully:", { + availableGPUs: initialStatus.availableGPUs, + driverVersion: initialStatus.driverVersion, + nodeId: nodeId.trim(), + }); + } catch (error) { + console.error("GPU Setup Error:", error); + throw error; + } +} From 766a25ccad874d92e01f2e07c19e6861b76b8955 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 2 Nov 2024 14:17:21 -0600 Subject: [PATCH 029/243] fix(registry): add image tag resolution correctly when using cluster --- .../server/wss/docker-container-logs.ts | 2 - apps/dokploy/server/wss/terminal.ts | 1 - packages/server/src/db/schema/registry.ts | 11 ++-- packages/server/src/setup/server-setup.ts | 1 - packages/server/src/utils/builders/index.ts | 36 ++++++---- packages/server/src/utils/cluster/upload.ts | 65 ++++++++++++++----- 6 files changed, 79 insertions(+), 37 deletions(-) diff --git a/apps/dokploy/server/wss/docker-container-logs.ts b/apps/dokploy/server/wss/docker-container-logs.ts index 63a0b89e6..1493f6986 100644 --- a/apps/dokploy/server/wss/docker-container-logs.ts +++ b/apps/dokploy/server/wss/docker-container-logs.ts @@ -63,7 +63,6 @@ export const setupDockerContainerLogsWebSocketServer = ( } stream .on("close", () => { - console.log("Connection closed ✅ Container Logs"); client.end(); ws.close(); }) @@ -88,7 +87,6 @@ export const setupDockerContainerLogsWebSocketServer = ( privateKey: server.sshKey?.privateKey, }); ws.on("close", () => { - console.log("Connection closed ✅, From Container Logs WS"); client.end(); }); } else { diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index 548d5e452..eb0bf2e20 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -113,7 +113,6 @@ export const setupTerminalWebSocketServer = ( }); ws.on("close", () => { - console.log("Connection closed ✅"); stream.end(); }); }, diff --git a/packages/server/src/db/schema/registry.ts b/packages/server/src/db/schema/registry.ts index ee1bab946..eaccb58b7 100644 --- a/packages/server/src/db/schema/registry.ts +++ b/packages/server/src/db/schema/registry.ts @@ -1,11 +1,10 @@ -import { relations, sql } from "drizzle-orm"; -import { boolean, pgEnum, pgTable, text, timestamp } from "drizzle-orm/pg-core"; +import { relations } from "drizzle-orm"; +import { pgEnum, pgTable, text } from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; import { nanoid } from "nanoid"; import { z } from "zod"; import { admins } from "./admin"; import { applications } from "./application"; -import { auth } from "./auth"; /** * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same * database instance for multiple projects. @@ -48,7 +47,7 @@ const createSchema = createInsertSchema(registry, { registryUrl: z.string(), adminId: z.string().min(1), registryId: z.string().min(1), - registryType: z.enum(["selfHosted", "cloud"]), + registryType: z.enum(["cloud"]), imagePrefix: z.string().nullable().optional(), }); @@ -59,7 +58,7 @@ export const apiCreateRegistry = createSchema username: z.string().min(1), password: z.string().min(1), registryUrl: z.string(), - registryType: z.enum(["selfHosted", "cloud"]), + registryType: z.enum(["cloud"]), imagePrefix: z.string().nullable().optional(), }) .required() @@ -72,7 +71,7 @@ export const apiTestRegistry = createSchema.pick({}).extend({ username: z.string().min(1), password: z.string().min(1), registryUrl: z.string(), - registryType: z.enum(["selfHosted", "cloud"]), + registryType: z.enum(["cloud"]), imagePrefix: z.string().nullable().optional(), serverId: z.string().optional(), }); diff --git a/packages/server/src/setup/server-setup.ts b/packages/server/src/setup/server-setup.ts index efd70a0dd..f2abd3c53 100644 --- a/packages/server/src/setup/server-setup.ts +++ b/packages/server/src/setup/server-setup.ts @@ -101,7 +101,6 @@ const installRequirements = async (serverId: string, logPath: string) => { } stream .on("close", () => { - writeStream.write("Connection closed ✅"); client.end(); resolve(); }) diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts index cf155e45b..35ac28cba 100644 --- a/packages/server/src/utils/builders/index.ts +++ b/packages/server/src/utils/builders/index.ts @@ -1,7 +1,8 @@ import { createWriteStream } from "node:fs"; +import { join } from "node:path"; import type { InferResultType } from "@dokploy/server/types/with"; import type { CreateServiceOptions } from "dockerode"; -import { uploadImage } from "../cluster/upload"; +import { uploadImage, uploadImageRemoteCommand } from "../cluster/upload"; import { calculateResources, generateBindMounts, @@ -69,19 +70,30 @@ export const getBuildCommand = ( application: ApplicationNested, logPath: string, ) => { - const { buildType } = application; + let command = ""; + const { buildType, registry } = application; switch (buildType) { case "nixpacks": - return getNixpacksCommand(application, logPath); + command = getNixpacksCommand(application, logPath); + break; case "heroku_buildpacks": - return getHerokuCommand(application, logPath); + command = getHerokuCommand(application, logPath); + break; case "paketo_buildpacks": - return getPaketoCommand(application, logPath); + command = getPaketoCommand(application, logPath); + break; case "static": - return getStaticCommand(application, logPath); + command = getStaticCommand(application, logPath); + break; case "dockerfile": - return getDockerCommand(application, logPath); + command = getDockerCommand(application, logPath); + break; } + if (registry) { + command += uploadImageRemoteCommand(application, logPath); + } + + return command; }; export const mechanizeDockerContainer = async ( @@ -186,11 +198,11 @@ const getImageName = (application: ApplicationNested) => { return dockerImage || "ERROR-NO-IMAGE-PROVIDED"; } - const registryUrl = registry?.registryUrl || ""; - const imagePrefix = registry?.imagePrefix ? `${registry.imagePrefix}/` : ""; - return registry - ? `${registryUrl}/${imagePrefix}${appName}` - : `${appName}:latest`; + if (registry) { + return join(registry.registryUrl, registry.imagePrefix || "", appName); + } + + return `${appName}:latest`; }; const getAuthConfig = (application: ApplicationNested) => { diff --git a/packages/server/src/utils/cluster/upload.ts b/packages/server/src/utils/cluster/upload.ts index 8cca9e3d4..a5aff7f42 100644 --- a/packages/server/src/utils/cluster/upload.ts +++ b/packages/server/src/utils/cluster/upload.ts @@ -1,4 +1,5 @@ import type { WriteStream } from "node:fs"; +import { join } from "node:path"; import type { ApplicationNested } from "../builders"; import { spawnAsync } from "../process/spawnAsync"; @@ -16,23 +17,14 @@ export const uploadImage = async ( const { appName } = application; const imageName = `${appName}:latest`; - const finalURL = - registryType === "selfHosted" - ? process.env.NODE_ENV === "development" - ? "localhost:5000" - : registryUrl - : registryUrl; + const finalURL = registryUrl; - const registryTag = imagePrefix - ? `${finalURL}/${imagePrefix}/${imageName}` - : `${finalURL}/${imageName}`; + const registryTag = join(finalURL, imagePrefix || "", imageName); try { - console.log(finalURL, registryTag); writeStream.write( `📦 [Enabled Registry] Uploading image to ${registry.registryType} | ${registryTag} | ${finalURL}\n`, ); - await spawnAsync( "docker", ["login", finalURL, "-u", registry.username, "-p", registry.password], @@ -49,6 +41,8 @@ export const uploadImage = async ( } }); + console.log("docker push ", registryTag); + await spawnAsync("docker", ["push", registryTag], (data) => { if (writeStream.writable) { writeStream.write(data); @@ -59,7 +53,48 @@ export const uploadImage = async ( throw error; } }; -// docker: -// endpoint: "unix:///var/run/docker.sock" -// exposedByDefault: false -// swarmMode: true + +export const uploadImageRemoteCommand = ( + application: ApplicationNested, + logPath: string, +) => { + const registry = application.registry; + + if (!registry) { + throw new Error("Registry not found"); + } + + const { registryUrl, imagePrefix } = registry; + const { appName } = application; + const imageName = `${appName}:latest`; + + const finalURL = registryUrl; + + const registryTag = join(finalURL, imagePrefix || "", imageName); + + try { + const command = ` + echo "📦 [Enabled Registry] Uploading image to '${registry.registryType}' | '${registryTag}'" >> ${logPath}; + docker login ${finalURL} -u ${registry.username} -p ${registry.password} >> ${logPath} 2>> ${logPath} || { + echo "❌ DockerHub Failed" >> ${logPath}; + exit 1; + } + echo "✅ DockerHub Login Success" >> ${logPath}; + docker tag ${imageName} ${registryTag} >> ${logPath} 2>> ${logPath} || { + echo "❌ Error tagging image" >> ${logPath}; + exit 1; + } + echo "✅ Image Tagged" >> ${logPath}; + + docker push ${registryTag} 2>> ${logPath} || { + echo "❌ Error pushing image" >> ${logPath}; + exit 1; + } + echo "✅ Image Pushed" >> ${logPath}; + `; + return command; + } catch (error) { + console.log(error); + throw error; + } +}; From d901b02e9285b54de099ca2921f4b784ea5a6780 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 2 Nov 2024 14:18:00 -0600 Subject: [PATCH 030/243] refactor(dokploy): remove log --- packages/server/src/utils/cluster/upload.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/server/src/utils/cluster/upload.ts b/packages/server/src/utils/cluster/upload.ts index a5aff7f42..97b5fa2e8 100644 --- a/packages/server/src/utils/cluster/upload.ts +++ b/packages/server/src/utils/cluster/upload.ts @@ -41,8 +41,6 @@ export const uploadImage = async ( } }); - console.log("docker push ", registryTag); - await spawnAsync("docker", ["push", registryTag], (data) => { if (writeStream.writable) { writeStream.write(data); From d15b6387a3abc1b5a41410036250460b1b7324d9 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:28:14 -0600 Subject: [PATCH 031/243] refactor(dokploy): remove registryURL from registryTag --- packages/server/src/utils/cluster/upload.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/cluster/upload.ts b/packages/server/src/utils/cluster/upload.ts index 97b5fa2e8..22c9881c6 100644 --- a/packages/server/src/utils/cluster/upload.ts +++ b/packages/server/src/utils/cluster/upload.ts @@ -19,7 +19,7 @@ export const uploadImage = async ( const finalURL = registryUrl; - const registryTag = join(finalURL, imagePrefix || "", imageName); + const registryTag = join(imagePrefix || "", imageName); try { writeStream.write( @@ -68,7 +68,7 @@ export const uploadImageRemoteCommand = ( const finalURL = registryUrl; - const registryTag = join(finalURL, imagePrefix || "", imageName); + const registryTag = join(imagePrefix || "", imageName); try { const command = ` From ef9f16a3c3024f980d449aa06b94173c6e3c2b5f Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:37:49 -0600 Subject: [PATCH 032/243] refactor(dokploy): remove registry url --- packages/server/src/utils/builders/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts index 35ac28cba..a07656025 100644 --- a/packages/server/src/utils/builders/index.ts +++ b/packages/server/src/utils/builders/index.ts @@ -199,7 +199,7 @@ const getImageName = (application: ApplicationNested) => { } if (registry) { - return join(registry.registryUrl, registry.imagePrefix || "", appName); + return join(registry.imagePrefix || "", appName); } return `${appName}:latest`; From ed7150fac10e0ea7645cd3f0a72b0c1406f33f6c Mon Sep 17 00:00:00 2001 From: vishalkadam47 Date: Sun, 3 Nov 2024 04:16:51 +0530 Subject: [PATCH 033/243] fix: Remove unused imports and interfaces from gpu-setup.ts --- packages/server/src/utils/gpu-setup.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/server/src/utils/gpu-setup.ts b/packages/server/src/utils/gpu-setup.ts index 71f3bf0ff..f1936bf6c 100644 --- a/packages/server/src/utils/gpu-setup.ts +++ b/packages/server/src/utils/gpu-setup.ts @@ -1,7 +1,5 @@ -import { docker } from "../constants"; import { execAsync } from "../utils/process/execAsync"; import { execAsyncRemote } from "../utils/process/execAsync"; -import { getRemoteDocker } from "./servers/remote-docker"; interface GPUInfo { driverInstalled: boolean; @@ -17,16 +15,6 @@ interface GPUInfo { gpuResources: number; } -interface DiscreteResourceSpec { - Kind: string; - Value: number; -} - -interface NamedGenericResource { - NamedResourceSpec?: { Kind: string; Value: string }; - DiscreteResourceSpec?: DiscreteResourceSpec; -} - export async function checkGPUStatus(serverId?: string): Promise { try { // Check NVIDIA Driver From 8af5afbb6c40cf287af02f27c117aef503bfb7b0 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 2 Nov 2024 17:11:46 -0600 Subject: [PATCH 034/243] chore(version): bump version --- apps/dokploy/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index e312e522a..5b9203a79 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.10.8", + "version": "v0.10.9", "private": true, "license": "Apache-2.0", "type": "module", From 0468c25bf80472c00bb13426f3dbbbb8cdea65db Mon Sep 17 00:00:00 2001 From: Dominik Koch Date: Sun, 3 Nov 2024 12:45:22 +0100 Subject: [PATCH 035/243] fix: add missing images --- apps/dokploy/public/templates/docmost.png | Bin 0 -> 12963 bytes apps/dokploy/public/templates/vaultwarden.svg | 19 ++++++++++++++++++ apps/dokploy/templates/templates.ts | 4 ++-- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 apps/dokploy/public/templates/docmost.png create mode 100644 apps/dokploy/public/templates/vaultwarden.svg diff --git a/apps/dokploy/public/templates/docmost.png b/apps/dokploy/public/templates/docmost.png new file mode 100644 index 0000000000000000000000000000000000000000..9bc8d210327ca3f4ed31dbaf33d7d483b44fd63c GIT binary patch literal 12963 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 + + + + + + + + + + + + + + + + + + diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 747acdc1b..28af7c56e 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -591,7 +591,7 @@ export const templates: TemplateData[] = [ name: "Docmost", version: "0.4.1", description: - "Docmost, an open-source collaborative wiki and documentation software.", + "Docmost, is an open-source collaborative wiki and documentation software.", logo: "docmost.png", links: { github: "https://github.com/docmost/docmost", @@ -607,7 +607,7 @@ export const templates: TemplateData[] = [ version: "1.32.3", description: "Unofficial Bitwarden compatible server written in Rust, formerly known as bitwarden_rs", - logo: "vaultwarden.png", + logo: "vaultwarden.svg", links: { github: "https://github.com/dani-garcia/vaultwarden", website: "", From e40a0fdc50aff4fcb50c4f58b3e6f086d4f65f34 Mon Sep 17 00:00:00 2001 From: sashagoncharov19 <0976053529@ukr.net> Date: Sun, 3 Nov 2024 14:07:00 +0000 Subject: [PATCH 036/243] fix: remove erpnext/mailserver templates --- apps/dokploy/public/templates/erpnext.png | Bin 6396 -> 0 bytes apps/dokploy/public/templates/mailserver.svg | 1 - .../templates/erpnext/docker-compose.yml | 211 ------------------ apps/dokploy/templates/erpnext/index.ts | 22 -- .../templates/mailserver/docker-compose.yml | 54 ----- apps/dokploy/templates/mailserver/index.ts | 20 -- 6 files changed, 308 deletions(-) delete mode 100644 apps/dokploy/public/templates/erpnext.png delete mode 100644 apps/dokploy/public/templates/mailserver.svg delete mode 100644 apps/dokploy/templates/erpnext/docker-compose.yml delete mode 100644 apps/dokploy/templates/erpnext/index.ts delete mode 100644 apps/dokploy/templates/mailserver/docker-compose.yml delete mode 100644 apps/dokploy/templates/mailserver/index.ts diff --git a/apps/dokploy/public/templates/erpnext.png b/apps/dokploy/public/templates/erpnext.png deleted file mode 100644 index c826955172ca1d98fb9b18d76cbaa39eccd91c82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6396 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 KMNUMnLSTY}Lm3bN diff --git a/apps/dokploy/public/templates/mailserver.svg b/apps/dokploy/public/templates/mailserver.svg deleted file mode 100644 index 7ec0dbb32..000000000 --- a/apps/dokploy/public/templates/mailserver.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dokploy/templates/erpnext/docker-compose.yml b/apps/dokploy/templates/erpnext/docker-compose.yml deleted file mode 100644 index d3aa92a14..000000000 --- a/apps/dokploy/templates/erpnext/docker-compose.yml +++ /dev/null @@ -1,211 +0,0 @@ -services: - backend: - image: frappe/erpnext:v15.35.1 - deploy: - restart_policy: - condition: on-failure - networks: - - dokploy-network - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - configurator: - image: frappe/erpnext:v15.35.1 - deploy: - restart_policy: - condition: none - entrypoint: - - bash - - -c - # add redis_socketio for backward compatibility - command: - - > - ls -1 apps > sites/apps.txt; - bench set-config -g db_host $$DB_HOST; - bench set-config -gp db_port $$DB_PORT; - bench set-config -g redis_cache "redis://$$REDIS_CACHE"; - bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; - bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; - bench set-config -gp socketio_port $$SOCKETIO_PORT; - environment: - DB_HOST: db - DB_PORT: "3306" - REDIS_CACHE: redis-cache:6379 - REDIS_QUEUE: redis-queue:6379 - SOCKETIO_PORT: "9000" - networks: - - dokploy-network - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - create-site: - image: frappe/erpnext:v15.35.1 - deploy: - restart_policy: - condition: none - networks: - - dokploy-network - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - entrypoint: - - bash - - -c - command: - - > - wait-for-it -t 120 db:3306; - wait-for-it -t 120 redis-cache:6379; - wait-for-it -t 120 redis-queue:6379; - export start=`date +%s`; - until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \ - [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \ - [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]]; - do - echo "Waiting for sites/common_site_config.json to be created"; - sleep 5; - if (( `date +%s`-start > 120 )); then - echo "could not find sites/common_site_config.json with required keys"; - exit 1 - fi - done; - echo "sites/common_site_config.json found"; - bench new-site --no-mariadb-socket --admin-password=admin --db-root-password=admin --install-app erpnext --set-default frontend; - - db: - image: mariadb:10.6 - healthcheck: - test: mysqladmin ping -h localhost --password=admin - interval: 1s - retries: 15 - deploy: - restart_policy: - condition: on-failure - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci - - --skip-character-set-client-handshake - - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 - environment: - MYSQL_ROOT_PASSWORD: admin - networks: - - dokploy-network - volumes: - - db-data:/var/lib/mysql - - frontend: - image: frappe/erpnext:v15.35.1 - depends_on: - - websocket - deploy: - restart_policy: - condition: on-failure - command: - - nginx-entrypoint.sh - environment: - BACKEND: backend:8000 - FRAPPE_SITE_NAME_HEADER: frontend - SOCKETIO: websocket:9000 - UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1 - UPSTREAM_REAL_IP_HEADER: X-Forwarded-For - UPSTREAM_REAL_IP_RECURSIVE: "off" - PROXY_READ_TIMEOUT: 120 - CLIENT_MAX_BODY_SIZE: 50m - networks: - - dokploy-network - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - ports: - - "8080:8080" - - queue-long: - image: frappe/erpnext:v15.35.1 - deploy: - restart_policy: - condition: on-failure - command: - - bench - - worker - - --queue - - long,default,short - networks: - - dokploy-network - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - queue-short: - image: frappe/erpnext:v15.35.1 - deploy: - restart_policy: - condition: on-failure - command: - - bench - - worker - - --queue - - short,default - networks: - - dokploy-network - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - redis-queue: - image: redis:6.2-alpine - deploy: - restart_policy: - condition: on-failure - networks: - - dokploy-network - volumes: - - redis-queue-data:/data - - redis-cache: - image: redis:6.2-alpine - deploy: - restart_policy: - condition: on-failure - networks: - - dokploy-network - volumes: - - redis-cache-data:/data - - scheduler: - image: frappe/erpnext:v15.35.1 - deploy: - restart_policy: - condition: on-failure - command: - - bench - - schedule - networks: - - dokploy-network - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - websocket: - image: frappe/erpnext:v15.35.1 - deploy: - restart_policy: - condition: on-failure - command: - - node - - /home/frappe/frappe-bench/apps/frappe/socketio.js - networks: - - dokploy-network - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - -volumes: - db-data: - redis-queue-data: - redis-cache-data: - sites: - logs: -networks: - dokploy-network: - external: true \ No newline at end of file diff --git a/apps/dokploy/templates/erpnext/index.ts b/apps/dokploy/templates/erpnext/index.ts deleted file mode 100644 index 2165c46eb..000000000 --- a/apps/dokploy/templates/erpnext/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - type DomainSchema, - type Schema, - type Template, - generateRandomDomain, -} from "../utils"; - -export function generate(schema: Schema): Template { - const mainDomain = generateRandomDomain(schema); - - const domains: DomainSchema[] = [ - { - host: mainDomain, - port: 8080, - serviceName: "frontend", - }, - ]; - - return { - domains, - }; -} diff --git a/apps/dokploy/templates/mailserver/docker-compose.yml b/apps/dokploy/templates/mailserver/docker-compose.yml deleted file mode 100644 index 8ab044a86..000000000 --- a/apps/dokploy/templates/mailserver/docker-compose.yml +++ /dev/null @@ -1,54 +0,0 @@ -services: - mailserver: - image: ghcr.io/docker-mailserver/docker-mailserver:latest - hostname: ${DMS_HOSTNAME} - ports: - - "25:25" # SMTP (STARTTLS) - - "465:465" # SMTP (Implicit TLS) - - "587:587" # SMTP (STARTTLS) - - "143:143" # IMAP (STARTTLS) - - "993:993" # IMAP (Implicit TLS) - volumes: - - dms-mail-data:/var/mail/ - - dms-mail-state:/var/mail-state/ - - dms-mail-logs:/var/log/mail/ - - dms-mail-config:/tmp/docker-mailserver/ - - /etc/dokploy/traefik/dynamic/acme.json:/etc/letsencrypt/acme.json:ro - - /etc/localtime:/etc/localtime:ro - environment: - - ENABLE_FAIL2BAN=${DMS_ENABLE_FAIL2BAN} - - PERMIT_DOCKER=${DMS_PERMIT_DOCKER} - - SPOOF_PROTECTION=${DMS_SPOOF_PROTECTION} - - SSL_TYPE=${DMS_SSL_TYPE} - - SSL_DOMAIN=${DMS_SSL_DOMAIN} - - POSTMASTER_ADDRESS=${DMS_POSTMASTER_ADDRESS} - cap_add: - - NET_ADMIN - restart: always - stop_grace_period: 1m - healthcheck: - test: ${DMS_HEALTHCHECK_CMD} - timeout: ${DMS_HEALTHCHECK_TIMEOUT} - retries: ${DMS_HEALTHCHECK_RETRIES} - command: > - sh -c ' - if [ ! -s /tmp/docker-mailserver/postfix-accounts.cf ]; then - echo "File does not exist or is empty. Running setup command..."; - setup email add "${DMS_DEFAULT_USER}" "${DMS_DEFAULT_USER_PASS}"; - else - echo "File exists and is not empty. Skipping setup command."; - fi - exec supervisord -c /etc/supervisor/supervisord.conf - ' - networks: - - dokploy-network - -networks: - dokploy-network: - external: true - -volumes: - dms-mail-data: - dms-mail-state: - dms-mail-logs: - dms-mail-config: \ No newline at end of file diff --git a/apps/dokploy/templates/mailserver/index.ts b/apps/dokploy/templates/mailserver/index.ts deleted file mode 100644 index 47379780b..000000000 --- a/apps/dokploy/templates/mailserver/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Schema, Template } from "../utils"; - -export function generate(schema: Schema): Template { - const envs = [ - "DMS_HOSTNAME=mail.example.com", - "DMS_HEALTHCHECK_CMD='ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1'", - "DMS_HEALTHCHECK_TIMEOUT=3s", - "DMS_HEALTHCHECK_RETRIES=0", - "DMS_POSTMASTER_ADDRESS=postmaster@example.com", - "DMS_DEFAULT_USER=admin@example.com", - "DMS_DEFAULT_USER_PASS=password", - "DMS_ENABLE_FAIL2BAN=1", - "DMS_PERMIT_DOCKER=network", - "DMS_SPOOF_PROTECTION=0", - "DMS_SSL_TYPE=letsencrypt", - "DMS_SSL_DOMAIN=example.com", - ]; - - return { envs }; -} From 7306d8c5139f4c41c2b2334c0a3c8d2432c44c88 Mon Sep 17 00:00:00 2001 From: vishalkadam47 Date: Sun, 3 Nov 2024 21:34:03 +0530 Subject: [PATCH 037/243] feat: Add GPU configuration and Update import path for gpu-setup functions --- .../settings/servers/gpu-support.tsx | 219 ++++++++++++++++++ apps/dokploy/server/api/routers/settings.ts | 2 +- 2 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx diff --git a/apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx b/apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx new file mode 100644 index 000000000..a0ef8d80f --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx @@ -0,0 +1,219 @@ +import { Button } from '@/components/ui/button'; +import { useState } from 'react'; +import { api } from '@/utils/api'; +import { toast } from 'sonner'; +import { TRPCClientError } from '@trpc/client'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { DialogAction } from '@/components/shared/dialog-action'; +import { AlertBlock } from '@/components/shared/alert-block'; +import { Cpu, CheckCircle2, XCircle, Loader2 } from 'lucide-react'; + +interface GPUSupportProps { + serverId?: string; +} + +export function GPUSupport({ serverId }: GPUSupportProps) { + const [isLoading, setIsLoading] = useState(false); + const utils = api.useContext(); + + const { data: gpuStatus, isLoading: isChecking } = api.settings.checkGPUStatus.useQuery( + { serverId }, + { + enabled: !!serverId, + refetchInterval: 5000 + } + ); + +const setupGPU = api.settings.setupGPU.useMutation({ + onMutate: () => { + setIsLoading(true); + }, + onSuccess: async () => { + toast.success('GPU support enabled successfully'); + setIsLoading(false); + + await Promise.all([ + utils.settings.checkGPUStatus.invalidate({ serverId }), + utils.server.invalidate() + ]); + }, + onError: (error) => { + if (error instanceof TRPCClientError) { + const errorMessage = error.message; + if (errorMessage.includes('permission denied')) { + toast.error('Permission denied. Please ensure proper sudo access.'); + } else if (errorMessage.includes('Failed to configure GPU')) { + toast.error('GPU configuration failed. Please check system requirements.'); + } else { + toast.error(errorMessage); + } + } else { + toast.error('Failed to enable GPU support. Please check server logs.'); + } + + setIsLoading(false); + } +}); + + const handleEnableGPU = async () => { + if (!serverId) { + toast.error('No server selected'); + return; + } + + try { + await setupGPU.mutateAsync({ serverId }); + } catch (error) { + // Error handling is done in mutation's onError + } + }; + + return ( + +
+ + +
+
+
+ + GPU Configuration +
+ Configure and monitor GPU support +
+ + + +
+
+ + + +
System Requirements:
+
    +
  • NVIDIA drivers must be installed on the host system
  • +
  • NVIDIA Container Runtime is required for GPU support
  • +
  • Compatible GPU hardware must be present
  • +
+
+ + {isChecking ? ( +
+ + Checking GPU status... +
+ ) : ( +
+ {/* Prerequisites Section */} +
+

Prerequisites

+

Shows all software checks and available hardware

+
+ + + + + + +
+
+ + {/* Configuration Status */} +
+

Docker Swarm GPU Status

+

Shows the configuration state that changes with the Enable GPU

+
+ + +
+
+
+ )} +
+
+
+
+ ); +} + +interface StatusRowProps { + label: string; + isEnabled?: boolean; + description?: string; + value?: string | number; + showIcon?: boolean; +} + +function StatusRow({ label, isEnabled, description, value, showIcon = true }: StatusRowProps) { + return ( +
+ {label} +
+ {showIcon ? ( + <> + {isEnabled ? ( + + ) : ( + + )} + + {description || (isEnabled ? 'Installed' : 'Not Installed')} + + + ) : ( + {value} + )} +
+
+ ); +} \ No newline at end of file diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index 13f671264..94167a2e0 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -55,7 +55,7 @@ import { import { checkGPUStatus, setupGPUSupport, -} from "@dokploy/server/src/utils/gpu-setup"; +} from "@dokploy/server"; import { generateOpenApiDocument } from "@dokploy/trpc-openapi"; import { TRPCError } from "@trpc/server"; import { sql } from "drizzle-orm"; From 6b7712e35fc2ac6ccc611dc242b3faf94d3cb29a Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:10:52 -0600 Subject: [PATCH 038/243] fix(dokploy): filter notifications by admin --- apps/dokploy/server/api/routers/settings.ts | 4 ++-- packages/server/src/services/application.ts | 4 ++++ packages/server/src/services/compose.ts | 4 ++++ packages/server/src/utils/backups/mariadb.ts | 2 ++ packages/server/src/utils/backups/mongo.ts | 2 ++ packages/server/src/utils/backups/mysql.ts | 2 ++ packages/server/src/utils/backups/postgres.ts | 2 ++ packages/server/src/utils/notifications/build-error.ts | 9 +++++++-- packages/server/src/utils/notifications/build-success.ts | 9 +++++++-- .../server/src/utils/notifications/database-backup.ts | 9 +++++++-- .../server/src/utils/notifications/docker-cleanup.ts | 8 ++++++-- 11 files changed, 45 insertions(+), 10 deletions(-) diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index 485e8c73f..e5c34b11c 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -242,7 +242,7 @@ export const settingsRouter = createTRPCRouter({ await cleanUpUnusedImages(server.serverId); await cleanUpDockerBuilder(server.serverId); await cleanUpSystemPrune(server.serverId); - await sendDockerCleanupNotifications(); + await sendDockerCleanupNotifications(server.adminId); }); } } else { @@ -278,7 +278,7 @@ export const settingsRouter = createTRPCRouter({ await cleanUpUnusedImages(); await cleanUpDockerBuilder(); await cleanUpSystemPrune(); - await sendDockerCleanupNotifications(); + await sendDockerCleanupNotifications(admin.adminId); }); } else { const currentJob = scheduledJobs["docker-cleanup"]; diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts index ec0c0c2f5..5885671b5 100644 --- a/packages/server/src/services/application.ts +++ b/packages/server/src/services/application.ts @@ -193,6 +193,7 @@ export const deployApplication = async ({ applicationName: application.name, applicationType: "application", buildLink, + adminId: application.project.adminId, }); } catch (error) { await updateDeploymentStatus(deployment.deploymentId, "error"); @@ -204,6 +205,7 @@ export const deployApplication = async ({ // @ts-ignore errorMessage: error?.message || "Error to build", buildLink, + adminId: application.project.adminId, }); console.log( @@ -314,6 +316,7 @@ export const deployRemoteApplication = async ({ applicationName: application.name, applicationType: "application", buildLink, + adminId: application.project.adminId, }); } catch (error) { // @ts-ignore @@ -336,6 +339,7 @@ export const deployRemoteApplication = async ({ // @ts-ignore errorMessage: error?.message || "Error to build", buildLink, + adminId: application.project.adminId, }); console.log( diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 61d7e5fc5..63d29539d 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -235,6 +235,7 @@ export const deployCompose = async ({ applicationName: compose.name, applicationType: "compose", buildLink, + adminId: compose.project.adminId, }); } catch (error) { await updateDeploymentStatus(deployment.deploymentId, "error"); @@ -248,6 +249,7 @@ export const deployCompose = async ({ // @ts-ignore errorMessage: error?.message || "Error to build", buildLink, + adminId: compose.project.adminId, }); throw error; } @@ -353,6 +355,7 @@ export const deployRemoteCompose = async ({ applicationName: compose.name, applicationType: "compose", buildLink, + adminId: compose.project.adminId, }); } catch (error) { // @ts-ignore @@ -376,6 +379,7 @@ export const deployRemoteCompose = async ({ // @ts-ignore errorMessage: error?.message || "Error to build", buildLink, + adminId: compose.project.adminId, }); throw error; } diff --git a/packages/server/src/utils/backups/mariadb.ts b/packages/server/src/utils/backups/mariadb.ts index b8621b28d..79cba9c57 100644 --- a/packages/server/src/utils/backups/mariadb.ts +++ b/packages/server/src/utils/backups/mariadb.ts @@ -49,6 +49,7 @@ export const runMariadbBackup = async ( projectName: project.name, databaseType: "mariadb", type: "success", + adminId: project.adminId, }); } catch (error) { console.log(error); @@ -59,6 +60,7 @@ export const runMariadbBackup = async ( type: "error", // @ts-ignore errorMessage: error?.message || "Error message not provided", + adminId: project.adminId, }); throw error; } diff --git a/packages/server/src/utils/backups/mongo.ts b/packages/server/src/utils/backups/mongo.ts index 1a30fe14a..ddd1b8896 100644 --- a/packages/server/src/utils/backups/mongo.ts +++ b/packages/server/src/utils/backups/mongo.ts @@ -46,6 +46,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => { projectName: project.name, databaseType: "mongodb", type: "success", + adminId: project.adminId, }); } catch (error) { console.log(error); @@ -56,6 +57,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => { type: "error", // @ts-ignore errorMessage: error?.message || "Error message not provided", + adminId: project.adminId, }); throw error; } diff --git a/packages/server/src/utils/backups/mysql.ts b/packages/server/src/utils/backups/mysql.ts index ea92ee84b..b505204c2 100644 --- a/packages/server/src/utils/backups/mysql.ts +++ b/packages/server/src/utils/backups/mysql.ts @@ -46,6 +46,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => { projectName: project.name, databaseType: "mysql", type: "success", + adminId: project.adminId, }); } catch (error) { console.log(error); @@ -56,6 +57,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => { type: "error", // @ts-ignore errorMessage: error?.message || "Error message not provided", + adminId: project.adminId, }); throw error; } diff --git a/packages/server/src/utils/backups/postgres.ts b/packages/server/src/utils/backups/postgres.ts index fe3171d6c..e9609fc89 100644 --- a/packages/server/src/utils/backups/postgres.ts +++ b/packages/server/src/utils/backups/postgres.ts @@ -49,6 +49,7 @@ export const runPostgresBackup = async ( projectName: project.name, databaseType: "postgres", type: "success", + adminId: project.adminId, }); } catch (error) { await sendDatabaseBackupNotifications({ @@ -58,6 +59,7 @@ export const runPostgresBackup = async ( type: "error", // @ts-ignore errorMessage: error?.message || "Error message not provided", + adminId: project.adminId, }); throw error; diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts index 703367ec5..9fcb06412 100644 --- a/packages/server/src/utils/notifications/build-error.ts +++ b/packages/server/src/utils/notifications/build-error.ts @@ -2,7 +2,7 @@ import { db } from "@dokploy/server/db"; import { notifications } from "@dokploy/server/db/schema"; import BuildFailedEmail from "@dokploy/server/emails/emails/build-failed"; import { renderAsync } from "@react-email/components"; -import { eq } from "drizzle-orm"; +import { and, eq } from "drizzle-orm"; import { sendDiscordNotification, sendEmailNotification, @@ -16,6 +16,7 @@ interface Props { applicationType: string; errorMessage: string; buildLink: string; + adminId: string; } export const sendBuildErrorNotifications = async ({ @@ -24,10 +25,14 @@ export const sendBuildErrorNotifications = async ({ applicationType, errorMessage, buildLink, + adminId, }: Props) => { const date = new Date(); const notificationList = await db.query.notifications.findMany({ - where: eq(notifications.appBuildError, true), + where: and( + eq(notifications.appBuildError, true), + eq(notifications.adminId, adminId), + ), with: { email: true, discord: true, diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts index 55cbb33d5..9210eca2a 100644 --- a/packages/server/src/utils/notifications/build-success.ts +++ b/packages/server/src/utils/notifications/build-success.ts @@ -2,7 +2,7 @@ import { db } from "@dokploy/server/db"; import { notifications } from "@dokploy/server/db/schema"; import BuildSuccessEmail from "@dokploy/server/emails/emails/build-success"; import { renderAsync } from "@react-email/components"; -import { eq } from "drizzle-orm"; +import { and, eq } from "drizzle-orm"; import { sendDiscordNotification, sendEmailNotification, @@ -15,6 +15,7 @@ interface Props { applicationName: string; applicationType: string; buildLink: string; + adminId: string; } export const sendBuildSuccessNotifications = async ({ @@ -22,10 +23,14 @@ export const sendBuildSuccessNotifications = async ({ applicationName, applicationType, buildLink, + adminId, }: Props) => { const date = new Date(); const notificationList = await db.query.notifications.findMany({ - where: eq(notifications.appDeploy, true), + where: and( + eq(notifications.appDeploy, true), + eq(notifications.adminId, adminId), + ), with: { email: true, discord: true, diff --git a/packages/server/src/utils/notifications/database-backup.ts b/packages/server/src/utils/notifications/database-backup.ts index 1b2a69092..c97afa031 100644 --- a/packages/server/src/utils/notifications/database-backup.ts +++ b/packages/server/src/utils/notifications/database-backup.ts @@ -2,7 +2,7 @@ import { db } from "@dokploy/server/db"; import { notifications } from "@dokploy/server/db/schema"; import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup"; import { renderAsync } from "@react-email/components"; -import { eq } from "drizzle-orm"; +import { and, eq } from "drizzle-orm"; import { sendDiscordNotification, sendEmailNotification, @@ -16,16 +16,21 @@ export const sendDatabaseBackupNotifications = async ({ databaseType, type, errorMessage, + adminId, }: { projectName: string; applicationName: string; databaseType: "postgres" | "mysql" | "mongodb" | "mariadb"; type: "error" | "success"; + adminId: string; errorMessage?: string; }) => { const date = new Date(); const notificationList = await db.query.notifications.findMany({ - where: eq(notifications.databaseBackup, true), + where: and( + eq(notifications.databaseBackup, true), + eq(notifications.adminId, adminId), + ), with: { email: true, discord: true, diff --git a/packages/server/src/utils/notifications/docker-cleanup.ts b/packages/server/src/utils/notifications/docker-cleanup.ts index a42657bca..a2e091393 100644 --- a/packages/server/src/utils/notifications/docker-cleanup.ts +++ b/packages/server/src/utils/notifications/docker-cleanup.ts @@ -2,7 +2,7 @@ import { db } from "@dokploy/server/db"; import { notifications } from "@dokploy/server/db/schema"; import DockerCleanupEmail from "@dokploy/server/emails/emails/docker-cleanup"; import { renderAsync } from "@react-email/components"; -import { eq } from "drizzle-orm"; +import { and, eq } from "drizzle-orm"; import { sendDiscordNotification, sendEmailNotification, @@ -11,11 +11,15 @@ import { } from "./utils"; export const sendDockerCleanupNotifications = async ( + adminId: string, message = "Docker cleanup for dokploy", ) => { const date = new Date(); const notificationList = await db.query.notifications.findMany({ - where: eq(notifications.dockerCleanup, true), + where: and( + eq(notifications.dockerCleanup, true), + eq(notifications.adminId, adminId), + ), with: { email: true, discord: true, From 34b12a0315cedb468c3edf1c85eb9dfedb39c6dd Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:20:55 -0600 Subject: [PATCH 039/243] chore(version): bump version --- apps/dokploy/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index 5b9203a79..e62bb1dd8 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.10.9", + "version": "v0.10.10", "private": true, "license": "Apache-2.0", "type": "module", From 2a24e1d7e83fb64fcb0d5d9450de3c240cd1cfcc Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:13:40 -0600 Subject: [PATCH 040/243] Update docker-compose.yml --- apps/dokploy/templates/vaultwarden/docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/templates/vaultwarden/docker-compose.yml b/apps/dokploy/templates/vaultwarden/docker-compose.yml index 4ccc5cbf8..456f585b2 100644 --- a/apps/dokploy/templates/vaultwarden/docker-compose.yml +++ b/apps/dokploy/templates/vaultwarden/docker-compose.yml @@ -8,7 +8,7 @@ services: volumes: - vaultwarden:/data ports: - - 11001:80 + - 80 volumes: - vaultwarden: \ No newline at end of file + vaultwarden: From 1b1d0597fe35ec4918bd63f03024f9e6098534fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bord=C3=A1=C5=A1?= Date: Mon, 4 Nov 2024 23:59:20 +0100 Subject: [PATCH 041/243] fix: domain path ignored in compose services --- packages/server/src/utils/docker/domain.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts index 28ede3085..a065f31c0 100644 --- a/packages/server/src/utils/docker/domain.ts +++ b/packages/server/src/utils/docker/domain.ts @@ -268,6 +268,12 @@ export const createDomainLabels = async ( `traefik.http.routers.${routerName}.service=${routerName}`, ]; + if (domain.path) { + labels.push( + `traefik.http.routers.${routerName}.rule=PathPrefix(\`${domain.path}\`)`, + ); + } + if (entrypoint === "web" && https) { labels.push( `traefik.http.routers.${routerName}.middlewares=redirect-to-https@file`, From b53da82204eb5a9f180d78d1f7f52356357868e5 Mon Sep 17 00:00:00 2001 From: vishalkadam47 Date: Tue, 5 Nov 2024 12:07:35 +0530 Subject: [PATCH 042/243] refactor: gpu support component and related api routers; update template environment variables --- .../settings/servers/gpu-support.tsx | 443 ++++++++++-------- apps/dokploy/server/api/routers/settings.ts | 5 +- apps/dokploy/templates/blender/index.ts | 12 +- 3 files changed, 249 insertions(+), 211 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx b/apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx index a0ef8d80f..ae931a3a9 100644 --- a/apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx @@ -1,219 +1,260 @@ -import { Button } from '@/components/ui/button'; -import { useState } from 'react'; -import { api } from '@/utils/api'; -import { toast } from 'sonner'; -import { TRPCClientError } from '@trpc/client'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { DialogAction } from '@/components/shared/dialog-action'; -import { AlertBlock } from '@/components/shared/alert-block'; -import { Cpu, CheckCircle2, XCircle, Loader2 } from 'lucide-react'; +import { AlertBlock } from "@/components/shared/alert-block"; +import { DialogAction } from "@/components/shared/dialog-action"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { api } from "@/utils/api"; +import { TRPCClientError } from "@trpc/client"; +import { CheckCircle2, Cpu, Loader2, XCircle } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; interface GPUSupportProps { - serverId?: string; + serverId?: string; } export function GPUSupport({ serverId }: GPUSupportProps) { - const [isLoading, setIsLoading] = useState(false); - const utils = api.useContext(); + const [isLoading, setIsLoading] = useState(false); + const utils = api.useContext(); - const { data: gpuStatus, isLoading: isChecking } = api.settings.checkGPUStatus.useQuery( - { serverId }, - { - enabled: !!serverId, - refetchInterval: 5000 - } - ); + const { data: gpuStatus, isLoading: isChecking } = + api.settings.checkGPUStatus.useQuery( + { serverId }, + { + enabled: !!serverId, + refetchInterval: 5000, + }, + ); -const setupGPU = api.settings.setupGPU.useMutation({ - onMutate: () => { - setIsLoading(true); - }, - onSuccess: async () => { - toast.success('GPU support enabled successfully'); - setIsLoading(false); - - await Promise.all([ - utils.settings.checkGPUStatus.invalidate({ serverId }), - utils.server.invalidate() - ]); - }, - onError: (error) => { - if (error instanceof TRPCClientError) { - const errorMessage = error.message; - if (errorMessage.includes('permission denied')) { - toast.error('Permission denied. Please ensure proper sudo access.'); - } else if (errorMessage.includes('Failed to configure GPU')) { - toast.error('GPU configuration failed. Please check system requirements.'); - } else { - toast.error(errorMessage); - } - } else { - toast.error('Failed to enable GPU support. Please check server logs.'); - } - - setIsLoading(false); - } -}); + const setupGPU = api.settings.setupGPU.useMutation({ + onMutate: () => { + setIsLoading(true); + }, + onSuccess: async () => { + toast.success("GPU support enabled successfully"); + setIsLoading(false); - const handleEnableGPU = async () => { - if (!serverId) { - toast.error('No server selected'); - return; - } + await Promise.all([ + utils.settings.checkGPUStatus.invalidate({ serverId }), + utils.server.invalidate(), + ]); + }, + onError: (error) => { + if (error instanceof TRPCClientError) { + const errorMessage = error.message; + if (errorMessage.includes("permission denied")) { + toast.error("Permission denied. Please ensure proper sudo access."); + } else if (errorMessage.includes("Failed to configure GPU")) { + toast.error( + "GPU configuration failed. Please check system requirements.", + ); + } else { + toast.error(errorMessage); + } + } else { + toast.error("Failed to enable GPU support. Please check server logs."); + } - try { - await setupGPU.mutateAsync({ serverId }); - } catch (error) { - // Error handling is done in mutation's onError - } - }; + setIsLoading(false); + }, + }); - return ( - -
- - -
-
-
- - GPU Configuration -
- Configure and monitor GPU support -
- - - -
-
+ const handleEnableGPU = async () => { + if (!serverId) { + toast.error("No server selected"); + return; + } - - -
System Requirements:
-
    -
  • NVIDIA drivers must be installed on the host system
  • -
  • NVIDIA Container Runtime is required for GPU support
  • -
  • Compatible GPU hardware must be present
  • -
-
+ try { + await setupGPU.mutateAsync({ serverId }); + } catch (error) { + // Error handling is done in mutation's onError + } + }; - {isChecking ? ( -
- - Checking GPU status... -
- ) : ( -
- {/* Prerequisites Section */} -
-

Prerequisites

-

Shows all software checks and available hardware

-
- - - - - - -
-
+ return ( + +
+ + +
+
+
+ + GPU Configuration +
+ + Configure and monitor GPU support + +
+ + + +
+
- {/* Configuration Status */} -
-

Docker Swarm GPU Status

-

Shows the configuration state that changes with the Enable GPU

-
- - -
-
-
- )} -
- -
-
- ); + + +
System Requirements:
+
    +
  • NVIDIA drivers must be installed on the host system
  • +
  • NVIDIA Container Runtime is required for GPU support
  • +
  • Compatible GPU hardware must be present
  • +
+
+ + {isChecking ? ( +
+ + Checking GPU status... +
+ ) : ( +
+ {/* Prerequisites Section */} +
+

Prerequisites

+

+ Shows all software checks and available hardware +

+
+ + + + + + +
+
+ + {/* Configuration Status */} +
+

+ Docker Swarm GPU Status +

+

+ Shows the configuration state that changes with the Enable + GPU +

+
+ + +
+
+
+ )} +
+
+
+
+ ); } interface StatusRowProps { - label: string; - isEnabled?: boolean; - description?: string; - value?: string | number; - showIcon?: boolean; + label: string; + isEnabled?: boolean; + description?: string; + value?: string | number; + showIcon?: boolean; } -function StatusRow({ label, isEnabled, description, value, showIcon = true }: StatusRowProps) { - return ( -
- {label} -
- {showIcon ? ( - <> - {isEnabled ? ( - - ) : ( - - )} - - {description || (isEnabled ? 'Installed' : 'Not Installed')} - - - ) : ( - {value} - )} -
-
- ); -} \ No newline at end of file +function StatusRow({ + label, + isEnabled, + description, + value, + showIcon = true, +}: StatusRowProps) { + return ( +
+ {label} +
+ {showIcon ? ( + <> + {isEnabled ? ( + + ) : ( + + )} + + {description || (isEnabled ? "Installed" : "Not Installed")} + + + ) : ( + {value} + )} +
+
+ ); +} diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index 465292826..608a50285 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -52,10 +52,7 @@ import { writeMainConfig, writeTraefikConfigInPath, } from "@dokploy/server"; -import { - checkGPUStatus, - setupGPUSupport, -} from "@dokploy/server"; +import { checkGPUStatus, setupGPUSupport } from "@dokploy/server"; import { generateOpenApiDocument } from "@dokploy/trpc-openapi"; import { TRPCError } from "@trpc/server"; import { sql } from "drizzle-orm"; diff --git a/apps/dokploy/templates/blender/index.ts b/apps/dokploy/templates/blender/index.ts index baf243e08..84e527554 100644 --- a/apps/dokploy/templates/blender/index.ts +++ b/apps/dokploy/templates/blender/index.ts @@ -19,12 +19,12 @@ export function generate(schema: Schema): Template { ]; const envs = [ - `PUID=1000`, - `PGID=1000`, - `TZ=Etc/UTC`, - `SUBFOLDER=/`, - `NVIDIA_VISIBLE_DEVICES=all`, - `NVIDIA_DRIVER_CAPABILITIES=all`, + "PUID=1000", + "PGID=1000", + "TZ=Etc/UTC", + "SUBFOLDER=/", + "NVIDIA_VISIBLE_DEVICES=all", + "NVIDIA_DRIVER_CAPABILITIES=all", ]; return { From 476057663bada10f5925e161c897c94b9ca1b7a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bord=C3=A1=C5=A1?= Date: Tue, 5 Nov 2024 11:39:30 +0100 Subject: [PATCH 043/243] fix: add path prefix only if the path is other than "/" --- packages/server/src/utils/docker/domain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts index a065f31c0..090bf02ed 100644 --- a/packages/server/src/utils/docker/domain.ts +++ b/packages/server/src/utils/docker/domain.ts @@ -268,7 +268,7 @@ export const createDomainLabels = async ( `traefik.http.routers.${routerName}.service=${routerName}`, ]; - if (domain.path) { + if (domain.path && domain.path.length > 0 && domain.path !== "/") { labels.push( `traefik.http.routers.${routerName}.rule=PathPrefix(\`${domain.path}\`)`, ); From f466e697dd700e18d106cc2db86890577cf319f3 Mon Sep 17 00:00:00 2001 From: Thomas Brq <71637888+thomasbrq@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:52:48 +0100 Subject: [PATCH 044/243] fix(dokploy): Wrong input for `target port` when updating ports. --- .../dashboard/application/advanced/ports/update-port.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/application/advanced/ports/update-port.tsx b/apps/dokploy/components/dashboard/application/advanced/ports/update-port.tsx index 0ed9d2e27..a9f7f32d5 100644 --- a/apps/dokploy/components/dashboard/application/advanced/ports/update-port.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/ports/update-port.tsx @@ -140,7 +140,7 @@ export const UpdatePort = ({ portId }: Props) => { Target Port - + From 2e6d9c34c0bdc61d9c4d8fb759f2ea9fddcbe654 Mon Sep 17 00:00:00 2001 From: vishalkadam47 Date: Thu, 7 Nov 2024 02:52:41 +0530 Subject: [PATCH 045/243] feat: add dokploy server gpu setup --- .../servers/actions/show-dokploy-actions.tsx | 2 + .../settings/servers/gpu-support-modal.tsx | 36 +++++++++++++++ .../settings/servers/gpu-support.tsx | 26 ++++++----- apps/dokploy/server/api/routers/settings.ts | 37 +++++++-------- packages/server/src/utils/gpu-setup.ts | 45 +++++++++++++++---- 5 files changed, 104 insertions(+), 42 deletions(-) create mode 100644 apps/dokploy/components/dashboard/settings/servers/gpu-support-modal.tsx diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx index 49f6772b5..9b12af840 100644 --- a/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx @@ -13,6 +13,7 @@ import { import { api } from "@/utils/api"; import { toast } from "sonner"; import { ShowModalLogs } from "../../web-server/show-modal-logs"; +import { GPUSupportModal } from "../gpu-support-modal"; export const ShowDokployActions = () => { const { mutateAsync: reloadServer, isLoading } = @@ -45,6 +46,7 @@ export const ShowDokployActions = () => { Watch logs + diff --git a/apps/dokploy/components/dashboard/settings/servers/gpu-support-modal.tsx b/apps/dokploy/components/dashboard/settings/servers/gpu-support-modal.tsx new file mode 100644 index 000000000..9cf858cd3 --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/servers/gpu-support-modal.tsx @@ -0,0 +1,36 @@ +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { useState } from "react"; +import { GPUSupport } from "./gpu-support"; + +export const GPUSupportModal = () => { + const [isOpen, setIsOpen] = useState(false); + + return ( + + + e.preventDefault()} + > + GPU Setup + + + + + + Dokploy Server GPU Setup + + + + + + + ); +}; diff --git a/apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx b/apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx index ae931a3a9..d0c178c4f 100644 --- a/apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/gpu-support.tsx @@ -26,7 +26,7 @@ export function GPUSupport({ serverId }: GPUSupportProps) { api.settings.checkGPUStatus.useQuery( { serverId }, { - enabled: !!serverId, + enabled: serverId !== undefined, refetchInterval: 5000, }, ); @@ -38,17 +38,20 @@ export function GPUSupport({ serverId }: GPUSupportProps) { onSuccess: async () => { toast.success("GPU support enabled successfully"); setIsLoading(false); - - await Promise.all([ - utils.settings.checkGPUStatus.invalidate({ serverId }), - utils.server.invalidate(), - ]); + await utils.settings.checkGPUStatus.invalidate({ serverId }); }, onError: (error) => { if (error instanceof TRPCClientError) { const errorMessage = error.message; - if (errorMessage.includes("permission denied")) { - toast.error("Permission denied. Please ensure proper sudo access."); + if ( + errorMessage.includes( + "Permission denied. Please ensure proper sudo access.", + ) || + errorMessage.includes("sudo access required") + ) { + toast.error( + "Administrator privileges required. Please enter your password when prompted.", + ); } else if (errorMessage.includes("Failed to configure GPU")) { toast.error( "GPU configuration failed. Please check system requirements.", @@ -59,13 +62,12 @@ export function GPUSupport({ serverId }: GPUSupportProps) { } else { toast.error("Failed to enable GPU support. Please check server logs."); } - setIsLoading(false); }, }); const handleEnableGPU = async () => { - if (!serverId) { + if (serverId === undefined) { toast.error("No server selected"); return; } @@ -99,7 +101,7 @@ export function GPUSupport({ serverId }: GPUSupportProps) { >
+ + + + + ); +}; diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx index 165918652..757c77955 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx @@ -87,7 +87,7 @@ export const UpdateServer = () => { }} isLoading={isLoading} > - Check updates + Check Updates )} diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts index 42ce15bc0..d910f2230 100644 --- a/apps/dokploy/server/api/routers/admin.ts +++ b/apps/dokploy/server/api/routers/admin.ts @@ -4,6 +4,7 @@ import { apiCreateUserInvitation, apiFindOneToken, apiRemoveUser, + apiUpdateAdmin, users, } from "@/server/db/schema"; import { @@ -13,6 +14,7 @@ import { findUserById, getUserByToken, removeUserByAuthId, + updateAdmin, } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; @@ -26,6 +28,12 @@ export const adminRouter = createTRPCRouter({ ...rest, }; }), + update: adminProcedure + .input(apiUpdateAdmin) + .mutation(async ({ input, ctx }) => { + const { authId } = await findAdminById(ctx.user.adminId); + return updateAdmin(authId, input); + }), createUserInvitation: adminProcedure .input(apiCreateUserInvitation) .mutation(async ({ input, ctx }) => { diff --git a/packages/server/src/db/schema/admin.ts b/packages/server/src/db/schema/admin.ts index cce611c24..222fb16c8 100644 --- a/packages/server/src/db/schema/admin.ts +++ b/packages/server/src/db/schema/admin.ts @@ -53,6 +53,8 @@ const createSchema = createInsertSchema(admins, { letsEncryptEmail: z.string().optional(), }); +export const apiUpdateAdmin = createSchema.partial(); + export const apiSaveSSHKey = createSchema .pick({ sshPrivateKey: true, From 2835c997e9424f94c137a7f36db5f6df5b71318b Mon Sep 17 00:00:00 2001 From: Krzysztof Durek <21038648+kdurek@users.noreply.github.com> Date: Sun, 17 Nov 2024 18:44:05 +0100 Subject: [PATCH 117/243] feat: update to use multi language --- .../dashboard/settings/servers/actions/show-dokploy-actions.tsx | 2 +- apps/dokploy/public/locales/en/settings.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx index 433928468..ee749244e 100644 --- a/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx @@ -63,7 +63,7 @@ export const ShowDokployActions = () => { className="cursor-pointer" onSelect={(e) => e.preventDefault()} > - Update Server IP + {t("settings.server.webServer.updateServerIp")} diff --git a/apps/dokploy/public/locales/en/settings.json b/apps/dokploy/public/locales/en/settings.json index 594323f4a..2103ecc00 100644 --- a/apps/dokploy/public/locales/en/settings.json +++ b/apps/dokploy/public/locales/en/settings.json @@ -14,6 +14,7 @@ "settings.server.webServer.actions": "Actions", "settings.server.webServer.reload": "Reload", "settings.server.webServer.watchLogs": "Watch logs", + "settings.server.webServer.updateServerIp": "Update Server IP", "settings.server.webServer.server.label": "Server", "settings.server.webServer.traefik.label": "Traefik", "settings.server.webServer.traefik.modifyEnv": "Modify Env", From 3015d69adc916e8491f0c6a39d12111158f020b0 Mon Sep 17 00:00:00 2001 From: Krzysztof Durek <21038648+kdurek@users.noreply.github.com> Date: Sun, 17 Nov 2024 18:52:22 +0100 Subject: [PATCH 118/243] feat: add polish translation --- apps/dokploy/public/locales/pl/common.json | 1 + apps/dokploy/public/locales/pl/settings.json | 44 ++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 apps/dokploy/public/locales/pl/common.json create mode 100644 apps/dokploy/public/locales/pl/settings.json diff --git a/apps/dokploy/public/locales/pl/common.json b/apps/dokploy/public/locales/pl/common.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/apps/dokploy/public/locales/pl/common.json @@ -0,0 +1 @@ +{} diff --git a/apps/dokploy/public/locales/pl/settings.json b/apps/dokploy/public/locales/pl/settings.json new file mode 100644 index 000000000..48531e69a --- /dev/null +++ b/apps/dokploy/public/locales/pl/settings.json @@ -0,0 +1,44 @@ +{ + "settings.common.save": "Zapisz", + "settings.server.domain.title": "Domena", + "settings.server.domain.description": "Dodaj domenę do aplikacji", + "settings.server.domain.form.domain": "Domena", + "settings.server.domain.form.letsEncryptEmail": "Email Let's Encrypt", + "settings.server.domain.form.certificate.label": "Certyfikat", + "settings.server.domain.form.certificate.placeholder": "Wybierz certyfikat", + "settings.server.domain.form.certificateOptions.none": "Brak", + "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Domyślny)", + + "settings.server.webServer.title": "Serwer", + "settings.server.webServer.description": "Przeładuj lub wyczyść serwer", + "settings.server.webServer.actions": "Akcje", + "settings.server.webServer.reload": "Przeładuj", + "settings.server.webServer.watchLogs": "Obserwuj logi", + "settings.server.webServer.updateServerIp": "Zaktualizuj IP serwera", + "settings.server.webServer.server.label": "Serwer", + "settings.server.webServer.traefik.label": "Traefik", + "settings.server.webServer.traefik.modifyEnv": "Zmodyfikuj środowisko", + "settings.server.webServer.storage.label": "Przestrzeń", + "settings.server.webServer.storage.cleanUnusedImages": "Wyczyść nieużywane obrazy", + "settings.server.webServer.storage.cleanUnusedVolumes": "Wyczyść nieużywane wolumeny", + "settings.server.webServer.storage.cleanStoppedContainers": "Wyczyść zatrzymane kontenery", + "settings.server.webServer.storage.cleanDockerBuilder": "Wyczyść Docker Builder i System", + "settings.server.webServer.storage.cleanMonitoring": "Wyczyść monitorowanie", + "settings.server.webServer.storage.cleanAll": "Wyczyść wszystko", + + "settings.profile.title": "Konto", + "settings.profile.description": "Zmień szczegóły swojego profilu", + "settings.profile.email": "Email", + "settings.profile.password": "Hasło", + "settings.profile.avatar": "Avatar", + + "settings.appearance.title": "Wygląd", + "settings.appearance.description": "Dostosuj motyw swojego pulpitu", + "settings.appearance.theme": "Motyw", + "settings.appearance.themeDescription": "Wybierz motyw swojego pulpitu", + "settings.appearance.themes.light": "Jasny", + "settings.appearance.themes.dark": "Ciemny", + "settings.appearance.themes.system": "System", + "settings.appearance.language": "Język", + "settings.appearance.languageDescription": "Wybierz język swojego pulpitu" +} From 6af574270204c5c8c64bad91c0c275998a732ad6 Mon Sep 17 00:00:00 2001 From: JiPai Date: Mon, 18 Nov 2024 03:31:30 +0800 Subject: [PATCH 119/243] fix(i18n): quick fix for locale cookie expire when browser close --- apps/dokploy/utils/hooks/use-locale.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/utils/hooks/use-locale.ts b/apps/dokploy/utils/hooks/use-locale.ts index f00e0df84..72ac6ab07 100644 --- a/apps/dokploy/utils/hooks/use-locale.ts +++ b/apps/dokploy/utils/hooks/use-locale.ts @@ -8,7 +8,7 @@ export default function useLocale() { const currentLocale = (Cookies.get("DOKPLOY_LOCALE") ?? "en") as Locale; const setLocale = (locale: Locale) => { - Cookies.set("DOKPLOY_LOCALE", locale); + Cookies.set("DOKPLOY_LOCALE", locale, { expires: 365 }); window.location.reload(); }; From 4bf5e5ca06c61f0bae05bb9a1d7c60b14ec148c0 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 17 Nov 2024 13:38:20 -0600 Subject: [PATCH 120/243] fix(dokploy): remove $ on presets redirect --- .../dashboard/application/advanced/redirects/add-redirect.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/advanced/redirects/add-redirect.tsx b/apps/dokploy/components/dashboard/application/advanced/redirects/add-redirect.tsx index 320471ba5..8ce547a29 100644 --- a/apps/dokploy/components/dashboard/application/advanced/redirects/add-redirect.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/redirects/add-redirect.tsx @@ -61,7 +61,7 @@ const redirectPresets = [ redirect: { regex: "^https?://(?:www.)?(.+)", permanent: true, - replacement: "https://www.$${1}", + replacement: "https://www.${1}", }, }, { @@ -70,7 +70,7 @@ const redirectPresets = [ redirect: { regex: "^https?://www.(.+)", permanent: true, - replacement: "https://$${1}", + replacement: "https://${1}", }, }, ]; From 82367213ea99f027401823721eaaea5decd56f35 Mon Sep 17 00:00:00 2001 From: Krzysztof Durek <21038648+kdurek@users.noreply.github.com> Date: Sun, 17 Nov 2024 21:58:37 +0100 Subject: [PATCH 121/243] feat: add ability for setting current public IP in server IP update form --- .../settings/web-server/update-server-ip.tsx | 40 ++++++++++++++++++- apps/dokploy/server/api/routers/server.ts | 5 +++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx index 4834ef929..264b10ac5 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx @@ -19,8 +19,15 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; +import { RefreshCw } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -41,6 +48,7 @@ export const UpdateServerIp = ({ children, serverId }: Props) => { const [isOpen, setIsOpen] = useState(false); const { data } = api.admin.one.useQuery(); + const { data: ip } = api.server.publicIp.useQuery(); const { mutateAsync, isLoading, error, isError } = api.admin.update.useMutation(); @@ -62,6 +70,11 @@ export const UpdateServerIp = ({ children, serverId }: Props) => { const utils = api.useUtils(); + const setCurrentIp = () => { + if (!ip) return; + form.setValue("serverIp", ip); + }; + const onSubmit = async (data: Schema) => { await mutateAsync({ serverIp: data.serverIp, @@ -97,8 +110,31 @@ export const UpdateServerIp = ({ children, serverId }: Props) => { render={({ field }) => ( Server IP - - + +
+ + + + + + + + +

Set current public IP

+
+
+
+
 										
diff --git a/apps/dokploy/server/api/routers/server.ts b/apps/dokploy/server/api/routers/server.ts
index 6caaa9c8b..97746131c 100644
--- a/apps/dokploy/server/api/routers/server.ts
+++ b/apps/dokploy/server/api/routers/server.ts
@@ -22,6 +22,7 @@ import {
 	findAdminById,
 	findServerById,
 	findServersByAdminId,
+	getPublicIpWithFallback,
 	haveActiveServices,
 	removeDeploymentsByServerId,
 	serverSetup,
@@ -181,4 +182,8 @@ export const serverRouter = createTRPCRouter({
 				throw error;
 			}
 		}),
+	publicIp: protectedProcedure.query(async ({ ctx }) => {
+		const ip = await getPublicIpWithFallback();
+		return ip;
+	}),
 });

From f138b0917fc18a3645bd2065daaa81826a6ec490 Mon Sep 17 00:00:00 2001
From: Krzysztof Durek <21038648+kdurek@users.noreply.github.com>
Date: Sun, 17 Nov 2024 22:05:52 +0100
Subject: [PATCH 122/243] feat: add Polish language support to appearance
 settings and locale configuration

---
 apps/dokploy/components/dashboard/settings/appearance-form.tsx | 3 ++-
 apps/dokploy/next-i18next.config.js                            | 2 +-
 apps/dokploy/pages/_app.tsx                                    | 2 +-
 apps/dokploy/utils/hooks/use-locale.ts                         | 2 +-
 4 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/apps/dokploy/components/dashboard/settings/appearance-form.tsx b/apps/dokploy/components/dashboard/settings/appearance-form.tsx
index a10b0d051..9bafbedab 100644
--- a/apps/dokploy/components/dashboard/settings/appearance-form.tsx
+++ b/apps/dokploy/components/dashboard/settings/appearance-form.tsx
@@ -37,7 +37,7 @@ const appearanceFormSchema = z.object({
 	theme: z.enum(["light", "dark", "system"], {
 		required_error: "Please select a theme.",
 	}),
-	language: z.enum(["en", "zh-Hans"], {
+	language: z.enum(["en", "pl", "zh-Hans"], {
 		required_error: "Please select a language.",
 	}),
 });
@@ -174,6 +174,7 @@ export function AppearanceForm() {
 											
 												{[
 													{ label: "English", value: "en" },
+													{ label: "Polski", value: "pl" },
 													{ label: "简体中文", value: "zh-Hans" },
 												].map((preset) => (
 													
diff --git a/apps/dokploy/next-i18next.config.js b/apps/dokploy/next-i18next.config.js
index 5c20bbea8..bac301cb4 100644
--- a/apps/dokploy/next-i18next.config.js
+++ b/apps/dokploy/next-i18next.config.js
@@ -2,7 +2,7 @@
 module.exports = {
 	i18n: {
 		defaultLocale: "en",
-		locales: ["en", "zh-Hans"],
+		locales: ["en", "pl", "zh-Hans"],
 		localeDetection: false,
 	},
 	fallbackLng: "en",
diff --git a/apps/dokploy/pages/_app.tsx b/apps/dokploy/pages/_app.tsx
index b5fcb1319..18cb3e7e6 100644
--- a/apps/dokploy/pages/_app.tsx
+++ b/apps/dokploy/pages/_app.tsx
@@ -71,7 +71,7 @@ export default api.withTRPC(
 		{
 			i18n: {
 				defaultLocale: "en",
-				locales: ["en", "zh-Hans"],
+				locales: ["en", "pl", "zh-Hans"],
 				localeDetection: false,
 			},
 			fallbackLng: "en",
diff --git a/apps/dokploy/utils/hooks/use-locale.ts b/apps/dokploy/utils/hooks/use-locale.ts
index f00e0df84..3c64f6438 100644
--- a/apps/dokploy/utils/hooks/use-locale.ts
+++ b/apps/dokploy/utils/hooks/use-locale.ts
@@ -1,6 +1,6 @@
 import Cookies from "js-cookie";
 
-const SUPPORTED_LOCALES = ["en", "zh-Hans"] as const;
+const SUPPORTED_LOCALES = ["en", "pl", "zh-Hans"] as const;
 
 type Locale = (typeof SUPPORTED_LOCALES)[number];
 

From 7003fe77c991f5691188b0b5e17765ed78ab8b26 Mon Sep 17 00:00:00 2001
From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Date: Sun, 17 Nov 2024 16:13:07 -0600
Subject: [PATCH 123/243] feat: add shared enviroment variables

---
 apps/dokploy/__test__/env/shared.test.ts      |  179 +
 .../components/dashboard/projects/add-env.tsx |  162 +
 .../components/dashboard/projects/show.tsx    |    5 +-
 apps/dokploy/drizzle/0043_closed_naoko.sql    |    2 +
 .../drizzle/0044_sour_true_believers.sql      |    1 +
 apps/dokploy/drizzle/meta/0043_snapshot.json  | 3982 +++++++++++++++++
 apps/dokploy/drizzle/meta/0044_snapshot.json  | 3975 ++++++++++++++++
 apps/dokploy/drizzle/meta/_journal.json       |   14 +
 packages/server/src/db/schema/project.ts      |   21 +-
 packages/server/src/utils/builders/compose.ts |   10 +-
 .../server/src/utils/builders/docker-file.ts  |   18 +-
 packages/server/src/utils/builders/heroku.ts  |   10 +-
 packages/server/src/utils/builders/index.ts   |   14 +-
 .../server/src/utils/builders/nixpacks.ts     |   10 +-
 packages/server/src/utils/builders/paketo.ts  |   10 +-
 packages/server/src/utils/builders/utils.ts   |   20 +-
 .../server/src/utils/databases/mariadb.ts     |   10 +-
 packages/server/src/utils/databases/mongo.ts  |   10 +-
 packages/server/src/utils/databases/mysql.ts  |   10 +-
 .../server/src/utils/databases/postgres.ts    |   10 +-
 packages/server/src/utils/databases/redis.ts  |   10 +-
 packages/server/src/utils/docker/utils.ts     |   24 +-
 22 files changed, 8469 insertions(+), 38 deletions(-)
 create mode 100644 apps/dokploy/__test__/env/shared.test.ts
 create mode 100644 apps/dokploy/components/dashboard/projects/add-env.tsx
 create mode 100644 apps/dokploy/drizzle/0043_closed_naoko.sql
 create mode 100644 apps/dokploy/drizzle/0044_sour_true_believers.sql
 create mode 100644 apps/dokploy/drizzle/meta/0043_snapshot.json
 create mode 100644 apps/dokploy/drizzle/meta/0044_snapshot.json

diff --git a/apps/dokploy/__test__/env/shared.test.ts b/apps/dokploy/__test__/env/shared.test.ts
new file mode 100644
index 000000000..06e8825fe
--- /dev/null
+++ b/apps/dokploy/__test__/env/shared.test.ts
@@ -0,0 +1,179 @@
+import { prepareEnvironmentVariables } from "@dokploy/server/index";
+import { describe, expect, it } from "vitest";
+
+const projectEnv = `
+ENVIRONMENT=staging
+DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db
+PORT=3000
+`;
+const serviceEnv = `
+ENVIRONMENT=\${{shared.ENVIRONMENT}}
+DATABASE_URL=\${{shared.DATABASE_URL}}
+SERVICE_PORT=4000
+`;
+
+describe("prepareEnvironmentVariables", () => {
+	it("resolves shared variables correctly", () => {
+		const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
+
+		expect(resolved).toEqual([
+			"ENVIRONMENT=staging",
+			"DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db",
+			"SERVICE_PORT=4000",
+		]);
+	});
+
+	it("handles undefined shared variables", () => {
+		const incompleteProjectEnv = `
+		NODE_ENV=production
+		`;
+
+		const invalidServiceEnv = `
+		UNDEFINED_VAR=\${{shared.UNDEFINED_VAR}}
+		`;
+
+		expect(
+			() =>
+				prepareEnvironmentVariables(invalidServiceEnv, incompleteProjectEnv), // Cambiado el orden
+		).toThrow("Invalid shared environment variable: shared.UNDEFINED_VAR");
+	});
+	it("allows service-specific variables to override shared variables", () => {
+		const serviceSpecificEnv = `
+		ENVIRONMENT=production
+		DATABASE_URL=\${{shared.DATABASE_URL}}
+		`;
+
+		const resolved = prepareEnvironmentVariables(
+			serviceSpecificEnv,
+			projectEnv,
+		);
+
+		expect(resolved).toEqual([
+			"ENVIRONMENT=production", // Overrides shared variable
+			"DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db",
+		]);
+	});
+
+	it("resolves complex references for dynamic endpoints", () => {
+		const projectEnv = `
+BASE_URL=https://api.example.com
+API_VERSION=v1
+PORT=8000
+`;
+		const serviceEnv = `
+API_ENDPOINT=\${{shared.BASE_URL}}/\${{shared.API_VERSION}}/endpoint
+SERVICE_PORT=9000
+`;
+		const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
+
+		expect(resolved).toEqual([
+			"API_ENDPOINT=https://api.example.com/v1/endpoint",
+			"SERVICE_PORT=9000",
+		]);
+	});
+
+	it("handles missing shared variables gracefully", () => {
+		const projectEnv = `
+PORT=8080
+`;
+		const serviceEnv = `
+MISSING_VAR=\${{shared.MISSING_KEY}}
+SERVICE_PORT=3000
+`;
+
+		expect(() => prepareEnvironmentVariables(serviceEnv, projectEnv)).toThrow(
+			"Invalid shared environment variable: shared.MISSING_KEY",
+		);
+	});
+
+	it("overrides shared variables with service-specific values", () => {
+		const projectEnv = `
+ENVIRONMENT=staging
+DATABASE_URL=postgres://project:project@localhost:5432/project_db
+`;
+		const serviceEnv = `
+ENVIRONMENT=\${{shared.ENVIRONMENT}}
+DATABASE_URL=postgres://service:service@localhost:5432/service_db
+SERVICE_NAME=my-service
+`;
+		const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
+
+		expect(resolved).toEqual([
+			"ENVIRONMENT=staging",
+			"DATABASE_URL=postgres://service:service@localhost:5432/service_db",
+			"SERVICE_NAME=my-service",
+		]);
+	});
+
+	it("handles shared variables with normal and unusual characters", () => {
+		const projectEnv = `
+ENVIRONMENT=PRODUCTION
+`;
+
+		// Needs to be in quotes
+		const serviceEnv = `
+NODE_ENV=\${{shared.ENVIRONMENT}}
+SPECIAL_VAR="$^@$^@#$^@!#$@#$-\${{shared.ENVIRONMENT}}"
+`;
+
+		const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
+
+		expect(resolved).toEqual([
+			"NODE_ENV=PRODUCTION",
+			"SPECIAL_VAR=$^@$^@#$^@!#$@#$-PRODUCTION",
+		]);
+	});
+
+	it("handles complex cases with multiple references, special characters, and spaces", () => {
+		const projectEnv = `
+ENVIRONMENT=STAGING
+APP_NAME=MyApp
+`;
+
+		const serviceEnv = `
+NODE_ENV=\${{shared.ENVIRONMENT}}
+COMPLEX_VAR="Prefix-$#^!@-\${{shared.ENVIRONMENT}}--\${{shared.APP_NAME}} Suffix "
+`;
+		const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
+
+		expect(resolved).toEqual([
+			"NODE_ENV=STAGING",
+			"COMPLEX_VAR=Prefix-$#^!@-STAGING--MyApp Suffix ",
+		]);
+	});
+
+	it("handles references enclosed in single quotes", () => {
+		const projectEnv = `
+	ENVIRONMENT=STAGING
+	APP_NAME=MyApp
+	`;
+
+		const serviceEnv = `
+	NODE_ENV='\${{shared.ENVIRONMENT}}'
+	COMPLEX_VAR='Prefix-$#^!@-\${{shared.ENVIRONMENT}}--\${{shared.APP_NAME}} Suffix'
+	`;
+		const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
+
+		expect(resolved).toEqual([
+			"NODE_ENV=STAGING",
+			"COMPLEX_VAR=Prefix-$#^!@-STAGING--MyApp Suffix",
+		]);
+	});
+
+	it("handles double and single quotes combined", () => {
+		const projectEnv = `
+ENVIRONMENT=PRODUCTION
+APP_NAME=MyApp
+`;
+		const serviceEnv = `
+NODE_ENV="'\${{shared.ENVIRONMENT}}'"
+COMPLEX_VAR="'Prefix \"DoubleQuoted\" and \${{shared.APP_NAME}}'"
+`;
+		const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
+
+		expect(resolved).toEqual([
+			"NODE_ENV='PRODUCTION'",
+			"COMPLEX_VAR='Prefix \"DoubleQuoted\" and MyApp'",
+		]);
+	});
+});
diff --git a/apps/dokploy/components/dashboard/projects/add-env.tsx b/apps/dokploy/components/dashboard/projects/add-env.tsx
new file mode 100644
index 000000000..5c53848bc
--- /dev/null
+++ b/apps/dokploy/components/dashboard/projects/add-env.tsx
@@ -0,0 +1,162 @@
+import { AlertBlock } from "@/components/shared/alert-block";
+import { CodeEditor } from "@/components/shared/code-editor";
+import { Button } from "@/components/ui/button";
+import {
+	Dialog,
+	DialogContent,
+	DialogDescription,
+	DialogFooter,
+	DialogHeader,
+	DialogTitle,
+	DialogTrigger,
+} from "@/components/ui/dialog";
+import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
+import {
+	Form,
+	FormControl,
+	FormField,
+	FormItem,
+	FormLabel,
+	FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { api } from "@/utils/api";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { AlertTriangle, FileIcon, SquarePen } from "lucide-react";
+import { useEffect, useState } from "react";
+import { useForm } from "react-hook-form";
+import { toast } from "sonner";
+import { z } from "zod";
+
+const updateProjectSchema = z.object({
+	env: z.string().optional(),
+});
+
+type UpdateProject = z.infer;
+
+interface Props {
+	projectId: string;
+}
+
+export const AddEnv = ({ projectId }: Props) => {
+	const [isOpen, setIsOpen] = useState(false);
+	const utils = api.useUtils();
+	const { mutateAsync, error, isError, isLoading } =
+		api.project.update.useMutation();
+	const { data } = api.project.one.useQuery(
+		{
+			projectId,
+		},
+		{
+			enabled: !!projectId,
+		},
+	);
+
+	console.log(data);
+	const form = useForm({
+		defaultValues: {
+			env: data?.env ?? "",
+		},
+		resolver: zodResolver(updateProjectSchema),
+	});
+	useEffect(() => {
+		if (data) {
+			form.reset({
+				env: data.env ?? "",
+			});
+		}
+	}, [data, form, form.reset]);
+
+	const onSubmit = async (formData: UpdateProject) => {
+		await mutateAsync({
+			env: formData.env || "",
+			projectId: projectId,
+		})
+			.then(() => {
+				toast.success("Project env updated succesfully");
+				utils.project.all.invalidate();
+			})
+			.catch(() => {
+				toast.error("Error to update the env");
+			})
+			.finally(() => {});
+	};
+
+	return (
+		
+			
+				 e.preventDefault()}
+				>
+					
+					Add Env
+				
+			
+			
+				
+					Modify Shared Env
+					Update the env variables
+				
+				{isError && {error?.message}}
+				
+					To use a shared env, in one of your services, you need to use like
+					this: Let's say you have a shared env ENVIROMENT="development" and you
+					want to use it in your service, you need to use like this:
+					
    +
  • + ENVIRONMENT=${"${{shared.ENVIRONMENT}}"} +
  • +
  • + DATABASE_URL=${"${{shared.DATABASE_URL}}"} +
  • +
{" "} + This allows the service to inherit and use the shared variables from + the project level, ensuring consistency across services. +
+
+
+
+ + ( + + Enviroment variables + + + + +
+												
+											
+
+ )} + /> + + + + + +
+
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index 6c3ff8cd4..275f9380d 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -35,6 +35,7 @@ import { import Link from "next/link"; import { Fragment } from "react"; import { toast } from "sonner"; +import { AddEnv } from "./add-env"; import { UpdateProject } from "./update"; export const ShowProjects = () => { @@ -190,7 +191,9 @@ export const ShowProjects = () => { Actions - +
e.stopPropagation()}> + +
e.stopPropagation()}>
diff --git a/apps/dokploy/drizzle/0043_closed_naoko.sql b/apps/dokploy/drizzle/0043_closed_naoko.sql new file mode 100644 index 000000000..02e07686c --- /dev/null +++ b/apps/dokploy/drizzle/0043_closed_naoko.sql @@ -0,0 +1,2 @@ +ALTER TABLE "admin" ADD COLUMN "env" text DEFAULT '' NOT NULL;--> statement-breakpoint +ALTER TABLE "project" ADD COLUMN "env" text DEFAULT '' NOT NULL; \ No newline at end of file diff --git a/apps/dokploy/drizzle/0044_sour_true_believers.sql b/apps/dokploy/drizzle/0044_sour_true_believers.sql new file mode 100644 index 000000000..bf088cb33 --- /dev/null +++ b/apps/dokploy/drizzle/0044_sour_true_believers.sql @@ -0,0 +1 @@ +ALTER TABLE "admin" DROP COLUMN IF EXISTS "env"; \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0043_snapshot.json b/apps/dokploy/drizzle/meta/0043_snapshot.json new file mode 100644 index 000000000..c93e4aa17 --- /dev/null +++ b/apps/dokploy/drizzle/meta/0043_snapshot.json @@ -0,0 +1,3982 @@ +{ + "id": "6e07937b-292a-4a59-9a3c-0fae18a1e976", + "prevId": "24bfb192-237f-4297-83d1-27988dcb6be2", + "version": "6", + "dialect": "postgresql", + "tables": { + "public.application": { + "name": "application", + "schema": "", + "columns": { + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildPath": { + "name": "buildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBuildPath": { + "name": "gitlabBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBuildPath": { + "name": "bitbucketBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBuildPath": { + "name": "customGitBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerfile": { + "name": "dockerfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerContextPath": { + "name": "dockerContextPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerBuildStage": { + "name": "dockerBuildStage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropBuildPath": { + "name": "dropBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "buildType": { + "name": "buildType", + "type": "buildType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'nixpacks'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "application", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_registryId_registry_registryId_fk": { + "name": "application_registryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "registryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_projectId_project_projectId_fk": { + "name": "application_projectId_project_projectId_fk", + "tableFrom": "application", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_githubId_github_githubId_fk": { + "name": "application_githubId_github_githubId_fk", + "tableFrom": "application", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_gitlabId_gitlab_gitlabId_fk": { + "name": "application_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "application", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "application_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "application", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_serverId_server_serverId_fk": { + "name": "application_serverId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.postgres": { + "name": "postgres", + "schema": "", + "columns": { + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_projectId_project_projectId_fk": { + "name": "postgres_projectId_project_projectId_fk", + "tableFrom": "postgres", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "postgres_serverId_server_serverId_fk": { + "name": "postgres_serverId_server_serverId_fk", + "tableFrom": "postgres", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "postgres_appName_unique": { + "name": "postgres_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isRegistered": { + "name": "isRegistered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expirationDate": { + "name": "expirationDate", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canCreateProjects": { + "name": "canCreateProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToSSHKeys": { + "name": "canAccessToSSHKeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateServices": { + "name": "canCreateServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteProjects": { + "name": "canDeleteProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteServices": { + "name": "canDeleteServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToDocker": { + "name": "canAccessToDocker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToAPI": { + "name": "canAccessToAPI", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToGitProviders": { + "name": "canAccessToGitProviders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToTraefikFiles": { + "name": "canAccessToTraefikFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accesedServices": { + "name": "accesedServices", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authId": { + "name": "authId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_adminId_admin_adminId_fk": { + "name": "user_adminId_admin_adminId_fk", + "tableFrom": "user", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_authId_auth_id_fk": { + "name": "user_authId_auth_id_fk", + "tableFrom": "user", + "tableTo": "auth", + "columnsFrom": [ + "authId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.admin": { + "name": "admin", + "schema": "", + "columns": { + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverIp": { + "name": "serverIp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "letsEncryptEmail": { + "name": "letsEncryptEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sshPrivateKey": { + "name": "sshPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enableLogRotation": { + "name": "enableLogRotation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "authId": { + "name": "authId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serversQuantity": { + "name": "serversQuantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "admin_authId_auth_id_fk": { + "name": "admin_authId_auth_id_fk", + "tableFrom": "admin", + "tableTo": "auth", + "columnsFrom": [ + "authId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.auth": { + "name": "auth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rol": { + "name": "rol", + "type": "Roles", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is2FAEnabled": { + "name": "is2FAEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resetPasswordToken": { + "name": "resetPasswordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resetPasswordExpiresAt": { + "name": "resetPasswordExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationToken": { + "name": "confirmationToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationExpiresAt": { + "name": "confirmationExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_email_unique": { + "name": "auth_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "project_adminId_admin_adminId_fk": { + "name": "project_adminId_admin_adminId_fk", + "tableFrom": "project", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.domain": { + "name": "domain", + "schema": "", + "columns": { + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domainType": { + "name": "domainType", + "type": "domainType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'application'" + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + } + }, + "indexes": {}, + "foreignKeys": { + "domain_composeId_compose_composeId_fk": { + "name": "domain_composeId_compose_composeId_fk", + "tableFrom": "domain", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_applicationId_application_applicationId_fk": { + "name": "domain_applicationId_application_applicationId_fk", + "tableFrom": "domain", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.mariadb": { + "name": "mariadb", + "schema": "", + "columns": { + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_projectId_project_projectId_fk": { + "name": "mariadb_projectId_project_projectId_fk", + "tableFrom": "mariadb", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mariadb_serverId_server_serverId_fk": { + "name": "mariadb_serverId_server_serverId_fk", + "tableFrom": "mariadb", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mariadb_appName_unique": { + "name": "mariadb_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.mongo": { + "name": "mongo", + "schema": "", + "columns": { + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mongo_projectId_project_projectId_fk": { + "name": "mongo_projectId_project_projectId_fk", + "tableFrom": "mongo", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mongo_serverId_server_serverId_fk": { + "name": "mongo_serverId_server_serverId_fk", + "tableFrom": "mongo", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mongo_appName_unique": { + "name": "mongo_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.mysql": { + "name": "mysql", + "schema": "", + "columns": { + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_projectId_project_projectId_fk": { + "name": "mysql_projectId_project_projectId_fk", + "tableFrom": "mysql", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mysql_serverId_server_serverId_fk": { + "name": "mysql_serverId_server_serverId_fk", + "tableFrom": "mysql", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mysql_appName_unique": { + "name": "mysql_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.backup": { + "name": "backup", + "schema": "", + "columns": { + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "database": { + "name": "database", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseType": { + "name": "databaseType", + "type": "databaseType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_destinationId_destination_destinationId_fk": { + "name": "backup_destinationId_destination_destinationId_fk", + "tableFrom": "backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_postgresId_postgres_postgresId_fk": { + "name": "backup_postgresId_postgres_postgresId_fk", + "tableFrom": "backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mariadbId_mariadb_mariadbId_fk": { + "name": "backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mysqlId_mysql_mysqlId_fk": { + "name": "backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mongoId_mongo_mongoId_fk": { + "name": "backup_mongoId_mongo_mongoId_fk", + "tableFrom": "backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.destination": { + "name": "destination", + "schema": "", + "columns": { + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessKey": { + "name": "accessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "destination_adminId_admin_adminId_fk": { + "name": "destination_adminId_admin_adminId_fk", + "tableFrom": "destination", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "deploymentStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'running'" + }, + "logPath": { + "name": "logPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "deployment_applicationId_application_applicationId_fk": { + "name": "deployment_applicationId_application_applicationId_fk", + "tableFrom": "deployment", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_composeId_compose_composeId_fk": { + "name": "deployment_composeId_compose_composeId_fk", + "tableFrom": "deployment", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_serverId_server_serverId_fk": { + "name": "deployment_serverId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.mount": { + "name": "mount", + "schema": "", + "columns": { + "mountId": { + "name": "mountId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "mountType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "hostPath": { + "name": "hostPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "mountPath": { + "name": "mountPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mount_applicationId_application_applicationId_fk": { + "name": "mount_applicationId_application_applicationId_fk", + "tableFrom": "mount", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_postgresId_postgres_postgresId_fk": { + "name": "mount_postgresId_postgres_postgresId_fk", + "tableFrom": "mount", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mariadbId_mariadb_mariadbId_fk": { + "name": "mount_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "mount", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mongoId_mongo_mongoId_fk": { + "name": "mount_mongoId_mongo_mongoId_fk", + "tableFrom": "mount", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mysqlId_mysql_mysqlId_fk": { + "name": "mount_mysqlId_mysql_mysqlId_fk", + "tableFrom": "mount", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_redisId_redis_redisId_fk": { + "name": "mount_redisId_redis_redisId_fk", + "tableFrom": "mount", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_composeId_compose_composeId_fk": { + "name": "mount_composeId_compose_composeId_fk", + "tableFrom": "mount", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "certificateId": { + "name": "certificateId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificateData": { + "name": "certificateData", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificatePath": { + "name": "certificatePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autoRenew": { + "name": "autoRenew", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "certificate_adminId_admin_adminId_fk": { + "name": "certificate_adminId_admin_adminId_fk", + "tableFrom": "certificate", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "certificate_serverId_server_serverId_fk": { + "name": "certificate_serverId_server_serverId_fk", + "tableFrom": "certificate", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_certificatePath_unique": { + "name": "certificate_certificatePath_unique", + "nullsNotDistinct": false, + "columns": [ + "certificatePath" + ] + } + } + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_auth_id_fk": { + "name": "session_user_id_auth_id_fk", + "tableFrom": "session", + "tableTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.redirect": { + "name": "redirect", + "schema": "", + "columns": { + "redirectId": { + "name": "redirectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "regex": { + "name": "regex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanent": { + "name": "permanent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "redirect_applicationId_application_applicationId_fk": { + "name": "redirect_applicationId_application_applicationId_fk", + "tableFrom": "redirect", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.security": { + "name": "security", + "schema": "", + "columns": { + "securityId": { + "name": "securityId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "security_applicationId_application_applicationId_fk": { + "name": "security_applicationId_application_applicationId_fk", + "tableFrom": "security", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_username_applicationId_unique": { + "name": "security_username_applicationId_unique", + "nullsNotDistinct": false, + "columns": [ + "username", + "applicationId" + ] + } + } + }, + "public.port": { + "name": "port", + "schema": "", + "columns": { + "portId": { + "name": "portId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "publishedPort": { + "name": "publishedPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "targetPort": { + "name": "targetPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "protocolType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "port_applicationId_application_applicationId_fk": { + "name": "port_applicationId_application_applicationId_fk", + "tableFrom": "port", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.redis": { + "name": "redis", + "schema": "", + "columns": { + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_projectId_project_projectId_fk": { + "name": "redis_projectId_project_projectId_fk", + "tableFrom": "redis", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "redis_serverId_server_serverId_fk": { + "name": "redis_serverId_server_serverId_fk", + "tableFrom": "redis", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "redis_appName_unique": { + "name": "redis_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.compose": { + "name": "compose", + "schema": "", + "columns": { + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeFile": { + "name": "composeFile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceTypeCompose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "composeType": { + "name": "composeType", + "type": "composeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'docker-compose'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "composePath": { + "name": "composePath", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'./docker-compose.yml'" + }, + "suffix": { + "name": "suffix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "randomize": { + "name": "randomize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "composeStatus": { + "name": "composeStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "compose", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_projectId_project_projectId_fk": { + "name": "compose_projectId_project_projectId_fk", + "tableFrom": "compose", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compose_githubId_github_githubId_fk": { + "name": "compose_githubId_github_githubId_fk", + "tableFrom": "compose", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_gitlabId_gitlab_gitlabId_fk": { + "name": "compose_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "compose", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "compose_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "compose", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_serverId_server_serverId_fk": { + "name": "compose_serverId_server_serverId_fk", + "tableFrom": "compose", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.registry": { + "name": "registry", + "schema": "", + "columns": { + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "registryName": { + "name": "registryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePrefix": { + "name": "imagePrefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selfHosted": { + "name": "selfHosted", + "type": "RegistryType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "registry_adminId_admin_adminId_fk": { + "name": "registry_adminId_admin_adminId_fk", + "tableFrom": "registry", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.discord": { + "name": "discord", + "schema": "", + "columns": { + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.email": { + "name": "email", + "schema": "", + "columns": { + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "smtpServer": { + "name": "smtpServer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtpPort": { + "name": "smtpPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "notificationId": { + "name": "notificationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appDeploy": { + "name": "appDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "appBuildError": { + "name": "appBuildError", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "databaseBackup": { + "name": "databaseBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dokployRestart": { + "name": "dokployRestart", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerCleanup": { + "name": "dockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notificationType": { + "name": "notificationType", + "type": "notificationType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "notification_slackId_slack_slackId_fk": { + "name": "notification_slackId_slack_slackId_fk", + "tableFrom": "notification", + "tableTo": "slack", + "columnsFrom": [ + "slackId" + ], + "columnsTo": [ + "slackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_telegramId_telegram_telegramId_fk": { + "name": "notification_telegramId_telegram_telegramId_fk", + "tableFrom": "notification", + "tableTo": "telegram", + "columnsFrom": [ + "telegramId" + ], + "columnsTo": [ + "telegramId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_discordId_discord_discordId_fk": { + "name": "notification_discordId_discord_discordId_fk", + "tableFrom": "notification", + "tableTo": "discord", + "columnsFrom": [ + "discordId" + ], + "columnsTo": [ + "discordId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_emailId_email_emailId_fk": { + "name": "notification_emailId_email_emailId_fk", + "tableFrom": "notification", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "emailId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_adminId_admin_adminId_fk": { + "name": "notification_adminId_admin_adminId_fk", + "tableFrom": "notification", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.slack": { + "name": "slack", + "schema": "", + "columns": { + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.telegram": { + "name": "telegram", + "schema": "", + "columns": { + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "botToken": { + "name": "botToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatId": { + "name": "chatId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.ssh-key": { + "name": "ssh-key", + "schema": "", + "columns": { + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "ssh-key_adminId_admin_adminId_fk": { + "name": "ssh-key_adminId_admin_adminId_fk", + "tableFrom": "ssh-key", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerType": { + "name": "providerType", + "type": "gitProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "git_provider_adminId_admin_adminId_fk": { + "name": "git_provider_adminId_admin_adminId_fk", + "tableFrom": "git_provider", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketWorkspaceName": { + "name": "bitbucketWorkspaceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bitbucket_gitProviderId_git_provider_gitProviderId_fk": { + "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "bitbucket", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.github": { + "name": "github", + "schema": "", + "columns": { + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "githubAppName": { + "name": "githubAppName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubAppId": { + "name": "githubAppId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "githubClientId": { + "name": "githubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubClientSecret": { + "name": "githubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubInstallationId": { + "name": "githubInstallationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubPrivateKey": { + "name": "githubPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubWebhookSecret": { + "name": "githubWebhookSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_gitProviderId_git_provider_gitProviderId_fk": { + "name": "github_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "github", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.gitlab": { + "name": "gitlab", + "schema": "", + "columns": { + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gitlab_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitlab", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.server": { + "name": "server", + "schema": "", + "columns": { + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverStatus": { + "name": "serverStatus", + "type": "serverStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "server_adminId_admin_adminId_fk": { + "name": "server_adminId_admin_adminId_fk", + "tableFrom": "server", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_sshKeyId_ssh-key_sshKeyId_fk": { + "name": "server_sshKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "server", + "tableTo": "ssh-key", + "columnsFrom": [ + "sshKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.buildType": { + "name": "buildType", + "schema": "public", + "values": [ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static" + ] + }, + "public.sourceType": { + "name": "sourceType", + "schema": "public", + "values": [ + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "drop" + ] + }, + "public.Roles": { + "name": "Roles", + "schema": "public", + "values": [ + "admin", + "user" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error" + ] + }, + "public.mountType": { + "name": "mountType", + "schema": "public", + "values": [ + "bind", + "volume", + "file" + ] + }, + "public.serviceType": { + "name": "serviceType", + "schema": "public", + "values": [ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.applicationStatus": { + "name": "applicationStatus", + "schema": "public", + "values": [ + "idle", + "running", + "done", + "error" + ] + }, + "public.certificateType": { + "name": "certificateType", + "schema": "public", + "values": [ + "letsencrypt", + "none" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "raw" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0044_snapshot.json b/apps/dokploy/drizzle/meta/0044_snapshot.json new file mode 100644 index 000000000..dd6287995 --- /dev/null +++ b/apps/dokploy/drizzle/meta/0044_snapshot.json @@ -0,0 +1,3975 @@ +{ + "id": "172ee8c6-ba83-460b-83e1-b61ba19f450b", + "prevId": "6e07937b-292a-4a59-9a3c-0fae18a1e976", + "version": "6", + "dialect": "postgresql", + "tables": { + "public.application": { + "name": "application", + "schema": "", + "columns": { + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildPath": { + "name": "buildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBuildPath": { + "name": "gitlabBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBuildPath": { + "name": "bitbucketBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBuildPath": { + "name": "customGitBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerfile": { + "name": "dockerfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerContextPath": { + "name": "dockerContextPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerBuildStage": { + "name": "dockerBuildStage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropBuildPath": { + "name": "dropBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "buildType": { + "name": "buildType", + "type": "buildType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'nixpacks'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "application", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_registryId_registry_registryId_fk": { + "name": "application_registryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "registryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_projectId_project_projectId_fk": { + "name": "application_projectId_project_projectId_fk", + "tableFrom": "application", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_githubId_github_githubId_fk": { + "name": "application_githubId_github_githubId_fk", + "tableFrom": "application", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_gitlabId_gitlab_gitlabId_fk": { + "name": "application_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "application", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "application_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "application", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_serverId_server_serverId_fk": { + "name": "application_serverId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.postgres": { + "name": "postgres", + "schema": "", + "columns": { + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_projectId_project_projectId_fk": { + "name": "postgres_projectId_project_projectId_fk", + "tableFrom": "postgres", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "postgres_serverId_server_serverId_fk": { + "name": "postgres_serverId_server_serverId_fk", + "tableFrom": "postgres", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "postgres_appName_unique": { + "name": "postgres_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isRegistered": { + "name": "isRegistered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expirationDate": { + "name": "expirationDate", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canCreateProjects": { + "name": "canCreateProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToSSHKeys": { + "name": "canAccessToSSHKeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateServices": { + "name": "canCreateServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteProjects": { + "name": "canDeleteProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteServices": { + "name": "canDeleteServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToDocker": { + "name": "canAccessToDocker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToAPI": { + "name": "canAccessToAPI", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToGitProviders": { + "name": "canAccessToGitProviders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToTraefikFiles": { + "name": "canAccessToTraefikFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accesedServices": { + "name": "accesedServices", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authId": { + "name": "authId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_adminId_admin_adminId_fk": { + "name": "user_adminId_admin_adminId_fk", + "tableFrom": "user", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_authId_auth_id_fk": { + "name": "user_authId_auth_id_fk", + "tableFrom": "user", + "tableTo": "auth", + "columnsFrom": [ + "authId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.admin": { + "name": "admin", + "schema": "", + "columns": { + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverIp": { + "name": "serverIp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "letsEncryptEmail": { + "name": "letsEncryptEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sshPrivateKey": { + "name": "sshPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enableLogRotation": { + "name": "enableLogRotation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "authId": { + "name": "authId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serversQuantity": { + "name": "serversQuantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "admin_authId_auth_id_fk": { + "name": "admin_authId_auth_id_fk", + "tableFrom": "admin", + "tableTo": "auth", + "columnsFrom": [ + "authId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.auth": { + "name": "auth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rol": { + "name": "rol", + "type": "Roles", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is2FAEnabled": { + "name": "is2FAEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resetPasswordToken": { + "name": "resetPasswordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resetPasswordExpiresAt": { + "name": "resetPasswordExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationToken": { + "name": "confirmationToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationExpiresAt": { + "name": "confirmationExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_email_unique": { + "name": "auth_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "project_adminId_admin_adminId_fk": { + "name": "project_adminId_admin_adminId_fk", + "tableFrom": "project", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.domain": { + "name": "domain", + "schema": "", + "columns": { + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domainType": { + "name": "domainType", + "type": "domainType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'application'" + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + } + }, + "indexes": {}, + "foreignKeys": { + "domain_composeId_compose_composeId_fk": { + "name": "domain_composeId_compose_composeId_fk", + "tableFrom": "domain", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_applicationId_application_applicationId_fk": { + "name": "domain_applicationId_application_applicationId_fk", + "tableFrom": "domain", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.mariadb": { + "name": "mariadb", + "schema": "", + "columns": { + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_projectId_project_projectId_fk": { + "name": "mariadb_projectId_project_projectId_fk", + "tableFrom": "mariadb", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mariadb_serverId_server_serverId_fk": { + "name": "mariadb_serverId_server_serverId_fk", + "tableFrom": "mariadb", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mariadb_appName_unique": { + "name": "mariadb_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.mongo": { + "name": "mongo", + "schema": "", + "columns": { + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mongo_projectId_project_projectId_fk": { + "name": "mongo_projectId_project_projectId_fk", + "tableFrom": "mongo", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mongo_serverId_server_serverId_fk": { + "name": "mongo_serverId_server_serverId_fk", + "tableFrom": "mongo", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mongo_appName_unique": { + "name": "mongo_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.mysql": { + "name": "mysql", + "schema": "", + "columns": { + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_projectId_project_projectId_fk": { + "name": "mysql_projectId_project_projectId_fk", + "tableFrom": "mysql", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mysql_serverId_server_serverId_fk": { + "name": "mysql_serverId_server_serverId_fk", + "tableFrom": "mysql", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mysql_appName_unique": { + "name": "mysql_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.backup": { + "name": "backup", + "schema": "", + "columns": { + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "database": { + "name": "database", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseType": { + "name": "databaseType", + "type": "databaseType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_destinationId_destination_destinationId_fk": { + "name": "backup_destinationId_destination_destinationId_fk", + "tableFrom": "backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_postgresId_postgres_postgresId_fk": { + "name": "backup_postgresId_postgres_postgresId_fk", + "tableFrom": "backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mariadbId_mariadb_mariadbId_fk": { + "name": "backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mysqlId_mysql_mysqlId_fk": { + "name": "backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mongoId_mongo_mongoId_fk": { + "name": "backup_mongoId_mongo_mongoId_fk", + "tableFrom": "backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.destination": { + "name": "destination", + "schema": "", + "columns": { + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessKey": { + "name": "accessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "destination_adminId_admin_adminId_fk": { + "name": "destination_adminId_admin_adminId_fk", + "tableFrom": "destination", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "deploymentStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'running'" + }, + "logPath": { + "name": "logPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "deployment_applicationId_application_applicationId_fk": { + "name": "deployment_applicationId_application_applicationId_fk", + "tableFrom": "deployment", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_composeId_compose_composeId_fk": { + "name": "deployment_composeId_compose_composeId_fk", + "tableFrom": "deployment", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_serverId_server_serverId_fk": { + "name": "deployment_serverId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.mount": { + "name": "mount", + "schema": "", + "columns": { + "mountId": { + "name": "mountId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "mountType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "hostPath": { + "name": "hostPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "mountPath": { + "name": "mountPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mount_applicationId_application_applicationId_fk": { + "name": "mount_applicationId_application_applicationId_fk", + "tableFrom": "mount", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_postgresId_postgres_postgresId_fk": { + "name": "mount_postgresId_postgres_postgresId_fk", + "tableFrom": "mount", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mariadbId_mariadb_mariadbId_fk": { + "name": "mount_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "mount", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mongoId_mongo_mongoId_fk": { + "name": "mount_mongoId_mongo_mongoId_fk", + "tableFrom": "mount", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mysqlId_mysql_mysqlId_fk": { + "name": "mount_mysqlId_mysql_mysqlId_fk", + "tableFrom": "mount", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_redisId_redis_redisId_fk": { + "name": "mount_redisId_redis_redisId_fk", + "tableFrom": "mount", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_composeId_compose_composeId_fk": { + "name": "mount_composeId_compose_composeId_fk", + "tableFrom": "mount", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "certificateId": { + "name": "certificateId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificateData": { + "name": "certificateData", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificatePath": { + "name": "certificatePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autoRenew": { + "name": "autoRenew", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "certificate_adminId_admin_adminId_fk": { + "name": "certificate_adminId_admin_adminId_fk", + "tableFrom": "certificate", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "certificate_serverId_server_serverId_fk": { + "name": "certificate_serverId_server_serverId_fk", + "tableFrom": "certificate", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_certificatePath_unique": { + "name": "certificate_certificatePath_unique", + "nullsNotDistinct": false, + "columns": [ + "certificatePath" + ] + } + } + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_auth_id_fk": { + "name": "session_user_id_auth_id_fk", + "tableFrom": "session", + "tableTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.redirect": { + "name": "redirect", + "schema": "", + "columns": { + "redirectId": { + "name": "redirectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "regex": { + "name": "regex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanent": { + "name": "permanent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "redirect_applicationId_application_applicationId_fk": { + "name": "redirect_applicationId_application_applicationId_fk", + "tableFrom": "redirect", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.security": { + "name": "security", + "schema": "", + "columns": { + "securityId": { + "name": "securityId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "security_applicationId_application_applicationId_fk": { + "name": "security_applicationId_application_applicationId_fk", + "tableFrom": "security", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_username_applicationId_unique": { + "name": "security_username_applicationId_unique", + "nullsNotDistinct": false, + "columns": [ + "username", + "applicationId" + ] + } + } + }, + "public.port": { + "name": "port", + "schema": "", + "columns": { + "portId": { + "name": "portId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "publishedPort": { + "name": "publishedPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "targetPort": { + "name": "targetPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "protocolType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "port_applicationId_application_applicationId_fk": { + "name": "port_applicationId_application_applicationId_fk", + "tableFrom": "port", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.redis": { + "name": "redis", + "schema": "", + "columns": { + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_projectId_project_projectId_fk": { + "name": "redis_projectId_project_projectId_fk", + "tableFrom": "redis", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "redis_serverId_server_serverId_fk": { + "name": "redis_serverId_server_serverId_fk", + "tableFrom": "redis", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "redis_appName_unique": { + "name": "redis_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.compose": { + "name": "compose", + "schema": "", + "columns": { + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeFile": { + "name": "composeFile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceTypeCompose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "composeType": { + "name": "composeType", + "type": "composeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'docker-compose'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "composePath": { + "name": "composePath", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'./docker-compose.yml'" + }, + "suffix": { + "name": "suffix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "randomize": { + "name": "randomize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "composeStatus": { + "name": "composeStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "compose", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_projectId_project_projectId_fk": { + "name": "compose_projectId_project_projectId_fk", + "tableFrom": "compose", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compose_githubId_github_githubId_fk": { + "name": "compose_githubId_github_githubId_fk", + "tableFrom": "compose", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_gitlabId_gitlab_gitlabId_fk": { + "name": "compose_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "compose", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "compose_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "compose", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_serverId_server_serverId_fk": { + "name": "compose_serverId_server_serverId_fk", + "tableFrom": "compose", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.registry": { + "name": "registry", + "schema": "", + "columns": { + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "registryName": { + "name": "registryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePrefix": { + "name": "imagePrefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selfHosted": { + "name": "selfHosted", + "type": "RegistryType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "registry_adminId_admin_adminId_fk": { + "name": "registry_adminId_admin_adminId_fk", + "tableFrom": "registry", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.discord": { + "name": "discord", + "schema": "", + "columns": { + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.email": { + "name": "email", + "schema": "", + "columns": { + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "smtpServer": { + "name": "smtpServer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtpPort": { + "name": "smtpPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "notificationId": { + "name": "notificationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appDeploy": { + "name": "appDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "appBuildError": { + "name": "appBuildError", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "databaseBackup": { + "name": "databaseBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dokployRestart": { + "name": "dokployRestart", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerCleanup": { + "name": "dockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notificationType": { + "name": "notificationType", + "type": "notificationType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "notification_slackId_slack_slackId_fk": { + "name": "notification_slackId_slack_slackId_fk", + "tableFrom": "notification", + "tableTo": "slack", + "columnsFrom": [ + "slackId" + ], + "columnsTo": [ + "slackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_telegramId_telegram_telegramId_fk": { + "name": "notification_telegramId_telegram_telegramId_fk", + "tableFrom": "notification", + "tableTo": "telegram", + "columnsFrom": [ + "telegramId" + ], + "columnsTo": [ + "telegramId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_discordId_discord_discordId_fk": { + "name": "notification_discordId_discord_discordId_fk", + "tableFrom": "notification", + "tableTo": "discord", + "columnsFrom": [ + "discordId" + ], + "columnsTo": [ + "discordId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_emailId_email_emailId_fk": { + "name": "notification_emailId_email_emailId_fk", + "tableFrom": "notification", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "emailId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_adminId_admin_adminId_fk": { + "name": "notification_adminId_admin_adminId_fk", + "tableFrom": "notification", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.slack": { + "name": "slack", + "schema": "", + "columns": { + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.telegram": { + "name": "telegram", + "schema": "", + "columns": { + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "botToken": { + "name": "botToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatId": { + "name": "chatId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.ssh-key": { + "name": "ssh-key", + "schema": "", + "columns": { + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "ssh-key_adminId_admin_adminId_fk": { + "name": "ssh-key_adminId_admin_adminId_fk", + "tableFrom": "ssh-key", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerType": { + "name": "providerType", + "type": "gitProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "git_provider_adminId_admin_adminId_fk": { + "name": "git_provider_adminId_admin_adminId_fk", + "tableFrom": "git_provider", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketWorkspaceName": { + "name": "bitbucketWorkspaceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bitbucket_gitProviderId_git_provider_gitProviderId_fk": { + "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "bitbucket", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.github": { + "name": "github", + "schema": "", + "columns": { + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "githubAppName": { + "name": "githubAppName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubAppId": { + "name": "githubAppId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "githubClientId": { + "name": "githubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubClientSecret": { + "name": "githubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubInstallationId": { + "name": "githubInstallationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubPrivateKey": { + "name": "githubPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubWebhookSecret": { + "name": "githubWebhookSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_gitProviderId_git_provider_gitProviderId_fk": { + "name": "github_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "github", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.gitlab": { + "name": "gitlab", + "schema": "", + "columns": { + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gitlab_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitlab", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.server": { + "name": "server", + "schema": "", + "columns": { + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverStatus": { + "name": "serverStatus", + "type": "serverStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "server_adminId_admin_adminId_fk": { + "name": "server_adminId_admin_adminId_fk", + "tableFrom": "server", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_sshKeyId_ssh-key_sshKeyId_fk": { + "name": "server_sshKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "server", + "tableTo": "ssh-key", + "columnsFrom": [ + "sshKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.buildType": { + "name": "buildType", + "schema": "public", + "values": [ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static" + ] + }, + "public.sourceType": { + "name": "sourceType", + "schema": "public", + "values": [ + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "drop" + ] + }, + "public.Roles": { + "name": "Roles", + "schema": "public", + "values": [ + "admin", + "user" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error" + ] + }, + "public.mountType": { + "name": "mountType", + "schema": "public", + "values": [ + "bind", + "volume", + "file" + ] + }, + "public.serviceType": { + "name": "serviceType", + "schema": "public", + "values": [ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.applicationStatus": { + "name": "applicationStatus", + "schema": "public", + "values": [ + "idle", + "running", + "done", + "error" + ] + }, + "public.certificateType": { + "name": "certificateType", + "schema": "public", + "values": [ + "letsencrypt", + "none" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "raw" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json index 72851672e..25673c694 100644 --- a/apps/dokploy/drizzle/meta/_journal.json +++ b/apps/dokploy/drizzle/meta/_journal.json @@ -302,6 +302,20 @@ "when": 1729984439862, "tag": "0042_fancy_havok", "breakpoints": true + }, + { + "idx": 43, + "version": "6", + "when": 1731873965888, + "tag": "0043_closed_naoko", + "breakpoints": true + }, + { + "idx": 44, + "version": "6", + "when": 1731875539532, + "tag": "0044_sour_true_believers", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/server/src/db/schema/project.ts b/packages/server/src/db/schema/project.ts index 9370d6f2e..7ed140d6f 100644 --- a/packages/server/src/db/schema/project.ts +++ b/packages/server/src/db/schema/project.ts @@ -26,6 +26,7 @@ export const projects = pgTable("project", { adminId: text("adminId") .notNull() .references(() => admins.adminId, { onDelete: "cascade" }), + env: text("env").notNull().default(""), }); export const projectRelations = relations(projects, ({ many, one }) => ({ @@ -65,10 +66,16 @@ export const apiRemoveProject = createSchema }) .required(); -export const apiUpdateProject = createSchema - .pick({ - name: true, - description: true, - projectId: true, - }) - .required(); +// export const apiUpdateProject = createSchema +// .pick({ +// name: true, +// description: true, +// projectId: true, +// env: true, +// }) +// .required(); + +export const apiUpdateProject = createSchema.partial().extend({ + projectId: z.string().min(1), +}); +// .omit({ serverId: true }); diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts index 7d3ce0ec9..3e64ed35e 100644 --- a/packages/server/src/utils/builders/compose.ts +++ b/packages/server/src/utils/builders/compose.ts @@ -180,7 +180,10 @@ const createEnvFile = (compose: ComposeNested) => { envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`; } - const envFileContent = prepareEnvironmentVariables(envContent).join("\n"); + const envFileContent = prepareEnvironmentVariables( + envContent, + compose.project.env, + ).join("\n"); if (!existsSync(dirname(envFilePath))) { mkdirSync(dirname(envFilePath), { recursive: true }); @@ -206,7 +209,10 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => { envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`; } - const envFileContent = prepareEnvironmentVariables(envContent).join("\n"); + const envFileContent = prepareEnvironmentVariables( + envContent, + compose.project.env, + ).join("\n"); const encodedContent = encodeBase64(envFileContent); return ` diff --git a/packages/server/src/utils/builders/docker-file.ts b/packages/server/src/utils/builders/docker-file.ts index f8d4a9298..cb6187122 100644 --- a/packages/server/src/utils/builders/docker-file.ts +++ b/packages/server/src/utils/builders/docker-file.ts @@ -20,7 +20,10 @@ export const buildCustomDocker = async ( const defaultContextPath = dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || "."; - const args = prepareEnvironmentVariables(buildArgs); + const args = prepareEnvironmentVariables( + buildArgs, + application.project.env, + ); const dockerContextPath = getDockerContextPath(application); @@ -38,7 +41,7 @@ export const buildCustomDocker = async ( as it could be publicly exposed. */ if (!publishDirectory) { - createEnvFile(dockerFilePath, env); + createEnvFile(dockerFilePath, env, application.project.env); } await spawnAsync( @@ -71,7 +74,10 @@ export const getDockerCommand = ( const defaultContextPath = dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || "."; - const args = prepareEnvironmentVariables(buildArgs); + const args = prepareEnvironmentVariables( + buildArgs, + application.project.env, + ); const dockerContextPath = getDockerContextPath(application) || defaultContextPath; @@ -92,7 +98,11 @@ export const getDockerCommand = ( */ let command = ""; if (!publishDirectory) { - command += createEnvFileCommand(dockerFilePath, env); + command += createEnvFileCommand( + dockerFilePath, + env, + application.project.env, + ); } command += ` diff --git a/packages/server/src/utils/builders/heroku.ts b/packages/server/src/utils/builders/heroku.ts index e3039ebf4..999b5fe61 100644 --- a/packages/server/src/utils/builders/heroku.ts +++ b/packages/server/src/utils/builders/heroku.ts @@ -11,7 +11,10 @@ export const buildHeroku = async ( ) => { const { env, appName } = application; const buildAppDirectory = getBuildAppDirectory(application); - const envVariables = prepareEnvironmentVariables(env); + const envVariables = prepareEnvironmentVariables( + env, + application.project.env, + ); try { const args = [ "build", @@ -44,7 +47,10 @@ export const getHerokuCommand = ( const { env, appName } = application; const buildAppDirectory = getBuildAppDirectory(application); - const envVariables = prepareEnvironmentVariables(env); + const envVariables = prepareEnvironmentVariables( + env, + application.project.env, + ); const args = [ "build", diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts index a07656025..702121d2d 100644 --- a/packages/server/src/utils/builders/index.ts +++ b/packages/server/src/utils/builders/index.ts @@ -24,7 +24,14 @@ import { buildStatic, getStaticCommand } from "./static"; // DOCKERFILE codeDirectory = where is the exact path of the (Dockerfile) export type ApplicationNested = InferResultType< "applications", - { mounts: true; security: true; redirects: true; ports: true; registry: true } + { + mounts: true; + security: true; + redirects: true; + ports: true; + registry: true; + project: true; + } >; export const buildApplication = async ( application: ApplicationNested, @@ -133,7 +140,10 @@ export const mechanizeDockerContainer = async ( const bindsMount = generateBindMounts(mounts); const filesMount = generateFileMounts(appName, application); - const envVariables = prepareEnvironmentVariables(env); + const envVariables = prepareEnvironmentVariables( + env, + application.project.env, + ); const image = getImageName(application); const authConfig = getAuthConfig(application); diff --git a/packages/server/src/utils/builders/nixpacks.ts b/packages/server/src/utils/builders/nixpacks.ts index 81e6f0612..7c10e4c07 100644 --- a/packages/server/src/utils/builders/nixpacks.ts +++ b/packages/server/src/utils/builders/nixpacks.ts @@ -18,7 +18,10 @@ export const buildNixpacks = async ( const buildAppDirectory = getBuildAppDirectory(application); const buildContainerId = `${appName}-${nanoid(10)}`; - const envVariables = prepareEnvironmentVariables(env); + const envVariables = prepareEnvironmentVariables( + env, + application.project.env, + ); const writeToStream = (data: string) => { if (writeStream.writable) { @@ -92,7 +95,10 @@ export const getNixpacksCommand = ( const buildAppDirectory = getBuildAppDirectory(application); const buildContainerId = `${appName}-${nanoid(10)}`; - const envVariables = prepareEnvironmentVariables(env); + const envVariables = prepareEnvironmentVariables( + env, + application.project.env, + ); const args = ["build", buildAppDirectory, "--name", appName]; diff --git a/packages/server/src/utils/builders/paketo.ts b/packages/server/src/utils/builders/paketo.ts index f7d170ead..21eef46d8 100644 --- a/packages/server/src/utils/builders/paketo.ts +++ b/packages/server/src/utils/builders/paketo.ts @@ -10,7 +10,10 @@ export const buildPaketo = async ( ) => { const { env, appName } = application; const buildAppDirectory = getBuildAppDirectory(application); - const envVariables = prepareEnvironmentVariables(env); + const envVariables = prepareEnvironmentVariables( + env, + application.project.env, + ); try { const args = [ "build", @@ -43,7 +46,10 @@ export const getPaketoCommand = ( const { env, appName } = application; const buildAppDirectory = getBuildAppDirectory(application); - const envVariables = prepareEnvironmentVariables(env); + const envVariables = prepareEnvironmentVariables( + env, + application.project.env, + ); const args = [ "build", diff --git a/packages/server/src/utils/builders/utils.ts b/packages/server/src/utils/builders/utils.ts index a72707f2f..8eb5bbb07 100644 --- a/packages/server/src/utils/builders/utils.ts +++ b/packages/server/src/utils/builders/utils.ts @@ -2,17 +2,29 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { encodeBase64, prepareEnvironmentVariables } from "../docker/utils"; -export const createEnvFile = (directory: string, env: string | null) => { +export const createEnvFile = ( + directory: string, + env: string | null, + projectEnv?: string | null, +) => { const envFilePath = join(dirname(directory), ".env"); if (!existsSync(dirname(envFilePath))) { mkdirSync(dirname(envFilePath), { recursive: true }); } - const envFileContent = prepareEnvironmentVariables(env).join("\n"); + const envFileContent = prepareEnvironmentVariables(env, projectEnv).join( + "\n", + ); writeFileSync(envFilePath, envFileContent); }; -export const createEnvFileCommand = (directory: string, env: string | null) => { - const envFileContent = prepareEnvironmentVariables(env).join("\n"); +export const createEnvFileCommand = ( + directory: string, + env: string | null, + projectEnv?: string | null, +) => { + const envFileContent = prepareEnvironmentVariables(env, projectEnv).join( + "\n", + ); const encodedContent = encodeBase64(envFileContent || ""); const envFilePath = join(dirname(directory), ".env"); diff --git a/packages/server/src/utils/databases/mariadb.ts b/packages/server/src/utils/databases/mariadb.ts index ab9a32a81..d1b41fc33 100644 --- a/packages/server/src/utils/databases/mariadb.ts +++ b/packages/server/src/utils/databases/mariadb.ts @@ -9,7 +9,10 @@ import { } from "../docker/utils"; import { getRemoteDocker } from "../servers/remote-docker"; -export type MariadbNested = InferResultType<"mariadb", { mounts: true }>; +export type MariadbNested = InferResultType< + "mariadb", + { mounts: true; project: true } +>; export const buildMariadb = async (mariadb: MariadbNested) => { const { appName, @@ -37,7 +40,10 @@ export const buildMariadb = async (mariadb: MariadbNested) => { cpuLimit, cpuReservation, }); - const envVariables = prepareEnvironmentVariables(defaultMariadbEnv); + const envVariables = prepareEnvironmentVariables( + defaultMariadbEnv, + mariadb.project.env, + ); const volumesMount = generateVolumeMounts(mounts); const bindsMount = generateBindMounts(mounts); const filesMount = generateFileMounts(appName, mariadb); diff --git a/packages/server/src/utils/databases/mongo.ts b/packages/server/src/utils/databases/mongo.ts index 352f7810e..c1b0542df 100644 --- a/packages/server/src/utils/databases/mongo.ts +++ b/packages/server/src/utils/databases/mongo.ts @@ -9,7 +9,10 @@ import { } from "../docker/utils"; import { getRemoteDocker } from "../servers/remote-docker"; -export type MongoNested = InferResultType<"mongo", { mounts: true }>; +export type MongoNested = InferResultType< + "mongo", + { mounts: true; project: true } +>; export const buildMongo = async (mongo: MongoNested) => { const { @@ -36,7 +39,10 @@ export const buildMongo = async (mongo: MongoNested) => { cpuLimit, cpuReservation, }); - const envVariables = prepareEnvironmentVariables(defaultMongoEnv); + const envVariables = prepareEnvironmentVariables( + defaultMongoEnv, + mongo.project.env, + ); const volumesMount = generateVolumeMounts(mounts); const bindsMount = generateBindMounts(mounts); const filesMount = generateFileMounts(appName, mongo); diff --git a/packages/server/src/utils/databases/mysql.ts b/packages/server/src/utils/databases/mysql.ts index af625d20c..5a6911771 100644 --- a/packages/server/src/utils/databases/mysql.ts +++ b/packages/server/src/utils/databases/mysql.ts @@ -9,7 +9,10 @@ import { } from "../docker/utils"; import { getRemoteDocker } from "../servers/remote-docker"; -export type MysqlNested = InferResultType<"mysql", { mounts: true }>; +export type MysqlNested = InferResultType< + "mysql", + { mounts: true; project: true } +>; export const buildMysql = async (mysql: MysqlNested) => { const { @@ -43,7 +46,10 @@ export const buildMysql = async (mysql: MysqlNested) => { cpuLimit, cpuReservation, }); - const envVariables = prepareEnvironmentVariables(defaultMysqlEnv); + const envVariables = prepareEnvironmentVariables( + defaultMysqlEnv, + mysql.project.env, + ); const volumesMount = generateVolumeMounts(mounts); const bindsMount = generateBindMounts(mounts); const filesMount = generateFileMounts(appName, mysql); diff --git a/packages/server/src/utils/databases/postgres.ts b/packages/server/src/utils/databases/postgres.ts index 873ad4a51..a8930a1cd 100644 --- a/packages/server/src/utils/databases/postgres.ts +++ b/packages/server/src/utils/databases/postgres.ts @@ -9,7 +9,10 @@ import { } from "../docker/utils"; import { getRemoteDocker } from "../servers/remote-docker"; -export type PostgresNested = InferResultType<"postgres", { mounts: true }>; +export type PostgresNested = InferResultType< + "postgres", + { mounts: true; project: true } +>; export const buildPostgres = async (postgres: PostgresNested) => { const { appName, @@ -36,7 +39,10 @@ export const buildPostgres = async (postgres: PostgresNested) => { cpuLimit, cpuReservation, }); - const envVariables = prepareEnvironmentVariables(defaultPostgresEnv); + const envVariables = prepareEnvironmentVariables( + defaultPostgresEnv, + postgres.project.env, + ); const volumesMount = generateVolumeMounts(mounts); const bindsMount = generateBindMounts(mounts); const filesMount = generateFileMounts(appName, postgres); diff --git a/packages/server/src/utils/databases/redis.ts b/packages/server/src/utils/databases/redis.ts index fc07bc01a..724069a17 100644 --- a/packages/server/src/utils/databases/redis.ts +++ b/packages/server/src/utils/databases/redis.ts @@ -9,7 +9,10 @@ import { } from "../docker/utils"; import { getRemoteDocker } from "../servers/remote-docker"; -export type RedisNested = InferResultType<"redis", { mounts: true }>; +export type RedisNested = InferResultType< + "redis", + { mounts: true; project: true } +>; export const buildRedis = async (redis: RedisNested) => { const { appName, @@ -34,7 +37,10 @@ export const buildRedis = async (redis: RedisNested) => { cpuLimit, cpuReservation, }); - const envVariables = prepareEnvironmentVariables(defaultRedisEnv); + const envVariables = prepareEnvironmentVariables( + defaultRedisEnv, + redis.project.env, + ); const volumesMount = generateVolumeMounts(mounts); const bindsMount = generateBindMounts(mounts); const filesMount = generateFileMounts(appName, redis); diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index 60793d158..afd686ef6 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -258,8 +258,28 @@ export const removeService = async ( } }; -export const prepareEnvironmentVariables = (env: string | null) => - Object.entries(parse(env ?? "")).map(([key, value]) => `${key}=${value}`); +export const prepareEnvironmentVariables = ( + serviceEnv: string | null, + projectEnv?: string | null, +) => { + const projectVars = parse(projectEnv ?? ""); + const serviceVars = parse(serviceEnv ?? ""); + + const resolvedVars = Object.entries(serviceVars).map(([key, value]) => { + let resolvedValue = value; + if (projectVars) { + resolvedValue = value.replace(/\$\{\{shared\.(.*?)\}\}/g, (_, ref) => { + if (projectVars[ref] !== undefined) { + return projectVars[ref]; + } + throw new Error(`Invalid shared environment variable: shared.${ref}`); + }); + } + return `${key}=${resolvedValue}`; + }); + + return resolvedVars; +}; export const prepareBuildArgs = (input: string | null) => { const pairs = (input ?? "").split("\n"); From 2f175f0e4416e88d8de72b68bde808d722227aa4 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:18:48 -0600 Subject: [PATCH 124/243] refactor: add missing types --- apps/dokploy/__test__/drop/drop.test.test.ts | 8 ++++++++ apps/dokploy/__test__/traefik/traefik.test.ts | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/apps/dokploy/__test__/drop/drop.test.test.ts b/apps/dokploy/__test__/drop/drop.test.test.ts index c906c1444..3cfef1170 100644 --- a/apps/dokploy/__test__/drop/drop.test.test.ts +++ b/apps/dokploy/__test__/drop/drop.test.test.ts @@ -32,6 +32,14 @@ const baseApp: ApplicationNested = { serverId: "", branch: null, dockerBuildStage: "", + project: { + env: "", + adminId: "", + name: "", + description: "", + createdAt: "", + projectId: "", + }, buildArgs: null, buildPath: "/", gitlabPathNamespace: "", diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index 637d12ff9..aa0749fe7 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -13,6 +13,14 @@ const baseApp: ApplicationNested = { branch: null, dockerBuildStage: "", buildArgs: null, + project: { + env: "", + adminId: "", + name: "", + description: "", + createdAt: "", + projectId: "", + }, buildPath: "/", gitlabPathNamespace: "", buildType: "nixpacks", From 2307346ae35361e26f4adc51696e68781ece7426 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 17 Nov 2024 18:23:27 -0600 Subject: [PATCH 125/243] refactor: add validation to prevent run on cloud --- apps/dokploy/server/api/routers/admin.ts | 6 ++++++ apps/dokploy/server/api/routers/server.ts | 3 +++ 2 files changed, 9 insertions(+) diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts index d910f2230..6029c7136 100644 --- a/apps/dokploy/server/api/routers/admin.ts +++ b/apps/dokploy/server/api/routers/admin.ts @@ -31,6 +31,12 @@ export const adminRouter = createTRPCRouter({ update: adminProcedure .input(apiUpdateAdmin) .mutation(async ({ input, ctx }) => { + if (ctx.user.rol === "user") { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to update this admin", + }); + } const { authId } = await findAdminById(ctx.user.adminId); return updateAdmin(authId, input); }), diff --git a/apps/dokploy/server/api/routers/server.ts b/apps/dokploy/server/api/routers/server.ts index 97746131c..0d4ef87f3 100644 --- a/apps/dokploy/server/api/routers/server.ts +++ b/apps/dokploy/server/api/routers/server.ts @@ -183,6 +183,9 @@ export const serverRouter = createTRPCRouter({ } }), publicIp: protectedProcedure.query(async ({ ctx }) => { + if (IS_CLOUD) { + return ""; + } const ip = await getPublicIpWithFallback(); return ip; }), From ce2dce340136cd3f238a106695edb3631d79ea64 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 17 Nov 2024 18:33:14 -0600 Subject: [PATCH 126/243] refactor: update shared to project --- apps/dokploy/__test__/env/shared.test.ts | 48 +++++++++++------------ packages/server/src/utils/docker/utils.ts | 4 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/apps/dokploy/__test__/env/shared.test.ts b/apps/dokploy/__test__/env/shared.test.ts index 06e8825fe..4a8448aa9 100644 --- a/apps/dokploy/__test__/env/shared.test.ts +++ b/apps/dokploy/__test__/env/shared.test.ts @@ -7,13 +7,13 @@ DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db PORT=3000 `; const serviceEnv = ` -ENVIRONMENT=\${{shared.ENVIRONMENT}} -DATABASE_URL=\${{shared.DATABASE_URL}} +ENVIRONMENT=\${{project.ENVIRONMENT}} +DATABASE_URL=\${{project.DATABASE_URL}} SERVICE_PORT=4000 `; describe("prepareEnvironmentVariables", () => { - it("resolves shared variables correctly", () => { + it("resolves project variables correctly", () => { const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv); expect(resolved).toEqual([ @@ -23,24 +23,24 @@ describe("prepareEnvironmentVariables", () => { ]); }); - it("handles undefined shared variables", () => { + it("handles undefined project variables", () => { const incompleteProjectEnv = ` NODE_ENV=production `; const invalidServiceEnv = ` - UNDEFINED_VAR=\${{shared.UNDEFINED_VAR}} + UNDEFINED_VAR=\${{project.UNDEFINED_VAR}} `; expect( () => prepareEnvironmentVariables(invalidServiceEnv, incompleteProjectEnv), // Cambiado el orden - ).toThrow("Invalid shared environment variable: shared.UNDEFINED_VAR"); + ).toThrow("Invalid project environment variable: project.UNDEFINED_VAR"); }); - it("allows service-specific variables to override shared variables", () => { + it("allows service-specific variables to override project variables", () => { const serviceSpecificEnv = ` ENVIRONMENT=production - DATABASE_URL=\${{shared.DATABASE_URL}} + DATABASE_URL=\${{project.DATABASE_URL}} `; const resolved = prepareEnvironmentVariables( @@ -49,7 +49,7 @@ describe("prepareEnvironmentVariables", () => { ); expect(resolved).toEqual([ - "ENVIRONMENT=production", // Overrides shared variable + "ENVIRONMENT=production", // Overrides project variable "DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db", ]); }); @@ -61,7 +61,7 @@ API_VERSION=v1 PORT=8000 `; const serviceEnv = ` -API_ENDPOINT=\${{shared.BASE_URL}}/\${{shared.API_VERSION}}/endpoint +API_ENDPOINT=\${{project.BASE_URL}}/\${{project.API_VERSION}}/endpoint SERVICE_PORT=9000 `; const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv); @@ -72,27 +72,27 @@ SERVICE_PORT=9000 ]); }); - it("handles missing shared variables gracefully", () => { + it("handles missing project variables gracefully", () => { const projectEnv = ` PORT=8080 `; const serviceEnv = ` -MISSING_VAR=\${{shared.MISSING_KEY}} +MISSING_VAR=\${{project.MISSING_KEY}} SERVICE_PORT=3000 `; expect(() => prepareEnvironmentVariables(serviceEnv, projectEnv)).toThrow( - "Invalid shared environment variable: shared.MISSING_KEY", + "Invalid project environment variable: project.MISSING_KEY", ); }); - it("overrides shared variables with service-specific values", () => { + it("overrides project variables with service-specific values", () => { const projectEnv = ` ENVIRONMENT=staging DATABASE_URL=postgres://project:project@localhost:5432/project_db `; const serviceEnv = ` -ENVIRONMENT=\${{shared.ENVIRONMENT}} +ENVIRONMENT=\${{project.ENVIRONMENT}} DATABASE_URL=postgres://service:service@localhost:5432/service_db SERVICE_NAME=my-service `; @@ -105,15 +105,15 @@ SERVICE_NAME=my-service ]); }); - it("handles shared variables with normal and unusual characters", () => { + it("handles project variables with normal and unusual characters", () => { const projectEnv = ` ENVIRONMENT=PRODUCTION `; // Needs to be in quotes const serviceEnv = ` -NODE_ENV=\${{shared.ENVIRONMENT}} -SPECIAL_VAR="$^@$^@#$^@!#$@#$-\${{shared.ENVIRONMENT}}" +NODE_ENV=\${{project.ENVIRONMENT}} +SPECIAL_VAR="$^@$^@#$^@!#$@#$-\${{project.ENVIRONMENT}}" `; const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv); @@ -131,8 +131,8 @@ APP_NAME=MyApp `; const serviceEnv = ` -NODE_ENV=\${{shared.ENVIRONMENT}} -COMPLEX_VAR="Prefix-$#^!@-\${{shared.ENVIRONMENT}}--\${{shared.APP_NAME}} Suffix " +NODE_ENV=\${{project.ENVIRONMENT}} +COMPLEX_VAR="Prefix-$#^!@-\${{project.ENVIRONMENT}}--\${{project.APP_NAME}} Suffix " `; const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv); @@ -149,8 +149,8 @@ COMPLEX_VAR="Prefix-$#^!@-\${{shared.ENVIRONMENT}}--\${{shared.APP_NAME}} Suffix `; const serviceEnv = ` - NODE_ENV='\${{shared.ENVIRONMENT}}' - COMPLEX_VAR='Prefix-$#^!@-\${{shared.ENVIRONMENT}}--\${{shared.APP_NAME}} Suffix' + NODE_ENV='\${{project.ENVIRONMENT}}' + COMPLEX_VAR='Prefix-$#^!@-\${{project.ENVIRONMENT}}--\${{project.APP_NAME}} Suffix' `; const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv); @@ -166,8 +166,8 @@ ENVIRONMENT=PRODUCTION APP_NAME=MyApp `; const serviceEnv = ` -NODE_ENV="'\${{shared.ENVIRONMENT}}'" -COMPLEX_VAR="'Prefix \"DoubleQuoted\" and \${{shared.APP_NAME}}'" +NODE_ENV="'\${{project.ENVIRONMENT}}'" +COMPLEX_VAR="'Prefix \"DoubleQuoted\" and \${{project.APP_NAME}}'" `; const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv); diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index afd686ef6..31e43b6d3 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -268,11 +268,11 @@ export const prepareEnvironmentVariables = ( const resolvedVars = Object.entries(serviceVars).map(([key, value]) => { let resolvedValue = value; if (projectVars) { - resolvedValue = value.replace(/\$\{\{shared\.(.*?)\}\}/g, (_, ref) => { + resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => { if (projectVars[ref] !== undefined) { return projectVars[ref]; } - throw new Error(`Invalid shared environment variable: shared.${ref}`); + throw new Error(`Invalid project environment variable: project.${ref}`); }); } return `${key}=${resolvedValue}`; From ddbb414225f77f0439018cd4930bfc778aaa7cbe Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 17 Nov 2024 18:53:52 -0600 Subject: [PATCH 127/243] chore(version): bump version --- apps/dokploy/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index fc695598a..561b881f5 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -1,6 +1,6 @@ { "name": "dokploy", - "version": "v0.11.2", + "version": "v0.12.0", "private": true, "license": "Apache-2.0", "type": "module", From a6e7edd4d9dee750892d6e02877b02fcb41a6929 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 17 Nov 2024 21:39:02 -0600 Subject: [PATCH 128/243] refactor: update share to project --- apps/dokploy/components/dashboard/projects/add-env.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/components/dashboard/projects/add-env.tsx b/apps/dokploy/components/dashboard/projects/add-env.tsx index 5c53848bc..79870ebbc 100644 --- a/apps/dokploy/components/dashboard/projects/add-env.tsx +++ b/apps/dokploy/components/dashboard/projects/add-env.tsx @@ -106,10 +106,10 @@ export const AddEnv = ({ projectId }: Props) => { want to use it in your service, you need to use like this:
  • - ENVIRONMENT=${"${{shared.ENVIRONMENT}}"} + ENVIRONMENT=${"{{project.ENVIRONMENT}}"}
  • - DATABASE_URL=${"${{shared.DATABASE_URL}}"} + DATABASE_URL=${"{{project.DATABASE_URL}}"}
{" "} This allows the service to inherit and use the shared variables from From da005bc511cc631ef878ce976319ce54f3b7ba9e Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 17 Nov 2024 22:25:03 -0600 Subject: [PATCH 129/243] chore: add startupfa.me sponsor --- .github/sponsors/startupfame.png | Bin 0 -> 13206 bytes README.md | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .github/sponsors/startupfame.png diff --git a/.github/sponsors/startupfame.png b/.github/sponsors/startupfame.png new file mode 100644 index 0000000000000000000000000000000000000000..da53b40b645ca67ba9f42b3676351097778fe1c5 GIT binary patch literal 13206 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 Lightspeed.run -Lightspeed.run +Cloudblast.io +Startupfame ### Community Backers 🤝 From 1c5fe8a2836d82ab21a9e1c68c63c527d60282da Mon Sep 17 00:00:00 2001 From: JiPai Date: Mon, 18 Nov 2024 14:09:42 +0800 Subject: [PATCH 130/243] feat(Profile): support use Gravatar as avatar --- .../settings/profile/profile-form.tsx | 19 +++++++++++++++++-- apps/dokploy/lib/utils.ts | 8 ++++++++ .../pages/dashboard/settings/appearance.tsx | 1 - 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx index e90eb5e56..1d4daa533 100644 --- a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx @@ -16,10 +16,11 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { generateSHA256Hash } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { useTranslation } from "next-i18next"; -import { useEffect } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -53,6 +54,14 @@ export const ProfileForm = () => { const { data, refetch } = api.auth.get.useQuery(); const { mutateAsync, isLoading } = api.auth.update.useMutation(); const { t } = useTranslation("settings"); + const [gravatarHash, setGravatarHash] = useState(null); + + const availableAvatars = useMemo(() => { + if (gravatarHash === null) return randomImages; + return randomImages.concat([ + `https://www.gravatar.com/avatar/${gravatarHash}`, + ]); + }, [gravatarHash]); const form = useForm({ defaultValues: { @@ -70,6 +79,12 @@ export const ProfileForm = () => { password: "", image: data?.image || "", }); + + if (data.email) { + generateSHA256Hash(data.email).then((hash) => { + setGravatarHash(hash); + }); + } } form.reset(); }, [form, form.reset, data]); @@ -154,7 +169,7 @@ export const ProfileForm = () => { value={field.value} className="flex flex-row flex-wrap gap-2 max-xl:justify-center" > - {randomImages.map((image) => ( + {availableAvatars.map((image) => ( diff --git a/apps/dokploy/lib/utils.ts b/apps/dokploy/lib/utils.ts index ac680b303..c83f5e22f 100644 --- a/apps/dokploy/lib/utils.ts +++ b/apps/dokploy/lib/utils.ts @@ -4,3 +4,11 @@ import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + +export async function generateSHA256Hash(text: string) { + const encoder = new TextEncoder(); + const data = encoder.encode(text); + const hashBuffer = await crypto.subtle.digest("SHA-256", data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); +} diff --git a/apps/dokploy/pages/dashboard/settings/appearance.tsx b/apps/dokploy/pages/dashboard/settings/appearance.tsx index 209d938c6..f7f497460 100644 --- a/apps/dokploy/pages/dashboard/settings/appearance.tsx +++ b/apps/dokploy/pages/dashboard/settings/appearance.tsx @@ -8,7 +8,6 @@ import { createServerSideHelpers } from "@trpc/react-query/server"; import type { GetServerSidePropsContext } from "next"; import React, { type ReactElement } from "react"; import superjson from "superjson"; -import nextI18NextConfig from "../../../next-i18next.config.cjs"; const Page = () => { return ( From 28f2c1a3c07ab9511be57fb6c110e973b7cf1779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20T=C3=B8n=20L=C3=B8vhaug?= Date: Mon, 18 Nov 2024 09:03:48 +0100 Subject: [PATCH 131/243] fix: template pointing to wrong TLD --- apps/dokploy/templates/templates.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 40f5e736c..d0b29fc66 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -771,8 +771,8 @@ export const templates: TemplateData[] = [ logo: "postiz.png", links: { github: "https://github.com/gitroomhq/postiz", - website: "https://postiz.io", - docs: "https://docs.postiz.io", + website: "https://postiz.com", + docs: "https://docs.postiz.com", }, tags: ["cms", "content-management", "publishing"], load: () => import("./postiz/index").then((m) => m.generate), From cda66606ec2cca2b8fc028046f58cfb2dde16ab7 Mon Sep 17 00:00:00 2001 From: WoWnik Date: Mon, 18 Nov 2024 11:47:41 +0300 Subject: [PATCH 132/243] feat: add russian translation init --- .../dashboard/settings/appearance-form.tsx | 3 +- apps/dokploy/next-i18next.config.cjs | 2 +- apps/dokploy/pages/_app.tsx | 2 +- apps/dokploy/public/locales/ru/common.json | 1 + apps/dokploy/public/locales/ru/settings.json | 44 +++++++++++++++++++ apps/dokploy/utils/hooks/use-locale.ts | 2 +- 6 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 apps/dokploy/public/locales/ru/common.json create mode 100644 apps/dokploy/public/locales/ru/settings.json diff --git a/apps/dokploy/components/dashboard/settings/appearance-form.tsx b/apps/dokploy/components/dashboard/settings/appearance-form.tsx index 9bafbedab..5d764cacd 100644 --- a/apps/dokploy/components/dashboard/settings/appearance-form.tsx +++ b/apps/dokploy/components/dashboard/settings/appearance-form.tsx @@ -37,7 +37,7 @@ const appearanceFormSchema = z.object({ theme: z.enum(["light", "dark", "system"], { required_error: "Please select a theme.", }), - language: z.enum(["en", "pl", "zh-Hans"], { + language: z.enum(["en", "pl", "zh-Hans", "ru"], { required_error: "Please select a language.", }), }); @@ -176,6 +176,7 @@ export function AppearanceForm() { { label: "English", value: "en" }, { label: "Polski", value: "pl" }, { label: "简体中文", value: "zh-Hans" }, + { label: "Русский", value: "ru" }, ].map((preset) => ( {preset.label} diff --git a/apps/dokploy/next-i18next.config.cjs b/apps/dokploy/next-i18next.config.cjs index bac301cb4..7a1e5b01a 100644 --- a/apps/dokploy/next-i18next.config.cjs +++ b/apps/dokploy/next-i18next.config.cjs @@ -2,7 +2,7 @@ module.exports = { i18n: { defaultLocale: "en", - locales: ["en", "pl", "zh-Hans"], + locales: ["en", "pl", "zh-Hans", "ru"], localeDetection: false, }, fallbackLng: "en", diff --git a/apps/dokploy/pages/_app.tsx b/apps/dokploy/pages/_app.tsx index 18cb3e7e6..b74f24aab 100644 --- a/apps/dokploy/pages/_app.tsx +++ b/apps/dokploy/pages/_app.tsx @@ -71,7 +71,7 @@ export default api.withTRPC( { i18n: { defaultLocale: "en", - locales: ["en", "pl", "zh-Hans"], + locales: ["en", "pl", "zh-Hans", "ru"], localeDetection: false, }, fallbackLng: "en", diff --git a/apps/dokploy/public/locales/ru/common.json b/apps/dokploy/public/locales/ru/common.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/apps/dokploy/public/locales/ru/common.json @@ -0,0 +1 @@ +{} diff --git a/apps/dokploy/public/locales/ru/settings.json b/apps/dokploy/public/locales/ru/settings.json new file mode 100644 index 000000000..55620c670 --- /dev/null +++ b/apps/dokploy/public/locales/ru/settings.json @@ -0,0 +1,44 @@ +{ + "settings.common.save": "Сохранить", + "settings.server.domain.title": "Домен сервера", + "settings.server.domain.description": "Установите домен для вашего серверного приложения Dokploy.", + "settings.server.domain.form.domain": "Домен", + "settings.server.domain.form.letsEncryptEmail": "Email для Let's Encrypt", + "settings.server.domain.form.certificate.label": "Сертификат", + "settings.server.domain.form.certificate.placeholder": "Выберите сертификат", + "settings.server.domain.form.certificateOptions.none": "Нет", + "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (По умолчанию)", + + "settings.server.webServer.title": "Веб-сервер", + "settings.server.webServer.description": "Перезагрузка или очистка веб-сервера.", + "settings.server.webServer.server.label": "Сервер", + "settings.server.webServer.traefik.label": "Traefik", + "settings.server.webServer.storage.label": "Дисковое пространство", + "settings.server.webServer.actions": "Действия", + "settings.server.webServer.reload": "Перезагрузить", + "settings.server.webServer.watchLogs": "Просмотр логов", + "settings.server.webServer.updateServerIp": "Изменить IP адрес", + "settings.server.webServer.traefik.modifyEnv": "Изменить переменные окружения", + "settings.server.webServer.storage.cleanUnusedImages": "Очистить неиспользуемые образы", + "settings.server.webServer.storage.cleanUnusedVolumes": "Очистить неиспользуемые тома", + "settings.server.webServer.storage.cleanStoppedContainers": "Очистить остановленные контейнеры", + "settings.server.webServer.storage.cleanDockerBuilder": "Очистить Docker Builder и систему", + "settings.server.webServer.storage.cleanMonitoring": "Очистить мониторинг", + "settings.server.webServer.storage.cleanAll": "Очистить всё", + + "settings.profile.title": "Аккаунт", + "settings.profile.description": "Измените данные вашего профиля.", + "settings.profile.email": "Email", + "settings.profile.password": "Пароль", + "settings.profile.avatar": "Аватар", + + "settings.appearance.title": "Внешний вид", + "settings.appearance.description": "Настройте тему Dokploy.", + "settings.appearance.theme": "Тема", + "settings.appearance.themeDescription": "Выберите тему системной панели", + "settings.appearance.themes.light": "Светлая", + "settings.appearance.themes.dark": "Тёмная", + "settings.appearance.themes.system": "Системная", + "settings.appearance.language": "Язык", + "settings.appearance.languageDescription": "Select a language for your dashboard" +} diff --git a/apps/dokploy/utils/hooks/use-locale.ts b/apps/dokploy/utils/hooks/use-locale.ts index caad152a1..52b5fbf4b 100644 --- a/apps/dokploy/utils/hooks/use-locale.ts +++ b/apps/dokploy/utils/hooks/use-locale.ts @@ -1,6 +1,6 @@ import Cookies from "js-cookie"; -const SUPPORTED_LOCALES = ["en", "pl", "zh-Hans"] as const; +const SUPPORTED_LOCALES = ["en", "pl", "zh-Hans", "ru"] as const; type Locale = (typeof SUPPORTED_LOCALES)[number]; From adde8126abc883c1652a2c79aa8998f2dee85ec4 Mon Sep 17 00:00:00 2001 From: mufeng Date: Mon, 18 Nov 2024 15:07:10 +0800 Subject: [PATCH 133/243] feat: add HeyForm template --- apps/dokploy/public/templates/heyform.svg | 5 ++ .../templates/heyform/docker-compose.yml | 48 +++++++++++++++++++ apps/dokploy/templates/heyform/index.ts | 32 +++++++++++++ apps/dokploy/templates/templates.ts | 15 ++++++ 4 files changed, 100 insertions(+) create mode 100644 apps/dokploy/public/templates/heyform.svg create mode 100644 apps/dokploy/templates/heyform/docker-compose.yml create mode 100644 apps/dokploy/templates/heyform/index.ts diff --git a/apps/dokploy/public/templates/heyform.svg b/apps/dokploy/public/templates/heyform.svg new file mode 100644 index 000000000..e1e34e615 --- /dev/null +++ b/apps/dokploy/public/templates/heyform.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/dokploy/templates/heyform/docker-compose.yml b/apps/dokploy/templates/heyform/docker-compose.yml new file mode 100644 index 000000000..b71207371 --- /dev/null +++ b/apps/dokploy/templates/heyform/docker-compose.yml @@ -0,0 +1,48 @@ +services: + heyform: + image: heyform/community-edition:latest + restart: always + volumes: + # Persist uploaded images + - heyform-data:/app/static/upload + depends_on: + - mongo + - redis + ports: + - '9513:8000' + env_file: + - .env + environment: + MONGO_URI: 'mongodb://mongo:27017/heyform' + REDIS_HOST: redis + REDIS_PORT: 6379 + networks: + - heyform-network + + mongo: + image: percona/percona-server-mongodb:4.4 + restart: always + networks: + - heyform-network + volumes: + # Persist MongoDB data + - mongo-data:/data/db + + redis: + image: redis + restart: always + command: "redis-server --appendonly yes" + networks: + - heyform-network + volumes: + # Persist KeyDB data + - redis-data:/data + +networks: + heyform-network: + driver: bridge + +volumes: + heyform-data: + mongo-data: + redis-data: diff --git a/apps/dokploy/templates/heyform/index.ts b/apps/dokploy/templates/heyform/index.ts new file mode 100644 index 000000000..5e1a86be7 --- /dev/null +++ b/apps/dokploy/templates/heyform/index.ts @@ -0,0 +1,32 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateBase64, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const mainDomain = generateRandomDomain(schema); + const sessionKey = generateBase64(64); + const formEncryptionKey = generateBase64(64); + + const domains: DomainSchema[] = [ + { + host: mainDomain, + port: 9513, + serviceName: "heyform", + }, + ]; + + const envs = [ + `APP_HOMEPAGE_URL=http://${mainDomain}`, + `SESSION_KEY=${sessionKey}`, + `FORM_ENCRYPTION_KEY=${formEncryptionKey}`, + ]; + + return { + envs, + domains, + }; +} diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 40f5e736c..26f225f05 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -837,4 +837,19 @@ export const templates: TemplateData[] = [ tags: ["3d", "rendering", "animation"], load: () => import("./blender/index").then((m) => m.generate), }, + { + id: "heyform", + name: "HeyForm", + version: "latest", + description: + "Allows anyone to create engaging conversational forms for surveys, questionnaires, quizzes, and polls. No coding skills required.", + logo: "heyform.svg", + links: { + github: "https://github.com/heyform/heyform", + website: "https://heyform.net", + docs: "https://docs.heyform.net", + }, + tags: ["form", "builder", "questionnaire", "quiz", "survey"], + load: () => import("./heyform/index").then((m) => m.generate), + }, ]; From 6fc1ce2fbc7e47fd6ccb01c37e430c215f8c5986 Mon Sep 17 00:00:00 2001 From: JiPai Date: Mon, 18 Nov 2024 22:25:56 +0800 Subject: [PATCH 134/243] chore(README): fix broken video thumbnail --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7886f42ac..ff25f7d0d 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com). ## Video Tutorial - Watch the video + Watch the video statement-breakpoint +CREATE TABLE IF NOT EXISTS "preview_deployments" ( + "previewDeploymentId" text PRIMARY KEY NOT NULL, + "branch" text NOT NULL, + "pullRequestId" text NOT NULL, + "pullRequestNumber" text NOT NULL, + "pullRequestURL" text NOT NULL, + "pullRequestTitle" text NOT NULL, + "pullRequestCommentId" text NOT NULL, + "appName" text NOT NULL, + "applicationId" text NOT NULL, + "domainId" text, + "deploymentId" text, + "createdAt" text NOT NULL, + "expiresAt" text, + CONSTRAINT "preview_deployments_appName_unique" UNIQUE("appName") +); +--> statement-breakpoint +ALTER TABLE "application" ADD COLUMN "previewEnv" text;--> statement-breakpoint +ALTER TABLE "application" ADD COLUMN "previewBuildArgs" text;--> statement-breakpoint +ALTER TABLE "application" ADD COLUMN "previewWildcard" text;--> statement-breakpoint +ALTER TABLE "application" ADD COLUMN "previewPort" integer DEFAULT 3000;--> statement-breakpoint +ALTER TABLE "application" ADD COLUMN "previewHttps" boolean DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE "application" ADD COLUMN "previewPath" text DEFAULT '/';--> statement-breakpoint +ALTER TABLE "application" ADD COLUMN "certificateType" "certificateType" DEFAULT 'none' NOT NULL;--> statement-breakpoint +ALTER TABLE "application" ADD COLUMN "previewLimit" integer DEFAULT 3;--> statement-breakpoint +ALTER TABLE "application" ADD COLUMN "isPreviewDeploymentsActive" boolean DEFAULT false;--> statement-breakpoint +ALTER TABLE "domain" ADD COLUMN "isPreviewDeployment" boolean DEFAULT false;--> statement-breakpoint +ALTER TABLE "domain" ADD COLUMN "previewDeploymentId" text;--> statement-breakpoint +ALTER TABLE "deployment" ADD COLUMN "isPreviewDeployment" boolean DEFAULT false;--> statement-breakpoint +ALTER TABLE "deployment" ADD COLUMN "previewDeploymentId" text;--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "preview_deployments" ADD CONSTRAINT "preview_deployments_applicationId_application_applicationId_fk" FOREIGN KEY ("applicationId") REFERENCES "public"."application"("applicationId") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "preview_deployments" ADD CONSTRAINT "preview_deployments_domainId_domain_domainId_fk" FOREIGN KEY ("domainId") REFERENCES "public"."domain"("domainId") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "preview_deployments" ADD CONSTRAINT "preview_deployments_deploymentId_deployment_deploymentId_fk" FOREIGN KEY ("deploymentId") REFERENCES "public"."deployment"("deploymentId") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "domain" ADD CONSTRAINT "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk" FOREIGN KEY ("previewDeploymentId") REFERENCES "public"."preview_deployments"("previewDeploymentId") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "deployment" ADD CONSTRAINT "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk" FOREIGN KEY ("previewDeploymentId") REFERENCES "public"."preview_deployments"("previewDeploymentId") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/apps/dokploy/drizzle/0048_clumsy_matthew_murdock.sql b/apps/dokploy/drizzle/0048_clumsy_matthew_murdock.sql new file mode 100644 index 000000000..ef939a029 --- /dev/null +++ b/apps/dokploy/drizzle/0048_clumsy_matthew_murdock.sql @@ -0,0 +1 @@ +ALTER TABLE "preview_deployments" ADD COLUMN "previewStatus" "applicationStatus" DEFAULT 'idle' NOT NULL; \ No newline at end of file diff --git a/apps/dokploy/drizzle/0049_useful_mole_man.sql b/apps/dokploy/drizzle/0049_useful_mole_man.sql new file mode 100644 index 000000000..c672e02f3 --- /dev/null +++ b/apps/dokploy/drizzle/0049_useful_mole_man.sql @@ -0,0 +1,3 @@ +ALTER TABLE "preview_deployments" DROP CONSTRAINT "preview_deployments_deploymentId_deployment_deploymentId_fk"; +--> statement-breakpoint +ALTER TABLE "preview_deployments" DROP COLUMN IF EXISTS "deploymentId"; \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0047_snapshot.json b/apps/dokploy/drizzle/meta/0047_snapshot.json new file mode 100644 index 000000000..34574a694 --- /dev/null +++ b/apps/dokploy/drizzle/meta/0047_snapshot.json @@ -0,0 +1,4237 @@ +{ + "id": "053ad983-6299-4420-a551-645490689733", + "prevId": "d70bcec5-e7af-4872-b2eb-f0a22ae2e3e8", + "version": "6", + "dialect": "postgresql", + "tables": { + "public.application": { + "name": "application", + "schema": "", + "columns": { + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewEnv": { + "name": "previewEnv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewBuildArgs": { + "name": "previewBuildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewWildcard": { + "name": "previewWildcard", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewPort": { + "name": "previewPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "previewHttps": { + "name": "previewHttps", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "previewPath": { + "name": "previewPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "previewLimit": { + "name": "previewLimit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "isPreviewDeploymentsActive": { + "name": "isPreviewDeploymentsActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildPath": { + "name": "buildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBuildPath": { + "name": "gitlabBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBuildPath": { + "name": "bitbucketBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBuildPath": { + "name": "customGitBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerfile": { + "name": "dockerfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerContextPath": { + "name": "dockerContextPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerBuildStage": { + "name": "dockerBuildStage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropBuildPath": { + "name": "dropBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "buildType": { + "name": "buildType", + "type": "buildType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'nixpacks'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "application", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_registryId_registry_registryId_fk": { + "name": "application_registryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "registryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_projectId_project_projectId_fk": { + "name": "application_projectId_project_projectId_fk", + "tableFrom": "application", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_githubId_github_githubId_fk": { + "name": "application_githubId_github_githubId_fk", + "tableFrom": "application", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_gitlabId_gitlab_gitlabId_fk": { + "name": "application_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "application", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "application_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "application", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_serverId_server_serverId_fk": { + "name": "application_serverId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.postgres": { + "name": "postgres", + "schema": "", + "columns": { + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_projectId_project_projectId_fk": { + "name": "postgres_projectId_project_projectId_fk", + "tableFrom": "postgres", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "postgres_serverId_server_serverId_fk": { + "name": "postgres_serverId_server_serverId_fk", + "tableFrom": "postgres", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "postgres_appName_unique": { + "name": "postgres_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isRegistered": { + "name": "isRegistered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expirationDate": { + "name": "expirationDate", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canCreateProjects": { + "name": "canCreateProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToSSHKeys": { + "name": "canAccessToSSHKeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateServices": { + "name": "canCreateServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteProjects": { + "name": "canDeleteProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteServices": { + "name": "canDeleteServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToDocker": { + "name": "canAccessToDocker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToAPI": { + "name": "canAccessToAPI", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToGitProviders": { + "name": "canAccessToGitProviders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToTraefikFiles": { + "name": "canAccessToTraefikFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accesedServices": { + "name": "accesedServices", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authId": { + "name": "authId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_adminId_admin_adminId_fk": { + "name": "user_adminId_admin_adminId_fk", + "tableFrom": "user", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_authId_auth_id_fk": { + "name": "user_authId_auth_id_fk", + "tableFrom": "user", + "tableTo": "auth", + "columnsFrom": [ + "authId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.admin": { + "name": "admin", + "schema": "", + "columns": { + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverIp": { + "name": "serverIp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "letsEncryptEmail": { + "name": "letsEncryptEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sshPrivateKey": { + "name": "sshPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enableLogRotation": { + "name": "enableLogRotation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "authId": { + "name": "authId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serversQuantity": { + "name": "serversQuantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "admin_authId_auth_id_fk": { + "name": "admin_authId_auth_id_fk", + "tableFrom": "admin", + "tableTo": "auth", + "columnsFrom": [ + "authId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.auth": { + "name": "auth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rol": { + "name": "rol", + "type": "Roles", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is2FAEnabled": { + "name": "is2FAEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resetPasswordToken": { + "name": "resetPasswordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resetPasswordExpiresAt": { + "name": "resetPasswordExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationToken": { + "name": "confirmationToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationExpiresAt": { + "name": "confirmationExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_email_unique": { + "name": "auth_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "project_adminId_admin_adminId_fk": { + "name": "project_adminId_admin_adminId_fk", + "tableFrom": "project", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.domain": { + "name": "domain", + "schema": "", + "columns": { + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domainType": { + "name": "domainType", + "type": "domainType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'application'" + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + } + }, + "indexes": {}, + "foreignKeys": { + "domain_composeId_compose_composeId_fk": { + "name": "domain_composeId_compose_composeId_fk", + "tableFrom": "domain", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_applicationId_application_applicationId_fk": { + "name": "domain_applicationId_application_applicationId_fk", + "tableFrom": "domain", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "domain", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.mariadb": { + "name": "mariadb", + "schema": "", + "columns": { + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_projectId_project_projectId_fk": { + "name": "mariadb_projectId_project_projectId_fk", + "tableFrom": "mariadb", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mariadb_serverId_server_serverId_fk": { + "name": "mariadb_serverId_server_serverId_fk", + "tableFrom": "mariadb", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mariadb_appName_unique": { + "name": "mariadb_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.mongo": { + "name": "mongo", + "schema": "", + "columns": { + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mongo_projectId_project_projectId_fk": { + "name": "mongo_projectId_project_projectId_fk", + "tableFrom": "mongo", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mongo_serverId_server_serverId_fk": { + "name": "mongo_serverId_server_serverId_fk", + "tableFrom": "mongo", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mongo_appName_unique": { + "name": "mongo_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.mysql": { + "name": "mysql", + "schema": "", + "columns": { + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_projectId_project_projectId_fk": { + "name": "mysql_projectId_project_projectId_fk", + "tableFrom": "mysql", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mysql_serverId_server_serverId_fk": { + "name": "mysql_serverId_server_serverId_fk", + "tableFrom": "mysql", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mysql_appName_unique": { + "name": "mysql_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.backup": { + "name": "backup", + "schema": "", + "columns": { + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "database": { + "name": "database", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseType": { + "name": "databaseType", + "type": "databaseType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_destinationId_destination_destinationId_fk": { + "name": "backup_destinationId_destination_destinationId_fk", + "tableFrom": "backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_postgresId_postgres_postgresId_fk": { + "name": "backup_postgresId_postgres_postgresId_fk", + "tableFrom": "backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mariadbId_mariadb_mariadbId_fk": { + "name": "backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mysqlId_mysql_mysqlId_fk": { + "name": "backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mongoId_mongo_mongoId_fk": { + "name": "backup_mongoId_mongo_mongoId_fk", + "tableFrom": "backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.destination": { + "name": "destination", + "schema": "", + "columns": { + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessKey": { + "name": "accessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "destination_adminId_admin_adminId_fk": { + "name": "destination_adminId_admin_adminId_fk", + "tableFrom": "destination", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "deploymentStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'running'" + }, + "logPath": { + "name": "logPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "deployment_applicationId_application_applicationId_fk": { + "name": "deployment_applicationId_application_applicationId_fk", + "tableFrom": "deployment", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_composeId_compose_composeId_fk": { + "name": "deployment_composeId_compose_composeId_fk", + "tableFrom": "deployment", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_serverId_server_serverId_fk": { + "name": "deployment_serverId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "deployment", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.mount": { + "name": "mount", + "schema": "", + "columns": { + "mountId": { + "name": "mountId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "mountType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "hostPath": { + "name": "hostPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "mountPath": { + "name": "mountPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mount_applicationId_application_applicationId_fk": { + "name": "mount_applicationId_application_applicationId_fk", + "tableFrom": "mount", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_postgresId_postgres_postgresId_fk": { + "name": "mount_postgresId_postgres_postgresId_fk", + "tableFrom": "mount", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mariadbId_mariadb_mariadbId_fk": { + "name": "mount_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "mount", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mongoId_mongo_mongoId_fk": { + "name": "mount_mongoId_mongo_mongoId_fk", + "tableFrom": "mount", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mysqlId_mysql_mysqlId_fk": { + "name": "mount_mysqlId_mysql_mysqlId_fk", + "tableFrom": "mount", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_redisId_redis_redisId_fk": { + "name": "mount_redisId_redis_redisId_fk", + "tableFrom": "mount", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_composeId_compose_composeId_fk": { + "name": "mount_composeId_compose_composeId_fk", + "tableFrom": "mount", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "certificateId": { + "name": "certificateId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificateData": { + "name": "certificateData", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificatePath": { + "name": "certificatePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autoRenew": { + "name": "autoRenew", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "certificate_adminId_admin_adminId_fk": { + "name": "certificate_adminId_admin_adminId_fk", + "tableFrom": "certificate", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "certificate_serverId_server_serverId_fk": { + "name": "certificate_serverId_server_serverId_fk", + "tableFrom": "certificate", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_certificatePath_unique": { + "name": "certificate_certificatePath_unique", + "nullsNotDistinct": false, + "columns": [ + "certificatePath" + ] + } + } + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_auth_id_fk": { + "name": "session_user_id_auth_id_fk", + "tableFrom": "session", + "tableTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.redirect": { + "name": "redirect", + "schema": "", + "columns": { + "redirectId": { + "name": "redirectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "regex": { + "name": "regex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanent": { + "name": "permanent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "redirect_applicationId_application_applicationId_fk": { + "name": "redirect_applicationId_application_applicationId_fk", + "tableFrom": "redirect", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.security": { + "name": "security", + "schema": "", + "columns": { + "securityId": { + "name": "securityId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "security_applicationId_application_applicationId_fk": { + "name": "security_applicationId_application_applicationId_fk", + "tableFrom": "security", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_username_applicationId_unique": { + "name": "security_username_applicationId_unique", + "nullsNotDistinct": false, + "columns": [ + "username", + "applicationId" + ] + } + } + }, + "public.port": { + "name": "port", + "schema": "", + "columns": { + "portId": { + "name": "portId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "publishedPort": { + "name": "publishedPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "targetPort": { + "name": "targetPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "protocolType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "port_applicationId_application_applicationId_fk": { + "name": "port_applicationId_application_applicationId_fk", + "tableFrom": "port", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.redis": { + "name": "redis", + "schema": "", + "columns": { + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_projectId_project_projectId_fk": { + "name": "redis_projectId_project_projectId_fk", + "tableFrom": "redis", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "redis_serverId_server_serverId_fk": { + "name": "redis_serverId_server_serverId_fk", + "tableFrom": "redis", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "redis_appName_unique": { + "name": "redis_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.compose": { + "name": "compose", + "schema": "", + "columns": { + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeFile": { + "name": "composeFile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceTypeCompose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "composeType": { + "name": "composeType", + "type": "composeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'docker-compose'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "composePath": { + "name": "composePath", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'./docker-compose.yml'" + }, + "suffix": { + "name": "suffix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "randomize": { + "name": "randomize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "composeStatus": { + "name": "composeStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "compose", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_projectId_project_projectId_fk": { + "name": "compose_projectId_project_projectId_fk", + "tableFrom": "compose", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compose_githubId_github_githubId_fk": { + "name": "compose_githubId_github_githubId_fk", + "tableFrom": "compose", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_gitlabId_gitlab_gitlabId_fk": { + "name": "compose_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "compose", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "compose_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "compose", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_serverId_server_serverId_fk": { + "name": "compose_serverId_server_serverId_fk", + "tableFrom": "compose", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.registry": { + "name": "registry", + "schema": "", + "columns": { + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "registryName": { + "name": "registryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePrefix": { + "name": "imagePrefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selfHosted": { + "name": "selfHosted", + "type": "RegistryType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "registry_adminId_admin_adminId_fk": { + "name": "registry_adminId_admin_adminId_fk", + "tableFrom": "registry", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.discord": { + "name": "discord", + "schema": "", + "columns": { + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.email": { + "name": "email", + "schema": "", + "columns": { + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "smtpServer": { + "name": "smtpServer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtpPort": { + "name": "smtpPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "notificationId": { + "name": "notificationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appDeploy": { + "name": "appDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "appBuildError": { + "name": "appBuildError", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "databaseBackup": { + "name": "databaseBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dokployRestart": { + "name": "dokployRestart", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerCleanup": { + "name": "dockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notificationType": { + "name": "notificationType", + "type": "notificationType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "notification_slackId_slack_slackId_fk": { + "name": "notification_slackId_slack_slackId_fk", + "tableFrom": "notification", + "tableTo": "slack", + "columnsFrom": [ + "slackId" + ], + "columnsTo": [ + "slackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_telegramId_telegram_telegramId_fk": { + "name": "notification_telegramId_telegram_telegramId_fk", + "tableFrom": "notification", + "tableTo": "telegram", + "columnsFrom": [ + "telegramId" + ], + "columnsTo": [ + "telegramId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_discordId_discord_discordId_fk": { + "name": "notification_discordId_discord_discordId_fk", + "tableFrom": "notification", + "tableTo": "discord", + "columnsFrom": [ + "discordId" + ], + "columnsTo": [ + "discordId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_emailId_email_emailId_fk": { + "name": "notification_emailId_email_emailId_fk", + "tableFrom": "notification", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "emailId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_adminId_admin_adminId_fk": { + "name": "notification_adminId_admin_adminId_fk", + "tableFrom": "notification", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.slack": { + "name": "slack", + "schema": "", + "columns": { + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.telegram": { + "name": "telegram", + "schema": "", + "columns": { + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "botToken": { + "name": "botToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatId": { + "name": "chatId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.ssh-key": { + "name": "ssh-key", + "schema": "", + "columns": { + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "ssh-key_adminId_admin_adminId_fk": { + "name": "ssh-key_adminId_admin_adminId_fk", + "tableFrom": "ssh-key", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerType": { + "name": "providerType", + "type": "gitProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "git_provider_adminId_admin_adminId_fk": { + "name": "git_provider_adminId_admin_adminId_fk", + "tableFrom": "git_provider", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketWorkspaceName": { + "name": "bitbucketWorkspaceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bitbucket_gitProviderId_git_provider_gitProviderId_fk": { + "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "bitbucket", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.github": { + "name": "github", + "schema": "", + "columns": { + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "githubAppName": { + "name": "githubAppName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubAppId": { + "name": "githubAppId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "githubClientId": { + "name": "githubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubClientSecret": { + "name": "githubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubInstallationId": { + "name": "githubInstallationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubPrivateKey": { + "name": "githubPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubWebhookSecret": { + "name": "githubWebhookSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_gitProviderId_git_provider_gitProviderId_fk": { + "name": "github_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "github", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.gitlab": { + "name": "gitlab", + "schema": "", + "columns": { + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gitlab_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitlab", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.server": { + "name": "server", + "schema": "", + "columns": { + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverStatus": { + "name": "serverStatus", + "type": "serverStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "server_adminId_admin_adminId_fk": { + "name": "server_adminId_admin_adminId_fk", + "tableFrom": "server", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_sshKeyId_ssh-key_sshKeyId_fk": { + "name": "server_sshKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "server", + "tableTo": "ssh-key", + "columnsFrom": [ + "sshKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.preview_deployments": { + "name": "preview_deployments", + "schema": "", + "columns": { + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestId": { + "name": "pullRequestId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestNumber": { + "name": "pullRequestNumber", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestURL": { + "name": "pullRequestURL", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestTitle": { + "name": "pullRequestTitle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestCommentId": { + "name": "pullRequestCommentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "preview_deployments_applicationId_application_applicationId_fk": { + "name": "preview_deployments_applicationId_application_applicationId_fk", + "tableFrom": "preview_deployments", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "preview_deployments_domainId_domain_domainId_fk": { + "name": "preview_deployments_domainId_domain_domainId_fk", + "tableFrom": "preview_deployments", + "tableTo": "domain", + "columnsFrom": [ + "domainId" + ], + "columnsTo": [ + "domainId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "preview_deployments_deploymentId_deployment_deploymentId_fk": { + "name": "preview_deployments_deploymentId_deployment_deploymentId_fk", + "tableFrom": "preview_deployments", + "tableTo": "deployment", + "columnsFrom": [ + "deploymentId" + ], + "columnsTo": [ + "deploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "preview_deployments_appName_unique": { + "name": "preview_deployments_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + } + }, + "enums": { + "public.buildType": { + "name": "buildType", + "schema": "public", + "values": [ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static" + ] + }, + "public.sourceType": { + "name": "sourceType", + "schema": "public", + "values": [ + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "drop" + ] + }, + "public.Roles": { + "name": "Roles", + "schema": "public", + "values": [ + "admin", + "user" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application", + "preview" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error" + ] + }, + "public.mountType": { + "name": "mountType", + "schema": "public", + "values": [ + "bind", + "volume", + "file" + ] + }, + "public.serviceType": { + "name": "serviceType", + "schema": "public", + "values": [ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.applicationStatus": { + "name": "applicationStatus", + "schema": "public", + "values": [ + "idle", + "running", + "done", + "error" + ] + }, + "public.certificateType": { + "name": "certificateType", + "schema": "public", + "values": [ + "letsencrypt", + "none" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "raw" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0048_snapshot.json b/apps/dokploy/drizzle/meta/0048_snapshot.json new file mode 100644 index 000000000..47ad2cb90 --- /dev/null +++ b/apps/dokploy/drizzle/meta/0048_snapshot.json @@ -0,0 +1,4245 @@ +{ + "id": "20236ed8-104c-487b-bcdf-d08b69fbc80a", + "prevId": "053ad983-6299-4420-a551-645490689733", + "version": "6", + "dialect": "postgresql", + "tables": { + "public.application": { + "name": "application", + "schema": "", + "columns": { + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewEnv": { + "name": "previewEnv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewBuildArgs": { + "name": "previewBuildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewWildcard": { + "name": "previewWildcard", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewPort": { + "name": "previewPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "previewHttps": { + "name": "previewHttps", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "previewPath": { + "name": "previewPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "previewLimit": { + "name": "previewLimit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "isPreviewDeploymentsActive": { + "name": "isPreviewDeploymentsActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildPath": { + "name": "buildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBuildPath": { + "name": "gitlabBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBuildPath": { + "name": "bitbucketBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBuildPath": { + "name": "customGitBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerfile": { + "name": "dockerfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerContextPath": { + "name": "dockerContextPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerBuildStage": { + "name": "dockerBuildStage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropBuildPath": { + "name": "dropBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "buildType": { + "name": "buildType", + "type": "buildType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'nixpacks'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "application", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_registryId_registry_registryId_fk": { + "name": "application_registryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "registryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_projectId_project_projectId_fk": { + "name": "application_projectId_project_projectId_fk", + "tableFrom": "application", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_githubId_github_githubId_fk": { + "name": "application_githubId_github_githubId_fk", + "tableFrom": "application", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_gitlabId_gitlab_gitlabId_fk": { + "name": "application_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "application", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "application_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "application", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_serverId_server_serverId_fk": { + "name": "application_serverId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.postgres": { + "name": "postgres", + "schema": "", + "columns": { + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_projectId_project_projectId_fk": { + "name": "postgres_projectId_project_projectId_fk", + "tableFrom": "postgres", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "postgres_serverId_server_serverId_fk": { + "name": "postgres_serverId_server_serverId_fk", + "tableFrom": "postgres", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "postgres_appName_unique": { + "name": "postgres_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isRegistered": { + "name": "isRegistered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expirationDate": { + "name": "expirationDate", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canCreateProjects": { + "name": "canCreateProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToSSHKeys": { + "name": "canAccessToSSHKeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateServices": { + "name": "canCreateServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteProjects": { + "name": "canDeleteProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteServices": { + "name": "canDeleteServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToDocker": { + "name": "canAccessToDocker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToAPI": { + "name": "canAccessToAPI", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToGitProviders": { + "name": "canAccessToGitProviders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToTraefikFiles": { + "name": "canAccessToTraefikFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accesedServices": { + "name": "accesedServices", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authId": { + "name": "authId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_adminId_admin_adminId_fk": { + "name": "user_adminId_admin_adminId_fk", + "tableFrom": "user", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_authId_auth_id_fk": { + "name": "user_authId_auth_id_fk", + "tableFrom": "user", + "tableTo": "auth", + "columnsFrom": [ + "authId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.admin": { + "name": "admin", + "schema": "", + "columns": { + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverIp": { + "name": "serverIp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "letsEncryptEmail": { + "name": "letsEncryptEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sshPrivateKey": { + "name": "sshPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enableLogRotation": { + "name": "enableLogRotation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "authId": { + "name": "authId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serversQuantity": { + "name": "serversQuantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "admin_authId_auth_id_fk": { + "name": "admin_authId_auth_id_fk", + "tableFrom": "admin", + "tableTo": "auth", + "columnsFrom": [ + "authId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.auth": { + "name": "auth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rol": { + "name": "rol", + "type": "Roles", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is2FAEnabled": { + "name": "is2FAEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resetPasswordToken": { + "name": "resetPasswordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resetPasswordExpiresAt": { + "name": "resetPasswordExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationToken": { + "name": "confirmationToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationExpiresAt": { + "name": "confirmationExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_email_unique": { + "name": "auth_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "project_adminId_admin_adminId_fk": { + "name": "project_adminId_admin_adminId_fk", + "tableFrom": "project", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.domain": { + "name": "domain", + "schema": "", + "columns": { + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domainType": { + "name": "domainType", + "type": "domainType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'application'" + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + } + }, + "indexes": {}, + "foreignKeys": { + "domain_composeId_compose_composeId_fk": { + "name": "domain_composeId_compose_composeId_fk", + "tableFrom": "domain", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_applicationId_application_applicationId_fk": { + "name": "domain_applicationId_application_applicationId_fk", + "tableFrom": "domain", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "domain", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.mariadb": { + "name": "mariadb", + "schema": "", + "columns": { + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_projectId_project_projectId_fk": { + "name": "mariadb_projectId_project_projectId_fk", + "tableFrom": "mariadb", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mariadb_serverId_server_serverId_fk": { + "name": "mariadb_serverId_server_serverId_fk", + "tableFrom": "mariadb", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mariadb_appName_unique": { + "name": "mariadb_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.mongo": { + "name": "mongo", + "schema": "", + "columns": { + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mongo_projectId_project_projectId_fk": { + "name": "mongo_projectId_project_projectId_fk", + "tableFrom": "mongo", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mongo_serverId_server_serverId_fk": { + "name": "mongo_serverId_server_serverId_fk", + "tableFrom": "mongo", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mongo_appName_unique": { + "name": "mongo_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.mysql": { + "name": "mysql", + "schema": "", + "columns": { + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_projectId_project_projectId_fk": { + "name": "mysql_projectId_project_projectId_fk", + "tableFrom": "mysql", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mysql_serverId_server_serverId_fk": { + "name": "mysql_serverId_server_serverId_fk", + "tableFrom": "mysql", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mysql_appName_unique": { + "name": "mysql_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.backup": { + "name": "backup", + "schema": "", + "columns": { + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "database": { + "name": "database", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseType": { + "name": "databaseType", + "type": "databaseType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_destinationId_destination_destinationId_fk": { + "name": "backup_destinationId_destination_destinationId_fk", + "tableFrom": "backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_postgresId_postgres_postgresId_fk": { + "name": "backup_postgresId_postgres_postgresId_fk", + "tableFrom": "backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mariadbId_mariadb_mariadbId_fk": { + "name": "backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mysqlId_mysql_mysqlId_fk": { + "name": "backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mongoId_mongo_mongoId_fk": { + "name": "backup_mongoId_mongo_mongoId_fk", + "tableFrom": "backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.destination": { + "name": "destination", + "schema": "", + "columns": { + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessKey": { + "name": "accessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "destination_adminId_admin_adminId_fk": { + "name": "destination_adminId_admin_adminId_fk", + "tableFrom": "destination", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "deploymentStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'running'" + }, + "logPath": { + "name": "logPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "deployment_applicationId_application_applicationId_fk": { + "name": "deployment_applicationId_application_applicationId_fk", + "tableFrom": "deployment", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_composeId_compose_composeId_fk": { + "name": "deployment_composeId_compose_composeId_fk", + "tableFrom": "deployment", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_serverId_server_serverId_fk": { + "name": "deployment_serverId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "deployment", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.mount": { + "name": "mount", + "schema": "", + "columns": { + "mountId": { + "name": "mountId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "mountType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "hostPath": { + "name": "hostPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "mountPath": { + "name": "mountPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mount_applicationId_application_applicationId_fk": { + "name": "mount_applicationId_application_applicationId_fk", + "tableFrom": "mount", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_postgresId_postgres_postgresId_fk": { + "name": "mount_postgresId_postgres_postgresId_fk", + "tableFrom": "mount", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mariadbId_mariadb_mariadbId_fk": { + "name": "mount_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "mount", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mongoId_mongo_mongoId_fk": { + "name": "mount_mongoId_mongo_mongoId_fk", + "tableFrom": "mount", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mysqlId_mysql_mysqlId_fk": { + "name": "mount_mysqlId_mysql_mysqlId_fk", + "tableFrom": "mount", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_redisId_redis_redisId_fk": { + "name": "mount_redisId_redis_redisId_fk", + "tableFrom": "mount", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_composeId_compose_composeId_fk": { + "name": "mount_composeId_compose_composeId_fk", + "tableFrom": "mount", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "certificateId": { + "name": "certificateId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificateData": { + "name": "certificateData", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificatePath": { + "name": "certificatePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autoRenew": { + "name": "autoRenew", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "certificate_adminId_admin_adminId_fk": { + "name": "certificate_adminId_admin_adminId_fk", + "tableFrom": "certificate", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "certificate_serverId_server_serverId_fk": { + "name": "certificate_serverId_server_serverId_fk", + "tableFrom": "certificate", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_certificatePath_unique": { + "name": "certificate_certificatePath_unique", + "nullsNotDistinct": false, + "columns": [ + "certificatePath" + ] + } + } + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_auth_id_fk": { + "name": "session_user_id_auth_id_fk", + "tableFrom": "session", + "tableTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.redirect": { + "name": "redirect", + "schema": "", + "columns": { + "redirectId": { + "name": "redirectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "regex": { + "name": "regex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanent": { + "name": "permanent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "redirect_applicationId_application_applicationId_fk": { + "name": "redirect_applicationId_application_applicationId_fk", + "tableFrom": "redirect", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.security": { + "name": "security", + "schema": "", + "columns": { + "securityId": { + "name": "securityId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "security_applicationId_application_applicationId_fk": { + "name": "security_applicationId_application_applicationId_fk", + "tableFrom": "security", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_username_applicationId_unique": { + "name": "security_username_applicationId_unique", + "nullsNotDistinct": false, + "columns": [ + "username", + "applicationId" + ] + } + } + }, + "public.port": { + "name": "port", + "schema": "", + "columns": { + "portId": { + "name": "portId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "publishedPort": { + "name": "publishedPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "targetPort": { + "name": "targetPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "protocolType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "port_applicationId_application_applicationId_fk": { + "name": "port_applicationId_application_applicationId_fk", + "tableFrom": "port", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.redis": { + "name": "redis", + "schema": "", + "columns": { + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_projectId_project_projectId_fk": { + "name": "redis_projectId_project_projectId_fk", + "tableFrom": "redis", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "redis_serverId_server_serverId_fk": { + "name": "redis_serverId_server_serverId_fk", + "tableFrom": "redis", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "redis_appName_unique": { + "name": "redis_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.compose": { + "name": "compose", + "schema": "", + "columns": { + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeFile": { + "name": "composeFile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceTypeCompose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "composeType": { + "name": "composeType", + "type": "composeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'docker-compose'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "composePath": { + "name": "composePath", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'./docker-compose.yml'" + }, + "suffix": { + "name": "suffix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "randomize": { + "name": "randomize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "composeStatus": { + "name": "composeStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "compose", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_projectId_project_projectId_fk": { + "name": "compose_projectId_project_projectId_fk", + "tableFrom": "compose", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compose_githubId_github_githubId_fk": { + "name": "compose_githubId_github_githubId_fk", + "tableFrom": "compose", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_gitlabId_gitlab_gitlabId_fk": { + "name": "compose_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "compose", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "compose_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "compose", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_serverId_server_serverId_fk": { + "name": "compose_serverId_server_serverId_fk", + "tableFrom": "compose", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.registry": { + "name": "registry", + "schema": "", + "columns": { + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "registryName": { + "name": "registryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePrefix": { + "name": "imagePrefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selfHosted": { + "name": "selfHosted", + "type": "RegistryType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "registry_adminId_admin_adminId_fk": { + "name": "registry_adminId_admin_adminId_fk", + "tableFrom": "registry", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.discord": { + "name": "discord", + "schema": "", + "columns": { + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.email": { + "name": "email", + "schema": "", + "columns": { + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "smtpServer": { + "name": "smtpServer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtpPort": { + "name": "smtpPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "notificationId": { + "name": "notificationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appDeploy": { + "name": "appDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "appBuildError": { + "name": "appBuildError", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "databaseBackup": { + "name": "databaseBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dokployRestart": { + "name": "dokployRestart", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerCleanup": { + "name": "dockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notificationType": { + "name": "notificationType", + "type": "notificationType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "notification_slackId_slack_slackId_fk": { + "name": "notification_slackId_slack_slackId_fk", + "tableFrom": "notification", + "tableTo": "slack", + "columnsFrom": [ + "slackId" + ], + "columnsTo": [ + "slackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_telegramId_telegram_telegramId_fk": { + "name": "notification_telegramId_telegram_telegramId_fk", + "tableFrom": "notification", + "tableTo": "telegram", + "columnsFrom": [ + "telegramId" + ], + "columnsTo": [ + "telegramId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_discordId_discord_discordId_fk": { + "name": "notification_discordId_discord_discordId_fk", + "tableFrom": "notification", + "tableTo": "discord", + "columnsFrom": [ + "discordId" + ], + "columnsTo": [ + "discordId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_emailId_email_emailId_fk": { + "name": "notification_emailId_email_emailId_fk", + "tableFrom": "notification", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "emailId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_adminId_admin_adminId_fk": { + "name": "notification_adminId_admin_adminId_fk", + "tableFrom": "notification", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.slack": { + "name": "slack", + "schema": "", + "columns": { + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.telegram": { + "name": "telegram", + "schema": "", + "columns": { + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "botToken": { + "name": "botToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatId": { + "name": "chatId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.ssh-key": { + "name": "ssh-key", + "schema": "", + "columns": { + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "ssh-key_adminId_admin_adminId_fk": { + "name": "ssh-key_adminId_admin_adminId_fk", + "tableFrom": "ssh-key", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerType": { + "name": "providerType", + "type": "gitProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "git_provider_adminId_admin_adminId_fk": { + "name": "git_provider_adminId_admin_adminId_fk", + "tableFrom": "git_provider", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketWorkspaceName": { + "name": "bitbucketWorkspaceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bitbucket_gitProviderId_git_provider_gitProviderId_fk": { + "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "bitbucket", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.github": { + "name": "github", + "schema": "", + "columns": { + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "githubAppName": { + "name": "githubAppName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubAppId": { + "name": "githubAppId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "githubClientId": { + "name": "githubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubClientSecret": { + "name": "githubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubInstallationId": { + "name": "githubInstallationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubPrivateKey": { + "name": "githubPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubWebhookSecret": { + "name": "githubWebhookSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_gitProviderId_git_provider_gitProviderId_fk": { + "name": "github_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "github", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.gitlab": { + "name": "gitlab", + "schema": "", + "columns": { + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gitlab_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitlab", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.server": { + "name": "server", + "schema": "", + "columns": { + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverStatus": { + "name": "serverStatus", + "type": "serverStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "server_adminId_admin_adminId_fk": { + "name": "server_adminId_admin_adminId_fk", + "tableFrom": "server", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_sshKeyId_ssh-key_sshKeyId_fk": { + "name": "server_sshKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "server", + "tableTo": "ssh-key", + "columnsFrom": [ + "sshKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.preview_deployments": { + "name": "preview_deployments", + "schema": "", + "columns": { + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestId": { + "name": "pullRequestId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestNumber": { + "name": "pullRequestNumber", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestURL": { + "name": "pullRequestURL", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestTitle": { + "name": "pullRequestTitle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestCommentId": { + "name": "pullRequestCommentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previewStatus": { + "name": "previewStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "preview_deployments_applicationId_application_applicationId_fk": { + "name": "preview_deployments_applicationId_application_applicationId_fk", + "tableFrom": "preview_deployments", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "preview_deployments_domainId_domain_domainId_fk": { + "name": "preview_deployments_domainId_domain_domainId_fk", + "tableFrom": "preview_deployments", + "tableTo": "domain", + "columnsFrom": [ + "domainId" + ], + "columnsTo": [ + "domainId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "preview_deployments_deploymentId_deployment_deploymentId_fk": { + "name": "preview_deployments_deploymentId_deployment_deploymentId_fk", + "tableFrom": "preview_deployments", + "tableTo": "deployment", + "columnsFrom": [ + "deploymentId" + ], + "columnsTo": [ + "deploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "preview_deployments_appName_unique": { + "name": "preview_deployments_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + } + }, + "enums": { + "public.buildType": { + "name": "buildType", + "schema": "public", + "values": [ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static" + ] + }, + "public.sourceType": { + "name": "sourceType", + "schema": "public", + "values": [ + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "drop" + ] + }, + "public.Roles": { + "name": "Roles", + "schema": "public", + "values": [ + "admin", + "user" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application", + "preview" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error" + ] + }, + "public.mountType": { + "name": "mountType", + "schema": "public", + "values": [ + "bind", + "volume", + "file" + ] + }, + "public.serviceType": { + "name": "serviceType", + "schema": "public", + "values": [ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.applicationStatus": { + "name": "applicationStatus", + "schema": "public", + "values": [ + "idle", + "running", + "done", + "error" + ] + }, + "public.certificateType": { + "name": "certificateType", + "schema": "public", + "values": [ + "letsencrypt", + "none" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "raw" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/0049_snapshot.json b/apps/dokploy/drizzle/meta/0049_snapshot.json new file mode 100644 index 000000000..43c6e0c02 --- /dev/null +++ b/apps/dokploy/drizzle/meta/0049_snapshot.json @@ -0,0 +1,4226 @@ +{ + "id": "71016fed-2c39-4d31-aa33-0e0aceb313ef", + "prevId": "20236ed8-104c-487b-bcdf-d08b69fbc80a", + "version": "6", + "dialect": "postgresql", + "tables": { + "public.application": { + "name": "application", + "schema": "", + "columns": { + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewEnv": { + "name": "previewEnv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewBuildArgs": { + "name": "previewBuildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewWildcard": { + "name": "previewWildcard", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewPort": { + "name": "previewPort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "previewHttps": { + "name": "previewHttps", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "previewPath": { + "name": "previewPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "previewLimit": { + "name": "previewLimit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "isPreviewDeploymentsActive": { + "name": "isPreviewDeploymentsActive", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "buildArgs": { + "name": "buildArgs", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "buildPath": { + "name": "buildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBuildPath": { + "name": "gitlabBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBuildPath": { + "name": "bitbucketBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBuildPath": { + "name": "customGitBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerfile": { + "name": "dockerfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerContextPath": { + "name": "dockerContextPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerBuildStage": { + "name": "dockerBuildStage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropBuildPath": { + "name": "dropBuildPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "healthCheckSwarm": { + "name": "healthCheckSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "restartPolicySwarm": { + "name": "restartPolicySwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "placementSwarm": { + "name": "placementSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "updateConfigSwarm": { + "name": "updateConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "rollbackConfigSwarm": { + "name": "rollbackConfigSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "modeSwarm": { + "name": "modeSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "labelsSwarm": { + "name": "labelsSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "networkSwarm": { + "name": "networkSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "replicas": { + "name": "replicas", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "buildType": { + "name": "buildType", + "type": "buildType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'nixpacks'" + }, + "publishDirectory": { + "name": "publishDirectory", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "application", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_registryId_registry_registryId_fk": { + "name": "application_registryId_registry_registryId_fk", + "tableFrom": "application", + "tableTo": "registry", + "columnsFrom": [ + "registryId" + ], + "columnsTo": [ + "registryId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_projectId_project_projectId_fk": { + "name": "application_projectId_project_projectId_fk", + "tableFrom": "application", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "application_githubId_github_githubId_fk": { + "name": "application_githubId_github_githubId_fk", + "tableFrom": "application", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_gitlabId_gitlab_gitlabId_fk": { + "name": "application_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "application", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "application_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "application", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "application_serverId_server_serverId_fk": { + "name": "application_serverId_server_serverId_fk", + "tableFrom": "application", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "application_appName_unique": { + "name": "application_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.postgres": { + "name": "postgres", + "schema": "", + "columns": { + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "postgres_projectId_project_projectId_fk": { + "name": "postgres_projectId_project_projectId_fk", + "tableFrom": "postgres", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "postgres_serverId_server_serverId_fk": { + "name": "postgres_serverId_server_serverId_fk", + "tableFrom": "postgres", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "postgres_appName_unique": { + "name": "postgres_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isRegistered": { + "name": "isRegistered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expirationDate": { + "name": "expirationDate", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canCreateProjects": { + "name": "canCreateProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToSSHKeys": { + "name": "canAccessToSSHKeys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canCreateServices": { + "name": "canCreateServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteProjects": { + "name": "canDeleteProjects", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canDeleteServices": { + "name": "canDeleteServices", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToDocker": { + "name": "canAccessToDocker", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToAPI": { + "name": "canAccessToAPI", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToGitProviders": { + "name": "canAccessToGitProviders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "canAccessToTraefikFiles": { + "name": "canAccessToTraefikFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "accesedProjects": { + "name": "accesedProjects", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "accesedServices": { + "name": "accesedServices", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authId": { + "name": "authId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_adminId_admin_adminId_fk": { + "name": "user_adminId_admin_adminId_fk", + "tableFrom": "user", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_authId_auth_id_fk": { + "name": "user_authId_auth_id_fk", + "tableFrom": "user", + "tableTo": "auth", + "columnsFrom": [ + "authId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.admin": { + "name": "admin", + "schema": "", + "columns": { + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "serverIp": { + "name": "serverIp", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "letsEncryptEmail": { + "name": "letsEncryptEmail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sshPrivateKey": { + "name": "sshPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enableLogRotation": { + "name": "enableLogRotation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "authId": { + "name": "authId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serversQuantity": { + "name": "serversQuantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "admin_authId_auth_id_fk": { + "name": "admin_authId_auth_id_fk", + "tableFrom": "admin", + "tableTo": "auth", + "columnsFrom": [ + "authId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.auth": { + "name": "auth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rol": { + "name": "rol", + "type": "Roles", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is2FAEnabled": { + "name": "is2FAEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resetPasswordToken": { + "name": "resetPasswordToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resetPasswordExpiresAt": { + "name": "resetPasswordExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationToken": { + "name": "confirmationToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confirmationExpiresAt": { + "name": "confirmationExpiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_email_unique": { + "name": "auth_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "project_adminId_admin_adminId_fk": { + "name": "project_adminId_admin_adminId_fk", + "tableFrom": "project", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.domain": { + "name": "domain", + "schema": "", + "columns": { + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "https": { + "name": "https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3000 + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'/'" + }, + "serviceName": { + "name": "serviceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domainType": { + "name": "domainType", + "type": "domainType", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'application'" + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificateType": { + "name": "certificateType", + "type": "certificateType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + } + }, + "indexes": {}, + "foreignKeys": { + "domain_composeId_compose_composeId_fk": { + "name": "domain_composeId_compose_composeId_fk", + "tableFrom": "domain", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_applicationId_application_applicationId_fk": { + "name": "domain_applicationId_application_applicationId_fk", + "tableFrom": "domain", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "domain", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.mariadb": { + "name": "mariadb", + "schema": "", + "columns": { + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mariadb_projectId_project_projectId_fk": { + "name": "mariadb_projectId_project_projectId_fk", + "tableFrom": "mariadb", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mariadb_serverId_server_serverId_fk": { + "name": "mariadb_serverId_server_serverId_fk", + "tableFrom": "mariadb", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mariadb_appName_unique": { + "name": "mariadb_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.mongo": { + "name": "mongo", + "schema": "", + "columns": { + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mongo_projectId_project_projectId_fk": { + "name": "mongo_projectId_project_projectId_fk", + "tableFrom": "mongo", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mongo_serverId_server_serverId_fk": { + "name": "mongo_serverId_server_serverId_fk", + "tableFrom": "mongo", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mongo_appName_unique": { + "name": "mongo_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.mysql": { + "name": "mysql", + "schema": "", + "columns": { + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "databaseName": { + "name": "databaseName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseUser": { + "name": "databaseUser", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databasePassword": { + "name": "databasePassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rootPassword": { + "name": "rootPassword", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mysql_projectId_project_projectId_fk": { + "name": "mysql_projectId_project_projectId_fk", + "tableFrom": "mysql", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mysql_serverId_server_serverId_fk": { + "name": "mysql_serverId_server_serverId_fk", + "tableFrom": "mysql", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mysql_appName_unique": { + "name": "mysql_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.backup": { + "name": "backup", + "schema": "", + "columns": { + "backupId": { + "name": "backupId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "database": { + "name": "database", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "databaseType": { + "name": "databaseType", + "type": "databaseType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "backup_destinationId_destination_destinationId_fk": { + "name": "backup_destinationId_destination_destinationId_fk", + "tableFrom": "backup", + "tableTo": "destination", + "columnsFrom": [ + "destinationId" + ], + "columnsTo": [ + "destinationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_postgresId_postgres_postgresId_fk": { + "name": "backup_postgresId_postgres_postgresId_fk", + "tableFrom": "backup", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mariadbId_mariadb_mariadbId_fk": { + "name": "backup_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "backup", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mysqlId_mysql_mysqlId_fk": { + "name": "backup_mysqlId_mysql_mysqlId_fk", + "tableFrom": "backup", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_mongoId_mongo_mongoId_fk": { + "name": "backup_mongoId_mongo_mongoId_fk", + "tableFrom": "backup", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.destination": { + "name": "destination", + "schema": "", + "columns": { + "destinationId": { + "name": "destinationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessKey": { + "name": "accessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secretAccessKey": { + "name": "secretAccessKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "destination_adminId_admin_adminId_fk": { + "name": "destination_adminId_admin_adminId_fk", + "tableFrom": "destination", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "deploymentId": { + "name": "deploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "deploymentStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'running'" + }, + "logPath": { + "name": "logPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPreviewDeployment": { + "name": "isPreviewDeployment", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "deployment_applicationId_application_applicationId_fk": { + "name": "deployment_applicationId_application_applicationId_fk", + "tableFrom": "deployment", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_composeId_compose_composeId_fk": { + "name": "deployment_composeId_compose_composeId_fk", + "tableFrom": "deployment", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_serverId_server_serverId_fk": { + "name": "deployment_serverId_server_serverId_fk", + "tableFrom": "deployment", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": { + "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk", + "tableFrom": "deployment", + "tableTo": "preview_deployments", + "columnsFrom": [ + "previewDeploymentId" + ], + "columnsTo": [ + "previewDeploymentId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.mount": { + "name": "mount", + "schema": "", + "columns": { + "mountId": { + "name": "mountId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "mountType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "hostPath": { + "name": "hostPath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volumeName": { + "name": "volumeName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filePath": { + "name": "filePath", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serviceType": { + "name": "serviceType", + "type": "serviceType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'application'" + }, + "mountPath": { + "name": "mountPath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postgresId": { + "name": "postgresId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mariadbId": { + "name": "mariadbId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mongoId": { + "name": "mongoId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mysqlId": { + "name": "mysqlId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mount_applicationId_application_applicationId_fk": { + "name": "mount_applicationId_application_applicationId_fk", + "tableFrom": "mount", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_postgresId_postgres_postgresId_fk": { + "name": "mount_postgresId_postgres_postgresId_fk", + "tableFrom": "mount", + "tableTo": "postgres", + "columnsFrom": [ + "postgresId" + ], + "columnsTo": [ + "postgresId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mariadbId_mariadb_mariadbId_fk": { + "name": "mount_mariadbId_mariadb_mariadbId_fk", + "tableFrom": "mount", + "tableTo": "mariadb", + "columnsFrom": [ + "mariadbId" + ], + "columnsTo": [ + "mariadbId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mongoId_mongo_mongoId_fk": { + "name": "mount_mongoId_mongo_mongoId_fk", + "tableFrom": "mount", + "tableTo": "mongo", + "columnsFrom": [ + "mongoId" + ], + "columnsTo": [ + "mongoId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_mysqlId_mysql_mysqlId_fk": { + "name": "mount_mysqlId_mysql_mysqlId_fk", + "tableFrom": "mount", + "tableTo": "mysql", + "columnsFrom": [ + "mysqlId" + ], + "columnsTo": [ + "mysqlId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_redisId_redis_redisId_fk": { + "name": "mount_redisId_redis_redisId_fk", + "tableFrom": "mount", + "tableTo": "redis", + "columnsFrom": [ + "redisId" + ], + "columnsTo": [ + "redisId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mount_composeId_compose_composeId_fk": { + "name": "mount_composeId_compose_composeId_fk", + "tableFrom": "mount", + "tableTo": "compose", + "columnsFrom": [ + "composeId" + ], + "columnsTo": [ + "composeId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "certificateId": { + "name": "certificateId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificateData": { + "name": "certificateData", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificatePath": { + "name": "certificatePath", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autoRenew": { + "name": "autoRenew", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "certificate_adminId_admin_adminId_fk": { + "name": "certificate_adminId_admin_adminId_fk", + "tableFrom": "certificate", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "certificate_serverId_server_serverId_fk": { + "name": "certificate_serverId_server_serverId_fk", + "tableFrom": "certificate", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "certificate_certificatePath_unique": { + "name": "certificate_certificatePath_unique", + "nullsNotDistinct": false, + "columns": [ + "certificatePath" + ] + } + } + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_auth_id_fk": { + "name": "session_user_id_auth_id_fk", + "tableFrom": "session", + "tableTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.redirect": { + "name": "redirect", + "schema": "", + "columns": { + "redirectId": { + "name": "redirectId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "regex": { + "name": "regex", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permanent": { + "name": "permanent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "uniqueConfigKey": { + "name": "uniqueConfigKey", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "redirect_applicationId_application_applicationId_fk": { + "name": "redirect_applicationId_application_applicationId_fk", + "tableFrom": "redirect", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.security": { + "name": "security", + "schema": "", + "columns": { + "securityId": { + "name": "securityId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "security_applicationId_application_applicationId_fk": { + "name": "security_applicationId_application_applicationId_fk", + "tableFrom": "security", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_username_applicationId_unique": { + "name": "security_username_applicationId_unique", + "nullsNotDistinct": false, + "columns": [ + "username", + "applicationId" + ] + } + } + }, + "public.port": { + "name": "port", + "schema": "", + "columns": { + "portId": { + "name": "portId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "publishedPort": { + "name": "publishedPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "targetPort": { + "name": "targetPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "protocol": { + "name": "protocol", + "type": "protocolType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "port_applicationId_application_applicationId_fk": { + "name": "port_applicationId_application_applicationId_fk", + "tableFrom": "port", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.redis": { + "name": "redis", + "schema": "", + "columns": { + "redisId": { + "name": "redisId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dockerImage": { + "name": "dockerImage", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "memoryReservation": { + "name": "memoryReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "memoryLimit": { + "name": "memoryLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuReservation": { + "name": "cpuReservation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cpuLimit": { + "name": "cpuLimit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "externalPort": { + "name": "externalPort", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationStatus": { + "name": "applicationStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "redis_projectId_project_projectId_fk": { + "name": "redis_projectId_project_projectId_fk", + "tableFrom": "redis", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "redis_serverId_server_serverId_fk": { + "name": "redis_serverId_server_serverId_fk", + "tableFrom": "redis", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "redis_appName_unique": { + "name": "redis_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + }, + "public.compose": { + "name": "compose", + "schema": "", + "columns": { + "composeId": { + "name": "composeId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "composeFile": { + "name": "composeFile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sourceType": { + "name": "sourceType", + "type": "sourceTypeCompose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "composeType": { + "name": "composeType", + "type": "composeType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'docker-compose'" + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "autoDeploy": { + "name": "autoDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "gitlabProjectId": { + "name": "gitlabProjectId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitlabRepository": { + "name": "gitlabRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabOwner": { + "name": "gitlabOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabBranch": { + "name": "gitlabBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabPathNamespace": { + "name": "gitlabPathNamespace", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketRepository": { + "name": "bitbucketRepository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketOwner": { + "name": "bitbucketOwner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketBranch": { + "name": "bitbucketBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitUrl": { + "name": "customGitUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitBranch": { + "name": "customGitBranch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customGitSSHKeyId": { + "name": "customGitSSHKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "composePath": { + "name": "composePath", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'./docker-compose.yml'" + }, + "suffix": { + "name": "suffix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "randomize": { + "name": "randomize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "composeStatus": { + "name": "composeStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "projectId": { + "name": "projectId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": { + "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "compose", + "tableTo": "ssh-key", + "columnsFrom": [ + "customGitSSHKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_projectId_project_projectId_fk": { + "name": "compose_projectId_project_projectId_fk", + "tableFrom": "compose", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "projectId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compose_githubId_github_githubId_fk": { + "name": "compose_githubId_github_githubId_fk", + "tableFrom": "compose", + "tableTo": "github", + "columnsFrom": [ + "githubId" + ], + "columnsTo": [ + "githubId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_gitlabId_gitlab_gitlabId_fk": { + "name": "compose_gitlabId_gitlab_gitlabId_fk", + "tableFrom": "compose", + "tableTo": "gitlab", + "columnsFrom": [ + "gitlabId" + ], + "columnsTo": [ + "gitlabId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_bitbucketId_bitbucket_bitbucketId_fk": { + "name": "compose_bitbucketId_bitbucket_bitbucketId_fk", + "tableFrom": "compose", + "tableTo": "bitbucket", + "columnsFrom": [ + "bitbucketId" + ], + "columnsTo": [ + "bitbucketId" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compose_serverId_server_serverId_fk": { + "name": "compose_serverId_server_serverId_fk", + "tableFrom": "compose", + "tableTo": "server", + "columnsFrom": [ + "serverId" + ], + "columnsTo": [ + "serverId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.registry": { + "name": "registry", + "schema": "", + "columns": { + "registryId": { + "name": "registryId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "registryName": { + "name": "registryName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imagePrefix": { + "name": "imagePrefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registryUrl": { + "name": "registryUrl", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selfHosted": { + "name": "selfHosted", + "type": "RegistryType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cloud'" + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "registry_adminId_admin_adminId_fk": { + "name": "registry_adminId_admin_adminId_fk", + "tableFrom": "registry", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.discord": { + "name": "discord", + "schema": "", + "columns": { + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.email": { + "name": "email", + "schema": "", + "columns": { + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "smtpServer": { + "name": "smtpServer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtpPort": { + "name": "smtpPort", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fromAddress": { + "name": "fromAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toAddress": { + "name": "toAddress", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "notificationId": { + "name": "notificationId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "appDeploy": { + "name": "appDeploy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "appBuildError": { + "name": "appBuildError", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "databaseBackup": { + "name": "databaseBackup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dokployRestart": { + "name": "dokployRestart", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dockerCleanup": { + "name": "dockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notificationType": { + "name": "notificationType", + "type": "notificationType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discordId": { + "name": "discordId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "notification_slackId_slack_slackId_fk": { + "name": "notification_slackId_slack_slackId_fk", + "tableFrom": "notification", + "tableTo": "slack", + "columnsFrom": [ + "slackId" + ], + "columnsTo": [ + "slackId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_telegramId_telegram_telegramId_fk": { + "name": "notification_telegramId_telegram_telegramId_fk", + "tableFrom": "notification", + "tableTo": "telegram", + "columnsFrom": [ + "telegramId" + ], + "columnsTo": [ + "telegramId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_discordId_discord_discordId_fk": { + "name": "notification_discordId_discord_discordId_fk", + "tableFrom": "notification", + "tableTo": "discord", + "columnsFrom": [ + "discordId" + ], + "columnsTo": [ + "discordId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_emailId_email_emailId_fk": { + "name": "notification_emailId_email_emailId_fk", + "tableFrom": "notification", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "emailId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_adminId_admin_adminId_fk": { + "name": "notification_adminId_admin_adminId_fk", + "tableFrom": "notification", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.slack": { + "name": "slack", + "schema": "", + "columns": { + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhookUrl": { + "name": "webhookUrl", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.telegram": { + "name": "telegram", + "schema": "", + "columns": { + "telegramId": { + "name": "telegramId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "botToken": { + "name": "botToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatId": { + "name": "chatId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.ssh-key": { + "name": "ssh-key", + "schema": "", + "columns": { + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "privateKey": { + "name": "privateKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "publicKey": { + "name": "publicKey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastUsedAt": { + "name": "lastUsedAt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "ssh-key_adminId_admin_adminId_fk": { + "name": "ssh-key_adminId_admin_adminId_fk", + "tableFrom": "ssh-key", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerType": { + "name": "providerType", + "type": "gitProviderType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "git_provider_adminId_admin_adminId_fk": { + "name": "git_provider_adminId_admin_adminId_fk", + "tableFrom": "git_provider", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.bitbucket": { + "name": "bitbucket", + "schema": "", + "columns": { + "bitbucketId": { + "name": "bitbucketId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bitbucketUsername": { + "name": "bitbucketUsername", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appPassword": { + "name": "appPassword", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bitbucketWorkspaceName": { + "name": "bitbucketWorkspaceName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bitbucket_gitProviderId_git_provider_gitProviderId_fk": { + "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "bitbucket", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.github": { + "name": "github", + "schema": "", + "columns": { + "githubId": { + "name": "githubId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "githubAppName": { + "name": "githubAppName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubAppId": { + "name": "githubAppId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "githubClientId": { + "name": "githubClientId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubClientSecret": { + "name": "githubClientSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubInstallationId": { + "name": "githubInstallationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubPrivateKey": { + "name": "githubPrivateKey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "githubWebhookSecret": { + "name": "githubWebhookSecret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_gitProviderId_git_provider_gitProviderId_fk": { + "name": "github_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "github", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.gitlab": { + "name": "gitlab", + "schema": "", + "columns": { + "gitlabId": { + "name": "gitlabId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "gitProviderId": { + "name": "gitProviderId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "gitlab_gitProviderId_git_provider_gitProviderId_fk": { + "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk", + "tableFrom": "gitlab", + "tableTo": "git_provider", + "columnsFrom": [ + "gitProviderId" + ], + "columnsTo": [ + "gitProviderId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.server": { + "name": "server", + "schema": "", + "columns": { + "serverId": { + "name": "serverId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enableDockerCleanup": { + "name": "enableDockerCleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adminId": { + "name": "adminId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serverStatus": { + "name": "serverStatus", + "type": "serverStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "sshKeyId": { + "name": "sshKeyId", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "server_adminId_admin_adminId_fk": { + "name": "server_adminId_admin_adminId_fk", + "tableFrom": "server", + "tableTo": "admin", + "columnsFrom": [ + "adminId" + ], + "columnsTo": [ + "adminId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_sshKeyId_ssh-key_sshKeyId_fk": { + "name": "server_sshKeyId_ssh-key_sshKeyId_fk", + "tableFrom": "server", + "tableTo": "ssh-key", + "columnsFrom": [ + "sshKeyId" + ], + "columnsTo": [ + "sshKeyId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.preview_deployments": { + "name": "preview_deployments", + "schema": "", + "columns": { + "previewDeploymentId": { + "name": "previewDeploymentId", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestId": { + "name": "pullRequestId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestNumber": { + "name": "pullRequestNumber", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestURL": { + "name": "pullRequestURL", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestTitle": { + "name": "pullRequestTitle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pullRequestCommentId": { + "name": "pullRequestCommentId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previewStatus": { + "name": "previewStatus", + "type": "applicationStatus", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "appName": { + "name": "appName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applicationId": { + "name": "applicationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domainId": { + "name": "domainId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "preview_deployments_applicationId_application_applicationId_fk": { + "name": "preview_deployments_applicationId_application_applicationId_fk", + "tableFrom": "preview_deployments", + "tableTo": "application", + "columnsFrom": [ + "applicationId" + ], + "columnsTo": [ + "applicationId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "preview_deployments_domainId_domain_domainId_fk": { + "name": "preview_deployments_domainId_domain_domainId_fk", + "tableFrom": "preview_deployments", + "tableTo": "domain", + "columnsFrom": [ + "domainId" + ], + "columnsTo": [ + "domainId" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "preview_deployments_appName_unique": { + "name": "preview_deployments_appName_unique", + "nullsNotDistinct": false, + "columns": [ + "appName" + ] + } + } + } + }, + "enums": { + "public.buildType": { + "name": "buildType", + "schema": "public", + "values": [ + "dockerfile", + "heroku_buildpacks", + "paketo_buildpacks", + "nixpacks", + "static" + ] + }, + "public.sourceType": { + "name": "sourceType", + "schema": "public", + "values": [ + "docker", + "git", + "github", + "gitlab", + "bitbucket", + "drop" + ] + }, + "public.Roles": { + "name": "Roles", + "schema": "public", + "values": [ + "admin", + "user" + ] + }, + "public.domainType": { + "name": "domainType", + "schema": "public", + "values": [ + "compose", + "application", + "preview" + ] + }, + "public.databaseType": { + "name": "databaseType", + "schema": "public", + "values": [ + "postgres", + "mariadb", + "mysql", + "mongo" + ] + }, + "public.deploymentStatus": { + "name": "deploymentStatus", + "schema": "public", + "values": [ + "running", + "done", + "error" + ] + }, + "public.mountType": { + "name": "mountType", + "schema": "public", + "values": [ + "bind", + "volume", + "file" + ] + }, + "public.serviceType": { + "name": "serviceType", + "schema": "public", + "values": [ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose" + ] + }, + "public.protocolType": { + "name": "protocolType", + "schema": "public", + "values": [ + "tcp", + "udp" + ] + }, + "public.applicationStatus": { + "name": "applicationStatus", + "schema": "public", + "values": [ + "idle", + "running", + "done", + "error" + ] + }, + "public.certificateType": { + "name": "certificateType", + "schema": "public", + "values": [ + "letsencrypt", + "none" + ] + }, + "public.composeType": { + "name": "composeType", + "schema": "public", + "values": [ + "docker-compose", + "stack" + ] + }, + "public.sourceTypeCompose": { + "name": "sourceTypeCompose", + "schema": "public", + "values": [ + "git", + "github", + "gitlab", + "bitbucket", + "raw" + ] + }, + "public.RegistryType": { + "name": "RegistryType", + "schema": "public", + "values": [ + "selfHosted", + "cloud" + ] + }, + "public.notificationType": { + "name": "notificationType", + "schema": "public", + "values": [ + "slack", + "telegram", + "discord", + "email" + ] + }, + "public.gitProviderType": { + "name": "gitProviderType", + "schema": "public", + "values": [ + "github", + "gitlab", + "bitbucket" + ] + }, + "public.serverStatus": { + "name": "serverStatus", + "schema": "public", + "values": [ + "active", + "inactive" + ] + } + }, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json index a2bb1be1b..2ffe385ef 100644 --- a/apps/dokploy/drizzle/meta/_journal.json +++ b/apps/dokploy/drizzle/meta/_journal.json @@ -330,6 +330,27 @@ "when": 1732851191048, "tag": "0046_purple_sleeper", "breakpoints": true + }, + { + "idx": 47, + "version": "6", + "when": 1733089956329, + "tag": "0047_red_stephen_strange", + "breakpoints": true + }, + { + "idx": 48, + "version": "6", + "when": 1733091544421, + "tag": "0048_clumsy_matthew_murdock", + "breakpoints": true + }, + { + "idx": 49, + "version": "6", + "when": 1733091820570, + "tag": "0049_useful_mole_man", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index f1e9505f1..11439f581 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -11,7 +11,8 @@ "build-next": "next build", "setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run", "reset-password": "node -r dotenv/config dist/reset-password.mjs", - "dev": "TURBOPACK=1 tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ", + "dev": "tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ", + "dev-turbopack": "TURBOPACK=1 tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json", "studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts", "migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts", "migration:run": "tsx -r dotenv/config migration.ts", diff --git a/apps/dokploy/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts index 1d8c094af..e89523e95 100644 --- a/apps/dokploy/pages/api/deploy/github.ts +++ b/apps/dokploy/pages/api/deploy/github.ts @@ -3,145 +3,259 @@ import { applications, compose, github } from "@/server/db/schema"; import type { DeploymentJob } from "@/server/queues/queue-types"; import { myQueue } from "@/server/queues/queueSetup"; import { deploy } from "@/server/utils/deploy"; -import { IS_CLOUD } from "@dokploy/server"; +import { + createPreviewDeployment, + type Domain, + findPreviewDeploymentByApplicationId, + findPreviewDeploymentsByPullRequestId, + IS_CLOUD, + removePreviewDeployment, +} from "@dokploy/server"; import { Webhooks } from "@octokit/webhooks"; import { and, eq } from "drizzle-orm"; import type { NextApiRequest, NextApiResponse } from "next"; import { extractCommitMessage, extractHash } from "./[refreshToken]"; +import { generateRandomDomain } from "@/templates/utils"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse + req: NextApiRequest, + res: NextApiResponse, ) { - const signature = req.headers["x-hub-signature-256"]; - const githubBody = req.body; + const signature = req.headers["x-hub-signature-256"]; + const githubBody = req.body; - if (!githubBody?.installation?.id) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } + if (!githubBody?.installation?.id) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } - const githubResult = await db.query.github.findFirst({ - where: eq(github.githubInstallationId, githubBody.installation.id), - }); + const githubResult = await db.query.github.findFirst({ + where: eq(github.githubInstallationId, githubBody.installation.id), + }); - if (!githubResult) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } + if (!githubResult) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } - if (!githubResult.githubWebhookSecret) { - res.status(400).json({ message: "Github Webhook Secret not set" }); - return; - } - const webhooks = new Webhooks({ - secret: githubResult.githubWebhookSecret, - }); + if (!githubResult.githubWebhookSecret) { + res.status(400).json({ message: "Github Webhook Secret not set" }); + return; + } + const webhooks = new Webhooks({ + secret: githubResult.githubWebhookSecret, + }); - const verified = await webhooks.verify( - JSON.stringify(githubBody), - signature as string - ); + const verified = await webhooks.verify( + JSON.stringify(githubBody), + signature as string, + ); - if (!verified) { - res.status(401).json({ message: "Unauthorized" }); - return; - } + if (!verified) { + res.status(401).json({ message: "Unauthorized" }); + return; + } - if (req.headers["x-github-event"] === "ping") { - res.status(200).json({ message: "Ping received, webhook is active" }); - return; - } + if (req.headers["x-github-event"] === "ping") { + res.status(200).json({ message: "Ping received, webhook is active" }); + return; + } - if (req.headers["x-github-event"] !== "push") { - res.status(400).json({ message: "We only accept push events" }); - return; - } + if ( + req.headers["x-github-event"] !== "push" && + req.headers["x-github-event"] !== "pull_request" + ) { + res + .status(400) + .json({ message: "We only accept push events or pull_request events" }); + return; + } - try { - const branchName = githubBody?.ref?.replace("refs/heads/", ""); - const repository = githubBody?.repository?.name; - const deploymentTitle = extractCommitMessage(req.headers, req.body); - const deploymentHash = extractHash(req.headers, req.body); + if (req.headers["x-github-event"] === "push") { + try { + const branchName = githubBody?.ref?.replace("refs/heads/", ""); + const repository = githubBody?.repository?.name; + const deploymentTitle = extractCommitMessage(req.headers, req.body); + const deploymentHash = extractHash(req.headers, req.body); - const apps = await db.query.applications.findMany({ - where: and( - eq(applications.sourceType, "github"), - eq(applications.autoDeploy, true), - eq(applications.branch, branchName), - eq(applications.repository, repository) - ), - }); + const apps = await db.query.applications.findMany({ + where: and( + eq(applications.sourceType, "github"), + eq(applications.autoDeploy, true), + eq(applications.branch, branchName), + eq(applications.repository, repository), + ), + }); - for (const app of apps) { - const jobData: DeploymentJob = { - applicationId: app.applicationId as string, - titleLog: deploymentTitle, - descriptionLog: `Hash: ${deploymentHash}`, - type: "deploy", - applicationType: "application", - server: !!app.serverId, - }; + for (const app of apps) { + const jobData: DeploymentJob = { + applicationId: app.applicationId as string, + titleLog: deploymentTitle, + descriptionLog: `Hash: ${deploymentHash}`, + type: "deploy", + applicationType: "application", + server: !!app.serverId, + }; - if (IS_CLOUD && app.serverId) { - jobData.serverId = app.serverId; - await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - } + if (IS_CLOUD && app.serverId) { + jobData.serverId = app.serverId; + await deploy(jobData); + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } - const composeApps = await db.query.compose.findMany({ - where: and( - eq(compose.sourceType, "github"), - eq(compose.autoDeploy, true), - eq(compose.branch, branchName), - eq(compose.repository, repository) - ), - }); + const composeApps = await db.query.compose.findMany({ + where: and( + eq(compose.sourceType, "github"), + eq(compose.autoDeploy, true), + eq(compose.branch, branchName), + eq(compose.repository, repository), + ), + }); - for (const composeApp of composeApps) { - const jobData: DeploymentJob = { - composeId: composeApp.composeId as string, - titleLog: deploymentTitle, - type: "deploy", - applicationType: "compose", - descriptionLog: `Hash: ${deploymentHash}`, - server: !!composeApp.serverId, - }; + for (const composeApp of composeApps) { + const jobData: DeploymentJob = { + composeId: composeApp.composeId as string, + titleLog: deploymentTitle, + type: "deploy", + applicationType: "compose", + descriptionLog: `Hash: ${deploymentHash}`, + server: !!composeApp.serverId, + }; - if (IS_CLOUD && composeApp.serverId) { - jobData.serverId = composeApp.serverId; - await deploy(jobData); - return true; - } + if (IS_CLOUD && composeApp.serverId) { + jobData.serverId = composeApp.serverId; + await deploy(jobData); + return true; + } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } - const totalApps = apps.length + composeApps.length; - const emptyApps = totalApps === 0; + const totalApps = apps.length + composeApps.length; + const emptyApps = totalApps === 0; - if (emptyApps) { - res.status(200).json({ message: "No apps to deploy" }); - return; - } - res.status(200).json({ message: `Deployed ${totalApps} apps` }); - } catch (error) { - res.status(400).json({ message: "Error To Deploy Application", error }); - } + if (emptyApps) { + res.status(200).json({ message: "No apps to deploy" }); + return; + } + res.status(200).json({ message: `Deployed ${totalApps} apps` }); + } catch (error) { + res.status(400).json({ message: "Error To Deploy Application", error }); + } + } else if (req.headers["x-github-event"] === "pull_request") { + const prId = githubBody?.pull_request?.id; + if (githubBody?.action === "closed") { + const previewDeploymentResult = + await findPreviewDeploymentsByPullRequestId(prId); + + if (previewDeploymentResult.length > 0) { + for (const previewDeployment of previewDeploymentResult) { + try { + await removePreviewDeployment( + previewDeployment.previewDeploymentId, + ); + } catch (error) { + console.log(error); + } + } + } + res.status(200).json({ message: "Preview Deployment Closed" }); + return; + } + // opened or synchronize or reopened + const repository = githubBody?.repository?.name; + const deploymentHash = githubBody?.pull_request?.head?.sha; + const branch = githubBody?.pull_request?.base?.ref; + + const apps = await db.query.applications.findMany({ + where: and( + eq(applications.sourceType, "github"), + eq(applications.autoDeploy, true), + eq(applications.repository, repository), + eq(applications.branch, branch), + eq(applications.isPreviewDeploymentsActive, true), + ), + with: { + previewDeployments: true, + }, + }); + + const prBranch = githubBody?.pull_request?.head?.ref; + + const prNumber = githubBody?.pull_request?.number; + const prTitle = githubBody?.pull_request?.title; + const prURL = githubBody?.pull_request?.html_url; + + for (const app of apps) { + const previewLimit = app?.previewLimit || 0; + if (app?.previewDeployments?.length > previewLimit) { + continue; + } + const previewDeploymentResult = + await findPreviewDeploymentByApplicationId(app.applicationId, prId); + + let previewDeploymentId = + previewDeploymentResult?.previewDeploymentId || ""; + + if (!previewDeploymentResult) { + const previewDeployment = await createPreviewDeployment({ + applicationId: app.applicationId as string, + branch: prBranch, + pullRequestId: prId, + pullRequestNumber: prNumber, + pullRequestTitle: prTitle, + pullRequestURL: prURL, + }); + previewDeploymentId = previewDeployment.previewDeploymentId; + } + + const jobData: DeploymentJob = { + applicationId: app.applicationId as string, + titleLog: "Preview Deployment", + descriptionLog: `Hash: ${deploymentHash}`, + type: "deploy", + applicationType: "application-preview", + server: !!app.serverId, + previewDeploymentId, + }; + + if (IS_CLOUD && app.serverId) { + jobData.serverId = app.serverId; + await deploy(jobData); + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } + return res.status(200).json({ message: "Apps Deployed" }); + } + + return res.status(400).json({ message: "No Actions matched" }); } + +// Genera el dominio random +// Crea el pull request entity +// Crea el deployment entity +// Luego lo que sigue...., unicamente github es soportado por ahora3 diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx index 156e1973a..bcbd4b78b 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx @@ -12,6 +12,7 @@ import { ShowDomains } from "@/components/dashboard/application/domains/show-dom import { ShowEnvironment } from "@/components/dashboard/application/environment/show"; import { ShowGeneralApplication } from "@/components/dashboard/application/general/show"; import { ShowDockerLogs } from "@/components/dashboard/application/logs/show"; +import { ShowPreviewDeployments } from "@/components/dashboard/application/preview-deployments/show-preview-deployments"; import { UpdateApplication } from "@/components/dashboard/application/update-application"; import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show"; import { ProjectLayout } from "@/components/layouts/project-layout"; @@ -51,7 +52,8 @@ type TabState = | "advanced" | "deployments" | "domains" - | "monitoring"; + | "monitoring" + | "preview-deployments"; const Service = ( props: InferGetServerSidePropsType, @@ -191,8 +193,8 @@ const Service = (
General @@ -202,6 +204,9 @@ const Service = ( )} Logs Deployments + + Preview Deployments + Domains Advanced @@ -244,6 +249,11 @@ const Service = (
+ +
+ +
+
diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts index 1b67d3507..85eb9763e 100644 --- a/apps/dokploy/server/api/root.ts +++ b/apps/dokploy/server/api/root.ts @@ -31,6 +31,7 @@ import { settingsRouter } from "./routers/settings"; import { sshRouter } from "./routers/ssh-key"; import { stripeRouter } from "./routers/stripe"; import { userRouter } from "./routers/user"; +import { previewDeploymentRouter } from "./routers/preview-deployment"; /** * This is the primary router for your server. @@ -55,6 +56,7 @@ export const appRouter = createTRPCRouter({ destination: destinationRouter, backup: backupRouter, deployment: deploymentRouter, + previewDeployment: previewDeploymentRouter, mounts: mountRouter, certificates: certificateRouter, settings: settingsRouter, diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts index 3c94fc933..b0a1000c5 100644 --- a/apps/dokploy/server/api/routers/domain.ts +++ b/apps/dokploy/server/api/routers/domain.ts @@ -13,6 +13,7 @@ import { findDomainById, findDomainsByApplicationId, findDomainsByComposeId, + findPreviewDeploymentById, generateTraefikMeDomain, manageDomain, removeDomain, @@ -108,12 +109,33 @@ export const domainRouter = createTRPCRouter({ message: "You are not authorized to access this compose", }); } + } else if (currentDomain.previewDeploymentId) { + const newPreviewDeployment = await findPreviewDeploymentById( + currentDomain.previewDeploymentId, + ); + if ( + newPreviewDeployment.application.project.adminId !== ctx.user.adminId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this preview deployment", + }); + } } const result = await updateDomainById(input.domainId, input); const domain = await findDomainById(input.domainId); if (domain.applicationId) { const application = await findApplicationById(domain.applicationId); await manageDomain(application, domain); + } else if (domain.previewDeploymentId) { + const previewDeployment = await findPreviewDeploymentById( + domain.previewDeploymentId, + ); + const application = await findApplicationById( + previewDeployment.applicationId, + ); + application.appName = previewDeployment.appName; + await manageDomain(application, domain); } return result; }), diff --git a/apps/dokploy/server/api/routers/preview-deployment.ts b/apps/dokploy/server/api/routers/preview-deployment.ts new file mode 100644 index 000000000..74b8461ae --- /dev/null +++ b/apps/dokploy/server/api/routers/preview-deployment.ts @@ -0,0 +1,54 @@ +import { apiFindAllByApplication } from "@/server/db/schema"; +import { + findApplicationById, + findPreviewDeploymentById, + findPreviewDeploymentsByApplicationId, + removePreviewDeployment, +} from "@dokploy/server"; +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import { createTRPCRouter, protectedProcedure } from "../trpc"; + +export const previewDeploymentRouter = createTRPCRouter({ + all: protectedProcedure + .input(apiFindAllByApplication) + .query(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this application", + }); + } + return await findPreviewDeploymentsByApplicationId(input.applicationId); + }), + delete: protectedProcedure + .input(z.object({ previewDeploymentId: z.string() })) + .mutation(async ({ input, ctx }) => { + const previewDeployment = await findPreviewDeploymentById( + input.previewDeploymentId, + ); + if (previewDeployment.application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this preview deployment", + }); + } + await removePreviewDeployment(input.previewDeploymentId); + return true; + }), + one: protectedProcedure + .input(z.object({ previewDeploymentId: z.string() })) + .query(async ({ input, ctx }) => { + const previewDeployment = await findPreviewDeploymentById( + input.previewDeploymentId, + ); + if (previewDeployment.application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this preview deployment", + }); + } + return previewDeployment; + }), +}); diff --git a/apps/dokploy/server/queues/deployments-queue.ts b/apps/dokploy/server/queues/deployments-queue.ts index 08e0c9a12..9ff8a1573 100644 --- a/apps/dokploy/server/queues/deployments-queue.ts +++ b/apps/dokploy/server/queues/deployments-queue.ts @@ -1,14 +1,17 @@ import { deployApplication, deployCompose, + deployPreviewApplication, deployRemoteApplication, deployRemoteCompose, + deployRemotePreviewApplication, rebuildApplication, rebuildCompose, rebuildRemoteApplication, rebuildRemoteCompose, updateApplicationStatus, updateCompose, + updatePreviewDeployment, } from "@dokploy/server"; import { type Job, Worker } from "bullmq"; import type { DeploymentJob } from "./queue-types"; @@ -18,8 +21,11 @@ export const deploymentWorker = new Worker( "deployments", async (job: Job) => { try { + console.log(job.data); + if (job.data.applicationType === "application") { await updateApplicationStatus(job.data.applicationId, "running"); + if (job.data.server) { if (job.data.type === "redeploy") { await rebuildRemoteApplication({ @@ -83,6 +89,29 @@ export const deploymentWorker = new Worker( }); } } + } else if (job.data.applicationType === "application-preview") { + await updatePreviewDeployment(job.data.previewDeploymentId, { + previewStatus: "running", + }); + if (job.data.server) { + if (job.data.type === "deploy") { + await deployRemotePreviewApplication({ + applicationId: job.data.applicationId, + titleLog: job.data.titleLog, + descriptionLog: job.data.descriptionLog, + previewDeploymentId: job.data.previewDeploymentId, + }); + } + } else { + if (job.data.type === "deploy") { + await deployPreviewApplication({ + applicationId: job.data.applicationId, + titleLog: job.data.titleLog, + descriptionLog: job.data.descriptionLog, + previewDeploymentId: job.data.previewDeploymentId, + }); + } + } } } catch (error) { console.log("Error", error); diff --git a/apps/dokploy/server/queues/queue-types.ts b/apps/dokploy/server/queues/queue-types.ts index f467836a2..ef8df6943 100644 --- a/apps/dokploy/server/queues/queue-types.ts +++ b/apps/dokploy/server/queues/queue-types.ts @@ -16,6 +16,16 @@ type DeployJob = type: "deploy" | "redeploy"; applicationType: "compose"; serverId?: string; + } + | { + applicationId: string; + titleLog: string; + descriptionLog: string; + server?: boolean; + type: "deploy"; + applicationType: "application-preview"; + previewDeploymentId: string; + serverId?: string; }; export type DeploymentJob = DeployJob; diff --git a/apps/dokploy/server/server.ts b/apps/dokploy/server/server.ts index b65446f8b..b13e5df5f 100644 --- a/apps/dokploy/server/server.ts +++ b/apps/dokploy/server/server.ts @@ -24,7 +24,7 @@ import { setupTerminalWebSocketServer } from "./wss/terminal"; config({ path: ".env" }); const PORT = Number.parseInt(process.env.PORT || "3000", 10); const dev = process.env.NODE_ENV !== "production"; -const app = next({ dev, turbopack: dev }); +const app = next({ dev, turbopack: process.env.TURBOPACK === "1" }); const handle = app.getRequestHandler(); void app.prepare().then(async () => { try { diff --git a/package.json b/package.json index 94d176a62..f520707db 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "dokploy:setup": "pnpm --filter=dokploy run setup", "dokploy:dev": "pnpm --filter=dokploy run dev", + "dokploy:dev:turbopack": "pnpm --filter=dokploy run dev-turbopack", "dokploy:build": "pnpm --filter=dokploy run build", "dokploy:start": "pnpm --filter=dokploy run start", "test": "pnpm --filter=dokploy run test", diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts index 3afc96845..382a2a0ac 100644 --- a/packages/server/src/db/schema/application.ts +++ b/packages/server/src/db/schema/application.ts @@ -22,9 +22,10 @@ import { redirects } from "./redirects"; import { registry } from "./registry"; import { security } from "./security"; import { server } from "./server"; -import { applicationStatus } from "./shared"; +import { applicationStatus, certificateType } from "./shared"; import { sshKeys } from "./ssh-key"; import { generateAppName } from "./utils"; +import { previewDeployments } from "./preview-deployments"; export const sourceType = pgEnum("sourceType", [ "docker", @@ -114,6 +115,19 @@ export const applications = pgTable("application", { .unique(), description: text("description"), env: text("env"), + previewEnv: text("previewEnv"), + previewBuildArgs: text("previewBuildArgs"), + previewWildcard: text("previewWildcard"), + previewPort: integer("previewPort").default(3000), + previewHttps: boolean("previewHttps").notNull().default(false), + previewPath: text("previewPath").default("/"), + previewCertificateType: certificateType("certificateType") + .notNull() + .default("none"), + previewLimit: integer("previewLimit").default(3), + isPreviewDeploymentsActive: boolean("isPreviewDeploymentsActive").default( + false, + ), buildArgs: text("buildArgs"), memoryReservation: integer("memoryReservation"), memoryLimit: integer("memoryLimit"), @@ -239,6 +253,7 @@ export const applicationsRelations = relations( fields: [applications.serverId], references: [server.serverId], }), + previewDeployments: many(previewDeployments), }), ); @@ -348,6 +363,7 @@ const createSchema = createInsertSchema(applications, { subtitle: z.string().optional(), dockerImage: z.string().optional(), username: z.string().optional(), + isPreviewDeploymentsActive: z.boolean().optional(), password: z.string().optional(), registryUrl: z.string().optional(), customGitSSHKeyId: z.string().optional(), @@ -378,6 +394,14 @@ const createSchema = createInsertSchema(applications, { modeSwarm: ServiceModeSwarmSchema.nullable(), labelsSwarm: LabelsSwarmSchema.nullable(), networkSwarm: NetworkSwarmSchema.nullable(), + previewPort: z.number().optional(), + previewEnv: z.string().optional(), + previewBuildArgs: z.string().optional(), + previewWildcard: z.string().optional(), + previewLimit: z.number().optional(), + previewHttps: z.boolean().optional(), + previewPath: z.string().optional(), + previewCertificateType: z.enum(["letsencrypt", "none"]).optional(), }); export const apiCreateApplication = createSchema.pick({ diff --git a/packages/server/src/db/schema/deployment.ts b/packages/server/src/db/schema/deployment.ts index db9838f05..f79b48ee9 100644 --- a/packages/server/src/db/schema/deployment.ts +++ b/packages/server/src/db/schema/deployment.ts @@ -1,11 +1,18 @@ -import { relations } from "drizzle-orm"; -import { pgEnum, pgTable, text } from "drizzle-orm/pg-core"; +import { is, relations } from "drizzle-orm"; +import { + type AnyPgColumn, + boolean, + pgEnum, + pgTable, + text, +} from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; import { nanoid } from "nanoid"; import { z } from "zod"; import { applications } from "./application"; import { compose } from "./compose"; import { server } from "./server"; +import { previewDeployments } from "./preview-deployments"; export const deploymentStatus = pgEnum("deploymentStatus", [ "running", @@ -32,6 +39,11 @@ export const deployments = pgTable("deployment", { serverId: text("serverId").references(() => server.serverId, { onDelete: "cascade", }), + isPreviewDeployment: boolean("isPreviewDeployment").default(false), + previewDeploymentId: text("previewDeploymentId").references( + (): AnyPgColumn => previewDeployments.previewDeploymentId, + { onDelete: "cascade" }, + ), createdAt: text("createdAt") .notNull() .$defaultFn(() => new Date().toISOString()), @@ -50,6 +62,10 @@ export const deploymentsRelations = relations(deployments, ({ one }) => ({ fields: [deployments.serverId], references: [server.serverId], }), + previewDeployment: one(previewDeployments, { + fields: [deployments.previewDeploymentId], + references: [previewDeployments.previewDeploymentId], + }), })); const schema = createInsertSchema(deployments, { @@ -59,6 +75,7 @@ const schema = createInsertSchema(deployments, { applicationId: z.string(), composeId: z.string(), description: z.string().optional(), + previewDeploymentId: z.string(), }); export const apiCreateDeployment = schema @@ -68,11 +85,24 @@ export const apiCreateDeployment = schema logPath: true, applicationId: true, description: true, + previewDeploymentId: true, }) .extend({ applicationId: z.string().min(1), }); +export const apiCreateDeploymentPreview = schema + .pick({ + title: true, + status: true, + logPath: true, + description: true, + previewDeploymentId: true, + }) + .extend({ + previewDeploymentId: z.string().min(1), + }); + export const apiCreateDeploymentCompose = schema .pick({ title: true, diff --git a/packages/server/src/db/schema/domain.ts b/packages/server/src/db/schema/domain.ts index 288291189..7c7696891 100644 --- a/packages/server/src/db/schema/domain.ts +++ b/packages/server/src/db/schema/domain.ts @@ -1,5 +1,6 @@ import { relations } from "drizzle-orm"; import { + type AnyPgColumn, boolean, integer, pgEnum, @@ -14,8 +15,13 @@ import { domain } from "../validations/domain"; import { applications } from "./application"; import { compose } from "./compose"; import { certificateType } from "./shared"; +import { previewDeployments } from "./preview-deployments"; -export const domainType = pgEnum("domainType", ["compose", "application"]); +export const domainType = pgEnum("domainType", [ + "compose", + "application", + "preview", +]); export const domains = pgTable("domain", { domainId: text("domainId") @@ -28,6 +34,7 @@ export const domains = pgTable("domain", { path: text("path").default("/"), serviceName: text("serviceName"), domainType: domainType("domainType").default("application"), + isPreviewDeployment: boolean("isPreviewDeployment").default(false), // TODO: remove uniqueConfigKey: serial("uniqueConfigKey"), createdAt: text("createdAt") .notNull() @@ -39,6 +46,10 @@ export const domains = pgTable("domain", { () => applications.applicationId, { onDelete: "cascade" }, ), + previewDeploymentId: text("previewDeploymentId").references( + (): AnyPgColumn => previewDeployments.previewDeploymentId, + { onDelete: "cascade" }, + ), certificateType: certificateType("certificateType").notNull().default("none"), }); @@ -51,6 +62,10 @@ export const domainsRelations = relations(domains, ({ one }) => ({ fields: [domains.composeId], references: [compose.composeId], }), + previewDeployment: one(previewDeployments, { + fields: [domains.previewDeploymentId], + references: [previewDeployments.previewDeploymentId], + }), })); const createSchema = createInsertSchema(domains, domain._def.schema.shape); @@ -65,6 +80,7 @@ export const apiCreateDomain = createSchema.pick({ composeId: true, serviceName: true, domainType: true, + previewDeploymentId: true, }); export const apiFindDomain = createSchema diff --git a/packages/server/src/db/schema/index.ts b/packages/server/src/db/schema/index.ts index 4a6103688..f07a18707 100644 --- a/packages/server/src/db/schema/index.ts +++ b/packages/server/src/db/schema/index.ts @@ -29,3 +29,4 @@ export * from "./github"; export * from "./gitlab"; export * from "./server"; export * from "./utils"; +export * from "./preview-deployments"; \ No newline at end of file diff --git a/packages/server/src/db/schema/preview-deployments.ts b/packages/server/src/db/schema/preview-deployments.ts new file mode 100644 index 000000000..5d0671e8d --- /dev/null +++ b/packages/server/src/db/schema/preview-deployments.ts @@ -0,0 +1,74 @@ +import { relations } from "drizzle-orm"; +import { pgTable, text } from "drizzle-orm/pg-core"; +import { nanoid } from "nanoid"; +import { applications } from "./application"; +import { domains } from "./domain"; +import { deployments } from "./deployment"; +import { createInsertSchema } from "drizzle-zod"; +import { z } from "zod"; +import { generateAppName } from "./utils"; +import { applicationStatus } from "./shared"; + +export const previewDeployments = pgTable("preview_deployments", { + previewDeploymentId: text("previewDeploymentId") + .notNull() + .primaryKey() + .$defaultFn(() => nanoid()), + branch: text("branch").notNull(), + pullRequestId: text("pullRequestId").notNull(), + pullRequestNumber: text("pullRequestNumber").notNull(), + pullRequestURL: text("pullRequestURL").notNull(), + pullRequestTitle: text("pullRequestTitle").notNull(), + pullRequestCommentId: text("pullRequestCommentId").notNull(), + previewStatus: applicationStatus("previewStatus").notNull().default("idle"), + appName: text("appName") + .notNull() + .$defaultFn(() => generateAppName("preview")) + .unique(), + applicationId: text("applicationId") + .notNull() + .references(() => applications.applicationId, { + onDelete: "cascade", + }), + domainId: text("domainId").references(() => domains.domainId, { + onDelete: "cascade", + }), + createdAt: text("createdAt") + .notNull() + .$defaultFn(() => new Date().toISOString()), + expiresAt: text("expiresAt"), +}); + +export const previewDeploymentsRelations = relations( + previewDeployments, + ({ one, many }) => ({ + deployments: many(deployments), + domain: one(domains, { + fields: [previewDeployments.domainId], + references: [domains.domainId], + }), + application: one(applications, { + fields: [previewDeployments.applicationId], + references: [applications.applicationId], + }), + }), +); + +export const createSchema = createInsertSchema(previewDeployments, { + applicationId: z.string(), +}); + +export const apiCreatePreviewDeployment = createSchema + .pick({ + applicationId: true, + domainId: true, + branch: true, + pullRequestId: true, + pullRequestNumber: true, + pullRequestURL: true, + pullRequestTitle: true, + }) + .extend({ + applicationId: z.string().min(1), + // deploymentId: z.string().min(1), + }); diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 12f3b64e5..b8ec30e24 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -20,6 +20,7 @@ export * from "./services/mount"; export * from "./services/certificate"; export * from "./services/redirect"; export * from "./services/security"; +export * from "./services/preview-deployment"; export * from "./services/port"; export * from "./services/redis"; export * from "./services/compose"; diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts index ae8a9b76c..ba36dcec3 100644 --- a/packages/server/src/services/application.ts +++ b/packages/server/src/services/application.ts @@ -28,6 +28,7 @@ import { getCustomGitCloneCommand, } from "@dokploy/server/utils/providers/git"; import { + authGithub, cloneGithubRepository, getGithubCloneCommand, } from "@dokploy/server/utils/providers/github"; @@ -40,8 +41,18 @@ import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { encodeBase64 } from "../utils/docker/utils"; import { getDokployUrl } from "./admin"; -import { createDeployment, updateDeploymentStatus } from "./deployment"; +import { + createDeployment, + createDeploymentPreview, + updateDeploymentStatus, +} from "./deployment"; import { validUniqueServerAppName } from "./project"; +import { + findPreviewDeploymentById, + updatePreviewDeployment, +} from "./preview-deployment"; +import { getIssueComment, updateIssueComment } from "./github"; +import { type Domain, getDomainHost } from "./domain"; export type Application = typeof applications.$inferSelect; export const createApplication = async ( @@ -100,6 +111,7 @@ export const findApplicationById = async (applicationId: string) => { github: true, bitbucket: true, server: true, + previewDeployments: true, }, }); if (!application) { @@ -168,7 +180,10 @@ export const deployApplication = async ({ try { if (application.sourceType === "github") { - await cloneGithubRepository(application, deployment.logPath); + await cloneGithubRepository({ + ...application, + logPath: deployment.logPath, + }); await buildApplication(application, deployment.logPath); } else if (application.sourceType === "gitlab") { await cloneGitlabRepository(application, deployment.logPath); @@ -276,7 +291,11 @@ export const deployRemoteApplication = async ({ if (application.serverId) { let command = "set -e;"; if (application.sourceType === "github") { - command += await getGithubCloneCommand(application, deployment.logPath); + command += await getGithubCloneCommand({ + ...application, + serverId: application.serverId, + logPath: deployment.logPath, + }); } else if (application.sourceType === "gitlab") { command += await getGitlabCloneCommand(application, deployment.logPath); } else if (application.sourceType === "bitbucket") { @@ -348,6 +367,183 @@ export const deployRemoteApplication = async ({ return true; }; +export const deployPreviewApplication = async ({ + applicationId, + titleLog = "Preview Deployment", + descriptionLog = "", + previewDeploymentId, +}: { + applicationId: string; + titleLog: string; + descriptionLog: string; + previewDeploymentId: string; +}) => { + const application = await findApplicationById(applicationId); + const deployment = await createDeploymentPreview({ + title: titleLog, + description: descriptionLog, + previewDeploymentId: previewDeploymentId, + }); + + const previewDeployment = + await findPreviewDeploymentById(previewDeploymentId); + + await updatePreviewDeployment(previewDeploymentId, { + createdAt: new Date().toISOString(), + }); + + const previewDomain = getDomainHost(previewDeployment?.domain as Domain); + const issueParams = { + owner: application?.owner || "", + repository: application?.repository || "", + issue_number: previewDeployment.pullRequestNumber, + comment_id: Number.parseInt(previewDeployment.pullRequestCommentId), + githubId: application?.githubId || "", + }; + try { + const buildingComment = getIssueComment( + application.name, + "running", + previewDomain, + ); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${buildingComment}`, + }); + application.appName = previewDeployment.appName; + application.env = application.previewEnv; + application.buildArgs = application.previewBuildArgs; + + if (application.sourceType === "github") { + await cloneGithubRepository({ + ...application, + appName: previewDeployment.appName, + branch: previewDeployment.branch, + logPath: deployment.logPath, + }); + await buildApplication(application, deployment.logPath); + } + // 4eef09efc46009187d668cf1c25f768d0bde4f91 + const successComment = getIssueComment( + application.name, + "success", + previewDomain, + ); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${successComment}`, + }); + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updatePreviewDeployment(previewDeploymentId, { + previewStatus: "done", + }); + } catch (error) { + const comment = getIssueComment(application.name, "error", previewDomain); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${comment}`, + }); + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updatePreviewDeployment(previewDeploymentId, { + previewStatus: "error", + }); + throw error; + } + + return true; +}; + +export const deployRemotePreviewApplication = async ({ + applicationId, + titleLog = "Preview Deployment", + descriptionLog = "", + previewDeploymentId, +}: { + applicationId: string; + titleLog: string; + descriptionLog: string; + previewDeploymentId: string; +}) => { + const application = await findApplicationById(applicationId); + const deployment = await createDeploymentPreview({ + title: titleLog, + description: descriptionLog, + previewDeploymentId: previewDeploymentId, + }); + + const previewDeployment = + await findPreviewDeploymentById(previewDeploymentId); + + await updatePreviewDeployment(previewDeploymentId, { + createdAt: new Date().toISOString(), + }); + + const previewDomain = getDomainHost(previewDeployment?.domain as Domain); + const issueParams = { + owner: application?.owner || "", + repository: application?.repository || "", + issue_number: previewDeployment.pullRequestNumber, + comment_id: Number.parseInt(previewDeployment.pullRequestCommentId), + githubId: application?.githubId || "", + }; + try { + const buildingComment = getIssueComment( + application.name, + "running", + previewDomain, + ); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${buildingComment}`, + }); + application.appName = previewDeployment.appName; + application.env = application.previewEnv; + application.buildArgs = application.previewBuildArgs; + + if (application.serverId) { + let command = "set -e;"; + if (application.sourceType === "github") { + command += await getGithubCloneCommand({ + ...application, + serverId: application.serverId, + logPath: deployment.logPath, + }); + } + + command += getBuildCommand(application, deployment.logPath); + await execAsyncRemote(application.serverId, command); + await mechanizeDockerContainer(application); + } + + const successComment = getIssueComment( + application.name, + "success", + previewDomain, + ); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${successComment}`, + }); + await updateDeploymentStatus(deployment.deploymentId, "done"); + await updatePreviewDeployment(previewDeploymentId, { + previewStatus: "done", + }); + } catch (error) { + const comment = getIssueComment(application.name, "error", previewDomain); + await updateIssueComment({ + ...issueParams, + body: `### Dokploy Preview Deployment\n\n${comment}`, + }); + await updateDeploymentStatus(deployment.deploymentId, "error"); + await updatePreviewDeployment(previewDeploymentId, { + previewStatus: "error", + }); + throw error; + } + + return true; +}; + export const rebuildRemoteApplication = async ({ applicationId, titleLog = "Rebuild deployment", diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 63d29539d..a12fc7d27 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -214,7 +214,11 @@ export const deployCompose = async ({ try { if (compose.sourceType === "github") { - await cloneGithubRepository(compose, deployment.logPath, true); + await cloneGithubRepository({ + ...compose, + logPath: deployment.logPath, + type: "compose", + }); } else if (compose.sourceType === "gitlab") { await cloneGitlabRepository(compose, deployment.logPath, true); } else if (compose.sourceType === "bitbucket") { @@ -314,11 +318,12 @@ export const deployRemoteCompose = async ({ let command = "set -e;"; if (compose.sourceType === "github") { - command += await getGithubCloneCommand( - compose, - deployment.logPath, - true, - ); + command += await getGithubCloneCommand({ + ...compose, + logPath: deployment.logPath, + type: "compose", + serverId: compose.serverId, + }); } else if (compose.sourceType === "gitlab") { command += await getGitlabCloneCommand( compose, diff --git a/packages/server/src/services/deployment.ts b/packages/server/src/services/deployment.ts index 63f3cf23f..b18b132de 100644 --- a/packages/server/src/services/deployment.ts +++ b/packages/server/src/services/deployment.ts @@ -5,13 +5,14 @@ import { db } from "@dokploy/server/db"; import { type apiCreateDeployment, type apiCreateDeploymentCompose, + type apiCreateDeploymentPreview, type apiCreateDeploymentServer, deployments, } from "@dokploy/server/db/schema"; import { removeDirectoryIfExistsContent } from "@dokploy/server/utils/filesystem/directory"; import { TRPCError } from "@trpc/server"; import { format } from "date-fns"; -import { desc, eq } from "drizzle-orm"; +import { and, desc, eq, isNull } from "drizzle-orm"; import { type Application, findApplicationById, @@ -21,6 +22,11 @@ import { type Compose, findComposeById, updateCompose } from "./compose"; import { type Server, findServerById } from "./server"; import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync"; +import { + findPreviewDeploymentById, + type PreviewDeployment, + updatePreviewDeployment, +} from "./preview-deployment"; export type Deployment = typeof deployments.$inferSelect; @@ -101,6 +107,74 @@ export const createDeployment = async ( } }; +export const createDeploymentPreview = async ( + deployment: Omit< + typeof apiCreateDeploymentPreview._type, + "deploymentId" | "createdAt" | "status" | "logPath" + >, +) => { + const previewDeployment = await findPreviewDeploymentById( + deployment.previewDeploymentId, + ); + try { + await removeLastTenPreviewDeploymenById( + deployment.previewDeploymentId, + previewDeployment?.application?.serverId, + ); + + const appName = `${previewDeployment.appName}`; + const { LOGS_PATH } = paths(!!previewDeployment?.application?.serverId); + const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss"); + const fileName = `${appName}-${formattedDateTime}.log`; + const logFilePath = path.join(LOGS_PATH, appName, fileName); + + if (previewDeployment?.application?.serverId) { + const server = await findServerById( + previewDeployment?.application?.serverId, + ); + + const command = ` + mkdir -p ${LOGS_PATH}/${appName}; + echo "Initializing deployment" >> ${logFilePath}; + `; + + await execAsyncRemote(server.serverId, command); + } else { + await fsPromises.mkdir(path.join(LOGS_PATH, appName), { + recursive: true, + }); + await fsPromises.writeFile(logFilePath, "Initializing deployment"); + } + + const deploymentCreate = await db + .insert(deployments) + .values({ + title: deployment.title || "Deployment", + status: "running", + logPath: logFilePath, + description: deployment.description || "", + previewDeploymentId: deployment.previewDeploymentId, + }) + .returning(); + if (deploymentCreate.length === 0 || !deploymentCreate[0]) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error to create the deployment", + }); + } + return deploymentCreate[0]; + } catch (error) { + await updatePreviewDeployment(deployment.previewDeploymentId, { + previewStatus: "error", + }); + console.log(error); + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error to create the deployment", + }); + } +}; + export const createDeploymentCompose = async ( deployment: Omit< typeof apiCreateDeploymentCompose._type, @@ -257,6 +331,41 @@ const removeLastTenComposeDeployments = async ( } }; +export const removeLastTenPreviewDeploymenById = async ( + previewDeploymentId: string, + serverId: string | null, +) => { + const deploymentList = await db.query.deployments.findMany({ + where: eq(deployments.previewDeploymentId, previewDeploymentId), + orderBy: desc(deployments.createdAt), + }); + + if (deploymentList.length > 10) { + const deploymentsToDelete = deploymentList.slice(10); + if (serverId) { + let command = ""; + for (const oldDeployment of deploymentsToDelete) { + const logPath = path.join(oldDeployment.logPath); + + command += ` + rm -rf ${logPath}; + `; + await removeDeployment(oldDeployment.deploymentId); + } + + await execAsyncRemote(serverId, command); + } else { + for (const oldDeployment of deploymentsToDelete) { + const logPath = path.join(oldDeployment.logPath); + if (existsSync(logPath)) { + await fsPromises.unlink(logPath); + } + await removeDeployment(oldDeployment.deploymentId); + } + } + } +}; + export const removeDeployments = async (application: Application) => { const { appName, applicationId } = application; const { LOGS_PATH } = paths(!!application.serverId); @@ -269,6 +378,30 @@ export const removeDeployments = async (application: Application) => { await removeDeploymentsByApplicationId(applicationId); }; +export const removeDeploymentsByPreviewDeploymentId = async ( + previewDeployment: PreviewDeployment, + serverId: string | null, +) => { + const { appName } = previewDeployment; + const { LOGS_PATH } = paths(!!serverId); + const logsPath = path.join(LOGS_PATH, appName); + if (serverId) { + await execAsyncRemote(serverId, `rm -rf ${logsPath}`); + } else { + await removeDirectoryIfExistsContent(logsPath); + } + + await db + .delete(deployments) + .where( + eq( + deployments.previewDeploymentId, + previewDeployment.previewDeploymentId, + ), + ) + .returning(); +}; + export const removeDeploymentsByComposeId = async (compose: Compose) => { const { appName } = compose; const { LOGS_PATH } = paths(!!compose.serverId); diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts index d5a40fe03..6ac613542 100644 --- a/packages/server/src/services/docker.ts +++ b/packages/server/src/services/docker.ts @@ -110,7 +110,7 @@ export const getContainersByAppNameMatch = async ( const command = appType === "docker-compose" ? `${cmd} --filter='label=com.docker.compose.project=${appName}'` - : `${cmd} | grep ${appName}`; + : `${cmd} | grep '^.*Name: ${appName}'`; if (serverId) { const { stdout, stderr } = await execAsyncRemote(serverId, command); diff --git a/packages/server/src/services/domain.ts b/packages/server/src/services/domain.ts index 28dd3ba24..b99c4869d 100644 --- a/packages/server/src/services/domain.ts +++ b/packages/server/src/services/domain.ts @@ -134,3 +134,7 @@ export const removeDomainById = async (domainId: string) => { return result[0]; }; + +export const getDomainHost = (domain: Domain) => { + return `${domain.https ? "https" : "http"}://${domain.host}`; +}; diff --git a/packages/server/src/services/github.ts b/packages/server/src/services/github.ts index a7317bc72..17ec7d720 100644 --- a/packages/server/src/services/github.ts +++ b/packages/server/src/services/github.ts @@ -6,6 +6,7 @@ import { } from "@dokploy/server/db/schema"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; +import { authGithub } from "../utils/providers/github"; export type Github = typeof github.$inferSelect; export const createGithub = async ( @@ -72,3 +73,56 @@ export const updateGithub = async ( .returning() .then((response) => response[0]); }; + +export const getIssueComment = ( + appName: string, + status: "success" | "error" | "running" | "initializing", + previewDomain: string, +) => { + let statusMessage = ""; + if (status === "success") { + statusMessage = "✅ Done"; + } else if (status === "error") { + statusMessage = "❌ Failed"; + } else if (status === "initializing") { + statusMessage = "🔄 Building"; + } else { + statusMessage = "🔄 Building"; + } + const finished = ` +| Name | Status | Preview | Updated (UTC) | +|------------|--------------|-------------------------------------|-----------------------| +| ${appName} | ${statusMessage} | [Preview URL](${previewDomain}) | ${new Date().toISOString()} | +`; + + return finished; +}; + +interface Comment { + owner: string; + repository: string; + issue_number: string; + body: string; + comment_id: number; + githubId: string; +} + +export const updateIssueComment = async ({ + owner, + repository, + issue_number, + body, + comment_id, + githubId, +}: Comment) => { + const github = await findGithubById(githubId); + const octokit = authGithub(github); + + await octokit.rest.issues.updateComment({ + owner: owner || "", + repo: repository || "", + issue_number: issue_number, + body, + comment_id: comment_id, + }); +}; diff --git a/packages/server/src/services/preview-deployment.ts b/packages/server/src/services/preview-deployment.ts new file mode 100644 index 000000000..59788adc6 --- /dev/null +++ b/packages/server/src/services/preview-deployment.ts @@ -0,0 +1,285 @@ +import { db } from "@dokploy/server/db"; +import { + type apiCreatePreviewDeployment, + deployments, + previewDeployments, +} from "@dokploy/server/db/schema"; +import { TRPCError } from "@trpc/server"; +import { and, desc, eq } from "drizzle-orm"; +import { slugify } from "../setup/server-setup"; +import { findApplicationById } from "./application"; +import { createDomain } from "./domain"; +import { generatePassword, generateRandomDomain } from "../templates/utils"; +import { manageDomain } from "../utils/traefik/domain"; +import { + removeDeployments, + removeDeploymentsByPreviewDeploymentId, +} from "./deployment"; +import { removeDirectoryCode } from "../utils/filesystem/directory"; +import { removeTraefikConfig } from "../utils/traefik/application"; +import { removeService } from "../utils/docker/utils"; +import { authGithub } from "../utils/providers/github"; +import { getIssueComment, type Github } from "./github"; +import { findAdminById } from "./admin"; + +export type PreviewDeployment = typeof previewDeployments.$inferSelect; + +export const findPreviewDeploymentById = async ( + previewDeploymentId: string, +) => { + const application = await db.query.previewDeployments.findFirst({ + where: eq(previewDeployments.previewDeploymentId, previewDeploymentId), + with: { + domain: true, + application: { + with: { + server: true, + project: true, + }, + }, + }, + }); + if (!application) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Preview Deployment not found", + }); + } + return application; +}; + +export const findApplicationByPreview = async (applicationId: string) => { + const application = await db.query.applications.findFirst({ + with: { + previewDeployments: { + where: eq(previewDeployments.applicationId, applicationId), + }, + project: true, + domains: true, + deployments: true, + mounts: true, + redirects: true, + security: true, + ports: true, + registry: true, + gitlab: true, + github: true, + bitbucket: true, + server: true, + }, + }); + + if (!application) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Applicationnot found", + }); + } + return application; +}; + +export const removePreviewDeployment = async (previewDeploymentId: string) => { + try { + const application = await findApplicationByPreview(previewDeploymentId); + const previewDeployment = + await findPreviewDeploymentById(previewDeploymentId); + + const deployment = await db + .delete(previewDeployments) + .where(eq(previewDeployments.previewDeploymentId, previewDeploymentId)) + .returning(); + + application.appName = previewDeployment.appName; + const cleanupOperations = [ + async () => + await removeDeploymentsByPreviewDeploymentId( + previewDeployment, + application.serverId, + ), + async () => + await removeDirectoryCode(application.appName, application.serverId), + async () => + await removeTraefikConfig(application.appName, application.serverId), + async () => + await removeService(application?.appName, application.serverId), + ]; + for (const operation of cleanupOperations) { + try { + await operation(); + } catch (error) {} + } + return deployment[0]; + } catch (error) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error to delete this preview deployment", + }); + } +}; +// testing-tesoitnmg-ddq0ul-preview-ihl44o +export const updatePreviewDeployment = async ( + previewDeploymentId: string, + previewDeploymentData: Partial, +) => { + const application = await db + .update(previewDeployments) + .set({ + ...previewDeploymentData, + }) + .where(eq(previewDeployments.previewDeploymentId, previewDeploymentId)) + .returning(); + + return application; +}; + +export const findPreviewDeploymentsByApplicationId = async ( + applicationId: string, +) => { + const deploymentsList = await db.query.previewDeployments.findMany({ + where: eq(previewDeployments.applicationId, applicationId), + orderBy: desc(previewDeployments.createdAt), + with: { + deployments: { + orderBy: desc(deployments.createdAt), + }, + domain: true, + }, + }); + return deploymentsList; +}; + +export const createPreviewDeployment = async ( + schema: typeof apiCreatePreviewDeployment._type, +) => { + const application = await findApplicationById(schema.applicationId); + const appName = `preview-${application.appName}-${generatePassword(6)}`; + + const generateDomain = await generateWildcardDomain( + application.previewWildcard || "*.traefik.me", + appName, + application.server?.ipAddress || "", + application.project.adminId, + ); + + const octokit = authGithub(application?.github as Github); + + const runningComment = getIssueComment( + application.name, + "initializing", + generateDomain, + ); + + const issue = await octokit.rest.issues.createComment({ + owner: application?.owner || "", + repo: application?.repository || "", + issue_number: Number.parseInt(schema.pullRequestNumber), + body: `### Dokploy Preview Deployment\n\n${runningComment}`, + }); + + const previewDeployment = await db + .insert(previewDeployments) + .values({ + ...schema, + appName: appName, + pullRequestCommentId: `${issue.data.id}`, + }) + .returning() + .then((value) => value[0]); + + if (!previewDeployment) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error to create the preview deployment", + }); + } + + const newDomain = await createDomain({ + host: generateDomain, + path: application.previewPath, + port: application.previewPort, + https: application.previewHttps, + certificateType: application.previewCertificateType, + domainType: "preview", + previewDeploymentId: previewDeployment.previewDeploymentId, + }); + + application.appName = appName; + + console.log(application); + + await manageDomain(application, newDomain); + + await db + .update(previewDeployments) + .set({ + domainId: newDomain.domainId, + }) + .where( + eq( + previewDeployments.previewDeploymentId, + previewDeployment.previewDeploymentId, + ), + ); + + return previewDeployment; +}; + +export const findPreviewDeploymentsByPullRequestId = async ( + pullRequestId: string, +) => { + const previewDeploymentResult = await db.query.previewDeployments.findMany({ + where: eq(previewDeployments.pullRequestId, pullRequestId), + }); + + return previewDeploymentResult; +}; + +export const findPreviewDeploymentByApplicationId = async ( + applicationId: string, + pullRequestId: string, +) => { + const previewDeploymentResult = await db.query.previewDeployments.findFirst({ + where: and( + eq(previewDeployments.applicationId, applicationId), + eq(previewDeployments.pullRequestId, pullRequestId), + ), + }); + + return previewDeploymentResult; +}; + +const generateWildcardDomain = async ( + baseDomain: string, + appName: string, + serverIp: string, + adminId: string, +): Promise => { + if (!baseDomain.startsWith("*.")) { + throw new Error('The base domain must start with "*."'); + } + const hash = `${appName}`; + if (baseDomain.includes("traefik.me")) { + let ip = ""; + + if (process.env.NODE_ENV === "development") { + ip = "127.0.0.1"; + } + + if (serverIp) { + ip = serverIp; + } + + if (!ip) { + const admin = await findAdminById(adminId); + ip = admin?.serverIp || ""; + } + + const slugIp = ip.replaceAll(".", "-"); + return baseDomain.replace( + "*", + `${hash}${slugIp === "" ? "" : `-${slugIp}`}`, + ); + } + + return baseDomain.replace("*", hash); +}; diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts index 702121d2d..1cdc9787c 100644 --- a/packages/server/src/utils/builders/index.ts +++ b/packages/server/src/utils/builders/index.ts @@ -17,6 +17,7 @@ import { buildHeroku, getHerokuCommand } from "./heroku"; import { buildNixpacks, getNixpacksCommand } from "./nixpacks"; import { buildPaketo, getPaketoCommand } from "./paketo"; import { buildStatic, getStaticCommand } from "./static"; +import { nanoid } from "nanoid"; // NIXPACKS codeDirectory = where is the path of the code directory // HEROKU codeDirectory = where is the path of the code directory @@ -33,6 +34,7 @@ export type ApplicationNested = InferResultType< project: true; } >; + export const buildApplication = async ( application: ApplicationNested, logPath: string, diff --git a/packages/server/src/utils/builders/nixpacks.ts b/packages/server/src/utils/builders/nixpacks.ts index 7c10e4c07..56560e4e2 100644 --- a/packages/server/src/utils/builders/nixpacks.ts +++ b/packages/server/src/utils/builders/nixpacks.ts @@ -14,7 +14,7 @@ export const buildNixpacks = async ( application: ApplicationNested, writeStream: WriteStream, ) => { - const { env, appName, publishDirectory, serverId } = application; + const { env, appName, publishDirectory } = application; const buildAppDirectory = getBuildAppDirectory(application); const buildContainerId = `${appName}-${nanoid(10)}`; diff --git a/packages/server/src/utils/providers/github.ts b/packages/server/src/utils/providers/github.ts index a63a822df..c366eeba5 100644 --- a/packages/server/src/utils/providers/github.ts +++ b/packages/server/src/utils/providers/github.ts @@ -74,11 +74,22 @@ export type ApplicationWithGithub = InferResultType< >; export type ComposeWithGithub = InferResultType<"compose", { github: true }>; -export const cloneGithubRepository = async ( - entity: ApplicationWithGithub | ComposeWithGithub, - logPath: string, - isCompose = false, -) => { + +interface CloneGithubRepository { + appName: string; + owner: string | null; + branch: string | null; + githubId: string | null; + repository: string | null; + logPath: string; + type?: "application" | "compose"; +} +export const cloneGithubRepository = async ({ + logPath, + type = "application", + ...entity +}: CloneGithubRepository) => { + const isCompose = type === "compose"; const { APPLICATIONS_PATH, COMPOSE_PATH } = paths(); const writeStream = createWriteStream(logPath, { flags: "a" }); const { appName, repository, owner, branch, githubId } = entity; @@ -145,13 +156,13 @@ export const cloneGithubRepository = async ( } }; -export const getGithubCloneCommand = async ( - entity: ApplicationWithGithub | ComposeWithGithub, - logPath: string, - isCompose = false, -) => { +export const getGithubCloneCommand = async ({ + logPath, + type = "application", + ...entity +}: CloneGithubRepository & { serverId: string }) => { const { appName, repository, owner, branch, githubId, serverId } = entity; - + const isCompose = type === "compose"; if (!serverId) { throw new TRPCError({ code: "NOT_FOUND", From fd0a472468cce82b235a9b869927a5b62c910d25 Mon Sep 17 00:00:00 2001 From: 190km Date: Tue, 3 Dec 2024 02:20:20 +0100 Subject: [PATCH 203/243] feat/fix: fixed stop button & added start button --- .../dashboard/compose/general/actions.tsx | 6 +- .../dashboard/compose/start-compose.tsx | 65 +++++ .../dashboard/compose/stop-compose.tsx | 65 +++++ apps/dokploy/pages/api/deploy/github.ts | 230 +++++++++--------- apps/dokploy/server/api/routers/compose.ts | 15 ++ packages/server/src/services/compose.ts | 30 +++ 6 files changed, 295 insertions(+), 116 deletions(-) create mode 100644 apps/dokploy/components/dashboard/compose/start-compose.tsx create mode 100644 apps/dokploy/components/dashboard/compose/stop-compose.tsx diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx index 365e37f51..140b8d96b 100644 --- a/apps/dokploy/components/dashboard/compose/general/actions.tsx +++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx @@ -14,6 +14,7 @@ import { CheckCircle2, ExternalLink, Globe, Terminal } from "lucide-react"; import Link from "next/link"; import { toast } from "sonner"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; +import { StartCompose } from "../start-compose"; import { DeployCompose } from "./deploy-compose"; import { RedbuildCompose } from "./rebuild-compose"; import { StopCompose } from "./stop-compose"; @@ -71,7 +72,10 @@ export const ComposeActions = ({ composeId }: Props) => { Autodeploy {data?.autoDeploy && } - {data?.composeType === "docker-compose" && ( + {data?.composeType === "docker-compose" && + data?.composeStatus === "idle" ? ( + + ) : ( )} diff --git a/apps/dokploy/components/dashboard/compose/start-compose.tsx b/apps/dokploy/components/dashboard/compose/start-compose.tsx new file mode 100644 index 000000000..20f990bb3 --- /dev/null +++ b/apps/dokploy/components/dashboard/compose/start-compose.tsx @@ -0,0 +1,65 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { api } from "@/utils/api"; +import { CheckCircle2 } from "lucide-react"; +import { toast } from "sonner"; + +interface Props { + composeId: string; +} + +export const StartCompose = ({ composeId }: Props) => { + const { mutateAsync, isLoading } = api.compose.start.useMutation(); + const utils = api.useUtils(); + return ( + + + + + + + + Are you sure to start the compose? + + + This will start the compose + + + + Cancel + { + await mutateAsync({ + composeId, + }) + .then(async () => { + await utils.compose.one.invalidate({ + composeId, + }); + toast.success("Compose started succesfully"); + }) + .catch(() => { + toast.error("Error to start the Compose"); + }); + }} + > + Confirm + + + + + ); +}; diff --git a/apps/dokploy/components/dashboard/compose/stop-compose.tsx b/apps/dokploy/components/dashboard/compose/stop-compose.tsx new file mode 100644 index 000000000..3080e755a --- /dev/null +++ b/apps/dokploy/components/dashboard/compose/stop-compose.tsx @@ -0,0 +1,65 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { api } from "@/utils/api"; +import { Ban } from "lucide-react"; +import { toast } from "sonner"; + +interface Props { + composeId: string; +} + +export const StopCompose = ({ composeId }: Props) => { + const { mutateAsync, isLoading } = api.compose.stop.useMutation(); + const utils = api.useUtils(); + return ( + + + + + + + + Are you absolutely sure to stop the compose? + + + This will stop the compose + + + + Cancel + { + await mutateAsync({ + composeId, + }) + .then(async () => { + await utils.compose.one.invalidate({ + composeId, + }); + toast.success("Compose stopped succesfully"); + }) + .catch(() => { + toast.error("Error to stop the Compose"); + }); + }} + > + Confirm + + + + + ); +}; diff --git a/apps/dokploy/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts index 1d8c094af..08589fae1 100644 --- a/apps/dokploy/pages/api/deploy/github.ts +++ b/apps/dokploy/pages/api/deploy/github.ts @@ -10,138 +10,138 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { extractCommitMessage, extractHash } from "./[refreshToken]"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse + req: NextApiRequest, + res: NextApiResponse, ) { - const signature = req.headers["x-hub-signature-256"]; - const githubBody = req.body; + const signature = req.headers["x-hub-signature-256"]; + const githubBody = req.body; - if (!githubBody?.installation?.id) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } + if (!githubBody?.installation?.id) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } - const githubResult = await db.query.github.findFirst({ - where: eq(github.githubInstallationId, githubBody.installation.id), - }); + const githubResult = await db.query.github.findFirst({ + where: eq(github.githubInstallationId, githubBody.installation.id), + }); - if (!githubResult) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } + if (!githubResult) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } - if (!githubResult.githubWebhookSecret) { - res.status(400).json({ message: "Github Webhook Secret not set" }); - return; - } - const webhooks = new Webhooks({ - secret: githubResult.githubWebhookSecret, - }); + if (!githubResult.githubWebhookSecret) { + res.status(400).json({ message: "Github Webhook Secret not set" }); + return; + } + const webhooks = new Webhooks({ + secret: githubResult.githubWebhookSecret, + }); - const verified = await webhooks.verify( - JSON.stringify(githubBody), - signature as string - ); + const verified = await webhooks.verify( + JSON.stringify(githubBody), + signature as string, + ); - if (!verified) { - res.status(401).json({ message: "Unauthorized" }); - return; - } + if (!verified) { + res.status(401).json({ message: "Unauthorized" }); + return; + } - if (req.headers["x-github-event"] === "ping") { - res.status(200).json({ message: "Ping received, webhook is active" }); - return; - } + if (req.headers["x-github-event"] === "ping") { + res.status(200).json({ message: "Ping received, webhook is active" }); + return; + } - if (req.headers["x-github-event"] !== "push") { - res.status(400).json({ message: "We only accept push events" }); - return; - } + if (req.headers["x-github-event"] !== "push") { + res.status(400).json({ message: "We only accept push events" }); + return; + } - try { - const branchName = githubBody?.ref?.replace("refs/heads/", ""); - const repository = githubBody?.repository?.name; - const deploymentTitle = extractCommitMessage(req.headers, req.body); - const deploymentHash = extractHash(req.headers, req.body); + try { + const branchName = githubBody?.ref?.replace("refs/heads/", ""); + const repository = githubBody?.repository?.name; + const deploymentTitle = extractCommitMessage(req.headers, req.body); + const deploymentHash = extractHash(req.headers, req.body); - const apps = await db.query.applications.findMany({ - where: and( - eq(applications.sourceType, "github"), - eq(applications.autoDeploy, true), - eq(applications.branch, branchName), - eq(applications.repository, repository) - ), - }); + const apps = await db.query.applications.findMany({ + where: and( + eq(applications.sourceType, "github"), + eq(applications.autoDeploy, true), + eq(applications.branch, branchName), + eq(applications.repository, repository), + ), + }); - for (const app of apps) { - const jobData: DeploymentJob = { - applicationId: app.applicationId as string, - titleLog: deploymentTitle, - descriptionLog: `Hash: ${deploymentHash}`, - type: "deploy", - applicationType: "application", - server: !!app.serverId, - }; + for (const app of apps) { + const jobData: DeploymentJob = { + applicationId: app.applicationId as string, + titleLog: deploymentTitle, + descriptionLog: `Hash: ${deploymentHash}`, + type: "deploy", + applicationType: "application", + server: !!app.serverId, + }; - if (IS_CLOUD && app.serverId) { - jobData.serverId = app.serverId; - await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - } + if (IS_CLOUD && app.serverId) { + jobData.serverId = app.serverId; + await deploy(jobData); + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } - const composeApps = await db.query.compose.findMany({ - where: and( - eq(compose.sourceType, "github"), - eq(compose.autoDeploy, true), - eq(compose.branch, branchName), - eq(compose.repository, repository) - ), - }); + const composeApps = await db.query.compose.findMany({ + where: and( + eq(compose.sourceType, "github"), + eq(compose.autoDeploy, true), + eq(compose.branch, branchName), + eq(compose.repository, repository), + ), + }); - for (const composeApp of composeApps) { - const jobData: DeploymentJob = { - composeId: composeApp.composeId as string, - titleLog: deploymentTitle, - type: "deploy", - applicationType: "compose", - descriptionLog: `Hash: ${deploymentHash}`, - server: !!composeApp.serverId, - }; + for (const composeApp of composeApps) { + const jobData: DeploymentJob = { + composeId: composeApp.composeId as string, + titleLog: deploymentTitle, + type: "deploy", + applicationType: "compose", + descriptionLog: `Hash: ${deploymentHash}`, + server: !!composeApp.serverId, + }; - if (IS_CLOUD && composeApp.serverId) { - jobData.serverId = composeApp.serverId; - await deploy(jobData); - return true; - } + if (IS_CLOUD && composeApp.serverId) { + jobData.serverId = composeApp.serverId; + await deploy(jobData); + return true; + } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } - const totalApps = apps.length + composeApps.length; - const emptyApps = totalApps === 0; + const totalApps = apps.length + composeApps.length; + const emptyApps = totalApps === 0; - if (emptyApps) { - res.status(200).json({ message: "No apps to deploy" }); - return; - } - res.status(200).json({ message: `Deployed ${totalApps} apps` }); - } catch (error) { - res.status(400).json({ message: "Error To Deploy Application", error }); - } + if (emptyApps) { + res.status(200).json({ message: "No apps to deploy" }); + return; + } + res.status(200).json({ message: `Deployed ${totalApps} apps` }); + } catch (error) { + res.status(400).json({ message: "Error To Deploy Application", error }); + } } diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 257f374f2..6d04e815f 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -48,6 +48,7 @@ import { removeCompose, removeComposeDirectory, removeDeploymentsByComposeId, + startCompose, stopCompose, updateCompose, } from "@dokploy/server"; @@ -309,6 +310,20 @@ export const composeRouter = createTRPCRouter({ } await stopCompose(input.composeId); + return true; + }), + start: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this compose", + }); + } + await startCompose(input.composeId); + return true; }), getDefaultCommand: protectedProcedure diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 63d29539d..604c2150e 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -463,6 +463,36 @@ export const removeCompose = async (compose: Compose) => { return true; }; +export const startCompose = async (composeId: string) => { + const compose = await findComposeById(composeId); + try { + const { COMPOSE_PATH } = paths(!!compose.serverId); + if (compose.composeType === "docker-compose") { + if (compose.serverId) { + await execAsyncRemote( + compose.serverId, + `cd ${join(COMPOSE_PATH, compose.appName, "code")} && docker compose -p ${compose.appName} up -d`, + ); + } else { + await execAsync(`docker compose -p ${compose.appName} up -d`, { + cwd: join(COMPOSE_PATH, compose.appName, "code"), + }); + } + } + + await updateCompose(composeId, { + composeStatus: "done", + }); + } catch (error) { + await updateCompose(composeId, { + composeStatus: "idle", + }); + throw error; + } + + return true; +}; + export const stopCompose = async (composeId: string) => { const compose = await findComposeById(composeId); try { From 40c97b8e9cf4433e3fa9b5beed992a26ea22d6fe Mon Sep 17 00:00:00 2001 From: usopp Date: Tue, 3 Dec 2024 02:42:19 +0100 Subject: [PATCH 204/243] Update github.ts --- apps/dokploy/pages/api/deploy/github.ts | 230 ++++++++++++------------ 1 file changed, 115 insertions(+), 115 deletions(-) diff --git a/apps/dokploy/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts index 08589fae1..1d8c094af 100644 --- a/apps/dokploy/pages/api/deploy/github.ts +++ b/apps/dokploy/pages/api/deploy/github.ts @@ -10,138 +10,138 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { extractCommitMessage, extractHash } from "./[refreshToken]"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse, + req: NextApiRequest, + res: NextApiResponse ) { - const signature = req.headers["x-hub-signature-256"]; - const githubBody = req.body; + const signature = req.headers["x-hub-signature-256"]; + const githubBody = req.body; - if (!githubBody?.installation?.id) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } + if (!githubBody?.installation?.id) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } - const githubResult = await db.query.github.findFirst({ - where: eq(github.githubInstallationId, githubBody.installation.id), - }); + const githubResult = await db.query.github.findFirst({ + where: eq(github.githubInstallationId, githubBody.installation.id), + }); - if (!githubResult) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } + if (!githubResult) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } - if (!githubResult.githubWebhookSecret) { - res.status(400).json({ message: "Github Webhook Secret not set" }); - return; - } - const webhooks = new Webhooks({ - secret: githubResult.githubWebhookSecret, - }); + if (!githubResult.githubWebhookSecret) { + res.status(400).json({ message: "Github Webhook Secret not set" }); + return; + } + const webhooks = new Webhooks({ + secret: githubResult.githubWebhookSecret, + }); - const verified = await webhooks.verify( - JSON.stringify(githubBody), - signature as string, - ); + const verified = await webhooks.verify( + JSON.stringify(githubBody), + signature as string + ); - if (!verified) { - res.status(401).json({ message: "Unauthorized" }); - return; - } + if (!verified) { + res.status(401).json({ message: "Unauthorized" }); + return; + } - if (req.headers["x-github-event"] === "ping") { - res.status(200).json({ message: "Ping received, webhook is active" }); - return; - } + if (req.headers["x-github-event"] === "ping") { + res.status(200).json({ message: "Ping received, webhook is active" }); + return; + } - if (req.headers["x-github-event"] !== "push") { - res.status(400).json({ message: "We only accept push events" }); - return; - } + if (req.headers["x-github-event"] !== "push") { + res.status(400).json({ message: "We only accept push events" }); + return; + } - try { - const branchName = githubBody?.ref?.replace("refs/heads/", ""); - const repository = githubBody?.repository?.name; - const deploymentTitle = extractCommitMessage(req.headers, req.body); - const deploymentHash = extractHash(req.headers, req.body); + try { + const branchName = githubBody?.ref?.replace("refs/heads/", ""); + const repository = githubBody?.repository?.name; + const deploymentTitle = extractCommitMessage(req.headers, req.body); + const deploymentHash = extractHash(req.headers, req.body); - const apps = await db.query.applications.findMany({ - where: and( - eq(applications.sourceType, "github"), - eq(applications.autoDeploy, true), - eq(applications.branch, branchName), - eq(applications.repository, repository), - ), - }); + const apps = await db.query.applications.findMany({ + where: and( + eq(applications.sourceType, "github"), + eq(applications.autoDeploy, true), + eq(applications.branch, branchName), + eq(applications.repository, repository) + ), + }); - for (const app of apps) { - const jobData: DeploymentJob = { - applicationId: app.applicationId as string, - titleLog: deploymentTitle, - descriptionLog: `Hash: ${deploymentHash}`, - type: "deploy", - applicationType: "application", - server: !!app.serverId, - }; + for (const app of apps) { + const jobData: DeploymentJob = { + applicationId: app.applicationId as string, + titleLog: deploymentTitle, + descriptionLog: `Hash: ${deploymentHash}`, + type: "deploy", + applicationType: "application", + server: !!app.serverId, + }; - if (IS_CLOUD && app.serverId) { - jobData.serverId = app.serverId; - await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - }, - ); - } + if (IS_CLOUD && app.serverId) { + jobData.serverId = app.serverId; + await deploy(jobData); + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + } + ); + } - const composeApps = await db.query.compose.findMany({ - where: and( - eq(compose.sourceType, "github"), - eq(compose.autoDeploy, true), - eq(compose.branch, branchName), - eq(compose.repository, repository), - ), - }); + const composeApps = await db.query.compose.findMany({ + where: and( + eq(compose.sourceType, "github"), + eq(compose.autoDeploy, true), + eq(compose.branch, branchName), + eq(compose.repository, repository) + ), + }); - for (const composeApp of composeApps) { - const jobData: DeploymentJob = { - composeId: composeApp.composeId as string, - titleLog: deploymentTitle, - type: "deploy", - applicationType: "compose", - descriptionLog: `Hash: ${deploymentHash}`, - server: !!composeApp.serverId, - }; + for (const composeApp of composeApps) { + const jobData: DeploymentJob = { + composeId: composeApp.composeId as string, + titleLog: deploymentTitle, + type: "deploy", + applicationType: "compose", + descriptionLog: `Hash: ${deploymentHash}`, + server: !!composeApp.serverId, + }; - if (IS_CLOUD && composeApp.serverId) { - jobData.serverId = composeApp.serverId; - await deploy(jobData); - return true; - } + if (IS_CLOUD && composeApp.serverId) { + jobData.serverId = composeApp.serverId; + await deploy(jobData); + return true; + } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - }, - ); - } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + } + ); + } - const totalApps = apps.length + composeApps.length; - const emptyApps = totalApps === 0; + const totalApps = apps.length + composeApps.length; + const emptyApps = totalApps === 0; - if (emptyApps) { - res.status(200).json({ message: "No apps to deploy" }); - return; - } - res.status(200).json({ message: `Deployed ${totalApps} apps` }); - } catch (error) { - res.status(400).json({ message: "Error To Deploy Application", error }); - } + if (emptyApps) { + res.status(200).json({ message: "No apps to deploy" }); + return; + } + res.status(200).json({ message: `Deployed ${totalApps} apps` }); + } catch (error) { + res.status(400).json({ message: "Error To Deploy Application", error }); + } } From d8787ec11d5c0780491f28f053068abdc776ec7f Mon Sep 17 00:00:00 2001 From: 190km Date: Tue, 3 Dec 2024 03:55:56 +0100 Subject: [PATCH 205/243] style: added autodeploy switch --- .../dashboard/application/general/show.tsx | 46 +++++++++---------- .../dashboard/compose/general/actions.tsx | 43 ++++++++--------- apps/dokploy/components/ui/switch.tsx | 6 ++- 3 files changed, 49 insertions(+), 46 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/general/show.tsx b/apps/dokploy/components/dashboard/application/general/show.tsx index 277ae1ebb..65c99e9b8 100644 --- a/apps/dokploy/components/dashboard/application/general/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/show.tsx @@ -2,9 +2,9 @@ import { ShowBuildChooseForm } from "@/components/dashboard/application/build/sh import { ShowProviderForm } from "@/components/dashboard/application/general/generic/show"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Toggle } from "@/components/ui/toggle"; +import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; -import { CheckCircle2, Terminal } from "lucide-react"; +import { Terminal } from "lucide-react"; import React from "react"; import { toast } from "sonner"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; @@ -39,27 +39,6 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => { appName={data?.appName || ""} /> - { - await update({ - applicationId, - autoDeploy: enabled, - }) - .then(async () => { - toast.success("Auto Deploy Updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error to update Auto Deploy"); - }); - }} - className="flex flex-row gap-2 items-center" - > - Autodeploy - {data?.autoDeploy && } - {data?.applicationStatus === "idle" ? ( @@ -75,6 +54,27 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => { Open Terminal +
+ Autodeploy + { + await update({ + applicationId, + autoDeploy: enabled, + }) + .then(async () => { + toast.success("Auto Deploy Updated"); + await refetch(); + }) + .catch(() => { + toast.error("Error to update Auto Deploy"); + }); + }} + className="flex flex-row gap-2 items-center" + /> +
diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx index 365e37f51..1531942f8 100644 --- a/apps/dokploy/components/dashboard/compose/general/actions.tsx +++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx @@ -8,7 +8,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Toggle } from "@/components/ui/toggle"; +import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; import { CheckCircle2, ExternalLink, Globe, Terminal } from "lucide-react"; import Link from "next/link"; @@ -50,26 +50,6 @@ export const ComposeActions = ({ composeId }: Props) => { return (
- { - await update({ - composeId, - autoDeploy: enabled, - }) - .then(async () => { - toast.success("Auto Deploy Updated"); - await refetch(); - }) - .catch(() => { - toast.error("Error to update Auto Deploy"); - }); - }} - className="flex flex-row gap-2 items-center" - > - Autodeploy {data?.autoDeploy && } - {data?.composeType === "docker-compose" && ( @@ -84,6 +64,27 @@ export const ComposeActions = ({ composeId }: Props) => { Open Terminal +
+ Autodeploy + { + await update({ + composeId, + autoDeploy: enabled, + }) + .then(async () => { + toast.success("Auto Deploy Updated"); + await refetch(); + }) + .catch(() => { + toast.error("Error to update Auto Deploy"); + }); + }} + className="flex flex-row gap-2 items-center" + /> +
{domains.length > 0 && ( diff --git a/apps/dokploy/components/ui/switch.tsx b/apps/dokploy/components/ui/switch.tsx index 96809024e..21cb85826 100644 --- a/apps/dokploy/components/ui/switch.tsx +++ b/apps/dokploy/components/ui/switch.tsx @@ -1,3 +1,5 @@ +"use client"; + import * as SwitchPrimitives from "@radix-ui/react-switch"; import * as React from "react"; @@ -9,7 +11,7 @@ const Switch = React.forwardRef< >(({ className, ...props }, ref) => ( From c4c4b459cc423597e770fb75c4f605501345d780 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:26:52 -0600 Subject: [PATCH 206/243] fix: allow multiple repositories from same name github #690 --- apps/dokploy/pages/api/deploy/github.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts index 1d8c094af..7f0e7f0b0 100644 --- a/apps/dokploy/pages/api/deploy/github.ts +++ b/apps/dokploy/pages/api/deploy/github.ts @@ -63,13 +63,16 @@ export default async function handler( const repository = githubBody?.repository?.name; const deploymentTitle = extractCommitMessage(req.headers, req.body); const deploymentHash = extractHash(req.headers, req.body); + const owner = githubBody?.repository?.owner?.name; + const apps = await db.query.applications.findMany({ where: and( eq(applications.sourceType, "github"), eq(applications.autoDeploy, true), eq(applications.branch, branchName), - eq(applications.repository, repository) + eq(applications.repository, repository), + eq(applications.owner, owner) ), }); @@ -103,7 +106,8 @@ export default async function handler( eq(compose.sourceType, "github"), eq(compose.autoDeploy, true), eq(compose.branch, branchName), - eq(compose.repository, repository) + eq(compose.repository, repository), + eq(compose.owner, owner) ), }); From 00c7ae3f407c125e5ef75793ae060e1089d0521c Mon Sep 17 00:00:00 2001 From: Daniel Gietmann Date: Tue, 3 Dec 2024 20:48:46 +0100 Subject: [PATCH 207/243] Updated Umami to v2.14.0 --- apps/dokploy/templates/templates.ts | 2 +- apps/dokploy/templates/umami/docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 6b7c5d6c7..878d01763 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -380,7 +380,7 @@ export const templates: TemplateData[] = [ { id: "umami", name: "Umami", - version: "v2.12.1", + version: "v2.14.0", description: "Umami is a simple, fast, privacy-focused alternative to Google Analytics.", logo: "umami.png", diff --git a/apps/dokploy/templates/umami/docker-compose.yml b/apps/dokploy/templates/umami/docker-compose.yml index 228762100..191c4803d 100644 --- a/apps/dokploy/templates/umami/docker-compose.yml +++ b/apps/dokploy/templates/umami/docker-compose.yml @@ -1,6 +1,6 @@ services: umami: - image: ghcr.io/umami-software/umami:postgresql-v2.13.2 + image: ghcr.io/umami-software/umami:postgresql-v2.14.0 restart: always healthcheck: test: ["CMD-SHELL", "curl http://localhost:3000/api/heartbeat"] From cbbbe44802157be0c89455c070e162f2670d8e7d Mon Sep 17 00:00:00 2001 From: Pedro Ramon Date: Wed, 4 Dec 2024 07:39:36 -0300 Subject: [PATCH 208/243] feat(i18n): add portuguese language support --- .../dashboard/settings/appearance-form.tsx | 3 +- apps/dokploy/next-i18next.config.cjs | 1 + apps/dokploy/pages/_app.tsx | 1 + apps/dokploy/public/locales/pt-br/common.json | 1 + .../public/locales/pt-br/settings.json | 44 +++++++++++++++++++ apps/dokploy/utils/hooks/use-locale.ts | 1 + 6 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 apps/dokploy/public/locales/pt-br/common.json create mode 100644 apps/dokploy/public/locales/pt-br/settings.json diff --git a/apps/dokploy/components/dashboard/settings/appearance-form.tsx b/apps/dokploy/components/dashboard/settings/appearance-form.tsx index 2a3f5132b..e9edbfe10 100644 --- a/apps/dokploy/components/dashboard/settings/appearance-form.tsx +++ b/apps/dokploy/components/dashboard/settings/appearance-form.tsx @@ -38,7 +38,7 @@ const appearanceFormSchema = z.object({ required_error: "Please select a theme.", }), language: z.enum( - ["en", "pl", "ru", "fr", "de", "tr", "zh-Hant", "zh-Hans", "fa", "ko"], + ["en", "pl", "ru", "fr", "de", "tr", "zh-Hant", "zh-Hans", "fa", "ko", "pt-br"], { required_error: "Please select a language.", }, @@ -186,6 +186,7 @@ export function AppearanceForm() { { label: "Türkçe", value: "tr" }, { label: "Persian", value: "fa" }, { label: "한국어", value: "ko" }, + { label: "Português", value: "pt-br" }, ].map((preset) => ( {preset.label} diff --git a/apps/dokploy/next-i18next.config.cjs b/apps/dokploy/next-i18next.config.cjs index adf741e91..ac4a5d06d 100644 --- a/apps/dokploy/next-i18next.config.cjs +++ b/apps/dokploy/next-i18next.config.cjs @@ -13,6 +13,7 @@ module.exports = { "zh-Hans", "fa", "ko", + "pt-br", ], localeDetection: false, }, diff --git a/apps/dokploy/pages/_app.tsx b/apps/dokploy/pages/_app.tsx index 17b986c57..d7034c97b 100644 --- a/apps/dokploy/pages/_app.tsx +++ b/apps/dokploy/pages/_app.tsx @@ -82,6 +82,7 @@ export default api.withTRPC( "zh-Hans", "fa", "ko", + "pt-br", ], localeDetection: false, }, diff --git a/apps/dokploy/public/locales/pt-br/common.json b/apps/dokploy/public/locales/pt-br/common.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/apps/dokploy/public/locales/pt-br/common.json @@ -0,0 +1 @@ +{} diff --git a/apps/dokploy/public/locales/pt-br/settings.json b/apps/dokploy/public/locales/pt-br/settings.json new file mode 100644 index 000000000..fc964be6c --- /dev/null +++ b/apps/dokploy/public/locales/pt-br/settings.json @@ -0,0 +1,44 @@ +{ + "settings.common.save": "Salvar", + "settings.server.domain.title": "Domínio do Servidor", + "settings.server.domain.description": "Configure o domínio do servidor", + "settings.server.domain.form.domain": "Domínio", + "settings.server.domain.form.letsEncryptEmail": "Email do Let's Encrypt", + "settings.server.domain.form.certificate.label": "Certificado", + "settings.server.domain.form.certificate.placeholder": "Selecione um Certificado", + "settings.server.domain.form.certificateOptions.none": "Nenhum", + "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Padrão)", + + "settings.server.webServer.title": "Servidor web", + "settings.server.webServer.description": "Limpar e recarregar servidor web.", + "settings.server.webServer.actions": "Ações", + "settings.server.webServer.reload": "Recarregar", + "settings.server.webServer.watchLogs": "Ver logs", + "settings.server.webServer.updateServerIp": "Atualizar IP do Servidor", + "settings.server.webServer.server.label": "Servidor", + "settings.server.webServer.traefik.label": "Traefik", + "settings.server.webServer.traefik.modifyEnv": "Alterar Env", + "settings.server.webServer.storage.label": "Armazenamento", + "settings.server.webServer.storage.cleanUnusedImages": "Limpar imagens não utilizadas", + "settings.server.webServer.storage.cleanUnusedVolumes": "Limpar volumes não utilizados", + "settings.server.webServer.storage.cleanStoppedContainers": "Limpar containers parados", + "settings.server.webServer.storage.cleanDockerBuilder": "Limpar Docker Builder & System", + "settings.server.webServer.storage.cleanMonitoring": "Limpar Monitoramento", + "settings.server.webServer.storage.cleanAll": "Limpar Tudo", + + "settings.profile.title": "Conta", + "settings.profile.description": "Altere os detalhes do seu perfil aqui.", + "settings.profile.email": "Email", + "settings.profile.password": "Senha", + "settings.profile.avatar": "Avatar", + + "settings.appearance.title": "Aparência", + "settings.appearance.description": "Personalize o tema do seu dashboard.", + "settings.appearance.theme": "Tema", + "settings.appearance.themeDescription": "Selecione um tema para o dashboard", + "settings.appearance.themes.light": "Claro", + "settings.appearance.themes.dark": "Escuro", + "settings.appearance.themes.system": "Automático", + "settings.appearance.language": "Linguagem", + "settings.appearance.languageDescription": "Selecione o idioma do dashboard" +} diff --git a/apps/dokploy/utils/hooks/use-locale.ts b/apps/dokploy/utils/hooks/use-locale.ts index eaeb1612f..5a2ec44b0 100644 --- a/apps/dokploy/utils/hooks/use-locale.ts +++ b/apps/dokploy/utils/hooks/use-locale.ts @@ -11,6 +11,7 @@ const SUPPORTED_LOCALES = [ "zh-Hans", "fa", "ko", + "pt-br", ] as const; type Locale = (typeof SUPPORTED_LOCALES)[number]; From 14573f90f7a052fb399da0c9c339249b1786a1c1 Mon Sep 17 00:00:00 2001 From: Shahriar <31452340+ShahriarKh@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:21:07 +0330 Subject: [PATCH 209/243] fix: small typo --- .../dashboard/settings/notifications/add-notification.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/settings/notifications/add-notification.tsx b/apps/dokploy/components/dashboard/settings/notifications/add-notification.tsx index 77621bec8..74a933a69 100644 --- a/apps/dokploy/components/dashboard/settings/notifications/add-notification.tsx +++ b/apps/dokploy/components/dashboard/settings/notifications/add-notification.tsx @@ -667,7 +667,7 @@ export const AddNotification = () => {
Dokploy Restart - Trigger the action when a dokploy is restarted. + Trigger the action when dokploy is restarted.
From c9d36160882abcf21367a0bb94ae552fcfb8c89d Mon Sep 17 00:00:00 2001 From: yerkow Date: Thu, 5 Dec 2024 10:17:54 +0500 Subject: [PATCH 210/243] feat(i18n): add kazakh language support --- .../dashboard/settings/appearance-form.tsx | 2 +- apps/dokploy/next-i18next.config.cjs | 37 +++++++++-------- apps/dokploy/pages/_app.tsx | 1 + apps/dokploy/public/locales/kz/common.json | 1 + apps/dokploy/public/locales/kz/settings.json | 41 +++++++++++++++++++ apps/dokploy/utils/hooks/use-locale.ts | 1 + 6 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 apps/dokploy/public/locales/kz/common.json create mode 100644 apps/dokploy/public/locales/kz/settings.json diff --git a/apps/dokploy/components/dashboard/settings/appearance-form.tsx b/apps/dokploy/components/dashboard/settings/appearance-form.tsx index 2a3f5132b..f128948d7 100644 --- a/apps/dokploy/components/dashboard/settings/appearance-form.tsx +++ b/apps/dokploy/components/dashboard/settings/appearance-form.tsx @@ -38,7 +38,7 @@ const appearanceFormSchema = z.object({ required_error: "Please select a theme.", }), language: z.enum( - ["en", "pl", "ru", "fr", "de", "tr", "zh-Hant", "zh-Hans", "fa", "ko"], + ["en", "pl", "ru", "fr", "de", "tr", "kz", "zh-Hant", "zh-Hans", "fa", "ko"], { required_error: "Please select a language.", }, diff --git a/apps/dokploy/next-i18next.config.cjs b/apps/dokploy/next-i18next.config.cjs index adf741e91..d044c0c11 100644 --- a/apps/dokploy/next-i18next.config.cjs +++ b/apps/dokploy/next-i18next.config.cjs @@ -1,21 +1,22 @@ /** @type {import('next-i18next').UserConfig} */ module.exports = { - i18n: { - defaultLocale: "en", - locales: [ - "en", - "pl", - "ru", - "fr", - "de", - "tr", - "zh-Hant", - "zh-Hans", - "fa", - "ko", - ], - localeDetection: false, - }, - fallbackLng: "en", - keySeparator: false, + i18n: { + defaultLocale: "en", + locales: [ + "en", + "pl", + "ru", + "fr", + "de", + "tr", + "kz", + "zh-Hant", + "zh-Hans", + "fa", + "ko", + ], + localeDetection: false, + }, + fallbackLng: "en", + keySeparator: false, }; diff --git a/apps/dokploy/pages/_app.tsx b/apps/dokploy/pages/_app.tsx index 17b986c57..2f16f8886 100644 --- a/apps/dokploy/pages/_app.tsx +++ b/apps/dokploy/pages/_app.tsx @@ -78,6 +78,7 @@ export default api.withTRPC( "fr", "de", "tr", + "kz", "zh-Hant", "zh-Hans", "fa", diff --git a/apps/dokploy/public/locales/kz/common.json b/apps/dokploy/public/locales/kz/common.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/apps/dokploy/public/locales/kz/common.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/apps/dokploy/public/locales/kz/settings.json b/apps/dokploy/public/locales/kz/settings.json new file mode 100644 index 000000000..97403d0e2 --- /dev/null +++ b/apps/dokploy/public/locales/kz/settings.json @@ -0,0 +1,41 @@ +{ + "settings.common.save": "Сақтау", + "settings.server.domain.title": "Сервер домені", + "settings.server.domain.description": "Dokploy сервер қолданбасына домен енгізіңіз.", + "settings.server.domain.form.domain": "Домен", + "settings.server.domain.form.letsEncryptEmail": "Let's Encrypt Эл. поштасы", + "settings.server.domain.form.certificate.label": "Сертификат", + "settings.server.domain.form.certificate.placeholder": "Сертификатты таңдаңыз", + "settings.server.domain.form.certificateOptions.none": "Жоқ", + "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Стандартты)", + "settings.server.webServer.title": "Веб-Сервер", + "settings.server.webServer.description": "Веб-серверді қайта жүктеу немесе тазалау.", + "settings.server.webServer.actions": "Әрекеттер", + "settings.server.webServer.reload": "Қайта жүктеу", + "settings.server.webServer.watchLogs": "Журналдарды қарау", + "settings.server.webServer.updateServerIp": "Сервердің IP жаңарту", + "settings.server.webServer.server.label": "Сервер", + "settings.server.webServer.traefik.label": "Traefik", + "settings.server.webServer.traefik.modifyEnv": "Env Өзгерту", + "settings.server.webServer.storage.label": "Диск кеңістігі", + "settings.server.webServer.storage.cleanUnusedImages": "Пайдаланылмаған образды тазалау", + "settings.server.webServer.storage.cleanUnusedVolumes": "Пайдаланылмаған томды тазалау", + "settings.server.webServer.storage.cleanStoppedContainers": "Тоқтатылған контейнерлерді тазалау", + "settings.server.webServer.storage.cleanDockerBuilder": "Docker Builder & Системаны тазалау", + "settings.server.webServer.storage.cleanMonitoring": "Мониторингті тазалау", + "settings.server.webServer.storage.cleanAll": "Барлығын тазалау", + "settings.profile.title": "Аккаунт", + "settings.profile.description": "Профиль мәліметтерін осы жерден өзгертіңіз.", + "settings.profile.email": "Эл. пошта", + "settings.profile.password": "Құпия сөз", + "settings.profile.avatar": "Аватар", + "settings.appearance.title": "Сыртқы түрі", + "settings.appearance.description": "Dokploy сыртқы келбетін өзгерту.", + "settings.appearance.theme": "Келбеті", + "settings.appearance.themeDescription": "Жүйе тақтасының келбетің таңдаңыз", + "settings.appearance.themes.light": "Жарық", + "settings.appearance.themes.dark": "Қараңғы", + "settings.appearance.themes.system": "Жүйелік", + "settings.appearance.language": "Тіл", + "settings.appearance.languageDescription": "Жүйе тақтасының тілің таңдаңыз" +} diff --git a/apps/dokploy/utils/hooks/use-locale.ts b/apps/dokploy/utils/hooks/use-locale.ts index eaeb1612f..df30b556c 100644 --- a/apps/dokploy/utils/hooks/use-locale.ts +++ b/apps/dokploy/utils/hooks/use-locale.ts @@ -7,6 +7,7 @@ const SUPPORTED_LOCALES = [ "fr", "de", "tr", + "kz", "zh-Hant", "zh-Hans", "fa", From 27f43e774a3063e35403a84295823afff4568313 Mon Sep 17 00:00:00 2001 From: yerkow Date: Thu, 5 Dec 2024 10:39:10 +0500 Subject: [PATCH 211/243] fix: kz label --- apps/dokploy/components/dashboard/settings/appearance-form.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/dokploy/components/dashboard/settings/appearance-form.tsx b/apps/dokploy/components/dashboard/settings/appearance-form.tsx index f128948d7..85db83772 100644 --- a/apps/dokploy/components/dashboard/settings/appearance-form.tsx +++ b/apps/dokploy/components/dashboard/settings/appearance-form.tsx @@ -184,6 +184,7 @@ export function AppearanceForm() { { label: "繁體中文", value: "zh-Hant" }, { label: "简体中文", value: "zh-Hans" }, { label: "Türkçe", value: "tr" }, + { label: "Қазақ", value: "tr" }, { label: "Persian", value: "fa" }, { label: "한국어", value: "ko" }, ].map((preset) => ( From b9faf4bd1a5607e9649cf1fb35d1d76bc0a3ecd4 Mon Sep 17 00:00:00 2001 From: 190km Date: Thu, 5 Dec 2024 23:39:03 +0100 Subject: [PATCH 212/243] feat: add prevent dialog when leaving a terminal --- .../docker/terminal/docker-terminal-modal.tsx | 120 +++++++---- .../web-server/docker-terminal-modal.tsx | 204 +++++++++++------- 2 files changed, 201 insertions(+), 123 deletions(-) diff --git a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx index 876d6838b..f0f8ca6ad 100644 --- a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx +++ b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx @@ -1,56 +1,94 @@ +import { Button } from "@/components/ui/button"; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, } from "@/components/ui/dialog"; import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; import dynamic from "next/dynamic"; +import { useState } from "react"; const Terminal = dynamic( - () => import("./docker-terminal").then((e) => e.DockerTerminal), - { - ssr: false, - }, + () => import("./docker-terminal").then((e) => e.DockerTerminal), + { + ssr: false, + } ); interface Props { - containerId: string; - serverId?: string; - children?: React.ReactNode; + containerId: string; + serverId?: string; + children?: React.ReactNode; } export const DockerTerminalModal = ({ - children, - containerId, - serverId, + children, + containerId, + serverId, }: Props) => { - return ( - - - e.preventDefault()} - > - {children} - - - - - Docker Terminal - - Easy way to access to docker container - - + const [mainDialogOpen, setMainDialogOpen] = useState(false); + const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); - - - - ); + const handleMainDialogOpenChange = (open: boolean) => { + if (!open) { + setConfirmDialogOpen(true); + } else { + setMainDialogOpen(true); + } + }; + + const handleConfirm = () => { + setConfirmDialogOpen(false); + setMainDialogOpen(false); + }; + + const handleCancel = () => { + setConfirmDialogOpen(false); + }; + return ( + + + e.preventDefault()} + > + {children} + + + + + Docker Terminal + + Easy way to access to docker container + + + + + + + + Are you sure you want to close the terminal? + + By clicking the confirm button, the terminal will be closed. + + + + + + + + + + + ); }; diff --git a/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx b/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx index f38ecf128..ba3253a4a 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx @@ -1,20 +1,22 @@ +import { Button } from "@/components/ui/button"; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, } from "@/components/ui/select"; import { api } from "@/utils/api"; import { Loader2 } from "lucide-react"; @@ -23,80 +25,118 @@ import type React from "react"; import { useEffect, useState } from "react"; const Terminal = dynamic( - () => - import("@/components/dashboard/docker/terminal/docker-terminal").then( - (e) => e.DockerTerminal, - ), - { - ssr: false, - }, + () => + import("@/components/dashboard/docker/terminal/docker-terminal").then( + (e) => e.DockerTerminal + ), + { + ssr: false, + } ); interface Props { - appName: string; - children?: React.ReactNode; - serverId?: string; + appName: string; + children?: React.ReactNode; + serverId?: string; } export const DockerTerminalModal = ({ children, appName, serverId }: Props) => { - const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery( - { - appName, - serverId, - }, - { - enabled: !!appName, - }, - ); - const [containerId, setContainerId] = useState(); + const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery( + { + appName, + serverId, + }, + { + enabled: !!appName, + } + ); + const [containerId, setContainerId] = useState(); + const [mainDialogOpen, setMainDialogOpen] = useState(false); + const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); - useEffect(() => { - if (data && data?.length > 0) { - setContainerId(data[0]?.containerId); - } - }, [data]); - return ( - - {children} - - - Docker Terminal - - Easy way to access to docker container - - - - - - - - ); + const handleMainDialogOpenChange = (open: boolean) => { + if (!open) { + setConfirmDialogOpen(true); + } else { + setMainDialogOpen(true); + } + }; + + const handleConfirm = () => { + setConfirmDialogOpen(false); + setMainDialogOpen(false); + }; + + const handleCancel = () => { + setConfirmDialogOpen(false); + }; + + useEffect(() => { + if (data && data?.length > 0) { + setContainerId(data[0]?.containerId); + } + }, [data]); + + return ( + + {children} + + + Docker Terminal + + Easy way to access to docker container + + + + + + + + + + Are you sure you want to close the terminal? + + + By clicking the confirm button, the terminal will be closed. + + + + + + + + + + + ); }; From 7f53e9cf075903f49b72a6727676fdadefc19453 Mon Sep 17 00:00:00 2001 From: mafrasil Date: Fri, 6 Dec 2024 17:48:21 +0400 Subject: [PATCH 213/243] add trigger template --- apps/dokploy/public/templates/trigger.svg | 2 + apps/dokploy/templates/templates.ts | 1953 +++++++++-------- .../templates/trigger/docker-compose.yml | 107 + apps/dokploy/templates/trigger/index.ts | 56 + 4 files changed, 1149 insertions(+), 969 deletions(-) create mode 100644 apps/dokploy/public/templates/trigger.svg create mode 100644 apps/dokploy/templates/trigger/docker-compose.yml create mode 100644 apps/dokploy/templates/trigger/index.ts diff --git a/apps/dokploy/public/templates/trigger.svg b/apps/dokploy/public/templates/trigger.svg new file mode 100644 index 000000000..4e0957f44 --- /dev/null +++ b/apps/dokploy/public/templates/trigger.svg @@ -0,0 +1,2 @@ + +Trigger.dev logo \ No newline at end of file diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 878d01763..23fb26a96 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -1,975 +1,990 @@ import type { TemplateData } from "./types/templates-data.type"; export const templates: TemplateData[] = [ - { - id: "supabase", - name: "SupaBase", - version: "1.24.07", - description: - "The open source Firebase alternative. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications. ", - links: { - github: "https://github.com/supabase/supabase", - website: "https://supabase.com/", - docs: "https://supabase.com/docs/guides/self-hosting", - }, - logo: "supabase.svg", - load: () => import("./supabase/index").then((m) => m.generate), - tags: ["database", "firebase", "postgres"], - }, - { - id: "pocketbase", - name: "Pocketbase", - version: "v0.22.12", - description: - "Pocketbase is a self-hosted alternative to Firebase that allows you to build and host your own backend services.", - links: { - github: "https://github.com/pocketbase/pocketbase", - website: "https://pocketbase.io/", - docs: "https://pocketbase.io/docs/", - }, - logo: "pocketbase.svg", - load: () => import("./pocketbase/index").then((m) => m.generate), - tags: ["database", "cms", "headless"], - }, - { - id: "plausible", - name: "Plausible", - version: "v2.1.0", - description: - "Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.", - logo: "plausible.svg", - links: { - github: "https://github.com/plausible/plausible", - website: "https://plausible.io/", - docs: "https://plausible.io/docs", - }, - tags: ["analytics"], - load: () => import("./plausible/index").then((m) => m.generate), - }, - { - id: "calcom", - name: "Calcom", - version: "v2.7.6", - description: - "Calcom is a open source alternative to Calendly that allows to create scheduling and booking services.", + { + id: "supabase", + name: "SupaBase", + version: "1.24.07", + description: + "The open source Firebase alternative. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications. ", + links: { + github: "https://github.com/supabase/supabase", + website: "https://supabase.com/", + docs: "https://supabase.com/docs/guides/self-hosting", + }, + logo: "supabase.svg", + load: () => import("./supabase/index").then((m) => m.generate), + tags: ["database", "firebase", "postgres"], + }, + { + id: "pocketbase", + name: "Pocketbase", + version: "v0.22.12", + description: + "Pocketbase is a self-hosted alternative to Firebase that allows you to build and host your own backend services.", + links: { + github: "https://github.com/pocketbase/pocketbase", + website: "https://pocketbase.io/", + docs: "https://pocketbase.io/docs/", + }, + logo: "pocketbase.svg", + load: () => import("./pocketbase/index").then((m) => m.generate), + tags: ["database", "cms", "headless"], + }, + { + id: "plausible", + name: "Plausible", + version: "v2.1.0", + description: + "Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.", + logo: "plausible.svg", + links: { + github: "https://github.com/plausible/plausible", + website: "https://plausible.io/", + docs: "https://plausible.io/docs", + }, + tags: ["analytics"], + load: () => import("./plausible/index").then((m) => m.generate), + }, + { + id: "calcom", + name: "Calcom", + version: "v2.7.6", + description: + "Calcom is a open source alternative to Calendly that allows to create scheduling and booking services.", - links: { - github: "https://github.com/calcom/cal.com", - website: "https://cal.com/", - docs: "https://cal.com/docs", - }, - logo: "calcom.jpg", - tags: ["scheduling", "booking"], - load: () => import("./calcom/index").then((m) => m.generate), - }, - { - id: "grafana", - name: "Grafana", - version: "9.5.20", - description: - "Grafana is an open source platform for data visualization and monitoring.", - logo: "grafana.svg", - links: { - github: "https://github.com/grafana/grafana", - website: "https://grafana.com/", - docs: "https://grafana.com/docs/", - }, - tags: ["monitoring"], - load: () => import("./grafana/index").then((m) => m.generate), - }, - { - id: "directus", - name: "Directus", - version: "11.0.2", - description: - "Directus is an open source headless CMS that provides an API-first solution for building custom backends.", - logo: "directus.jpg", - links: { - github: "https://github.com/directus/directus", - website: "https://directus.io/", - docs: "https://docs.directus.io/", - }, - tags: ["cms"], - load: () => import("./directus/index").then((m) => m.generate), - }, - { - id: "baserow", - name: "Baserow", - version: "1.25.2", - description: - "Baserow is an open source database management tool that allows you to create and manage databases.", - logo: "baserow.webp", - links: { - github: "https://github.com/Baserow/baserow", - website: "https://baserow.io/", - docs: "https://baserow.io/docs/index", - }, - tags: ["database"], - load: () => import("./baserow/index").then((m) => m.generate), - }, - { - id: "ghost", - name: "Ghost", - version: "5.0.0", - description: - "Ghost is a free and open source, professional publishing platform built on a modern Node.js technology stack.", - logo: "ghost.jpeg", - links: { - github: "https://github.com/TryGhost/Ghost", - website: "https://ghost.org/", - docs: "https://ghost.org/docs/", - }, - tags: ["cms"], - load: () => import("./ghost/index").then((m) => m.generate), - }, - { - id: "uptime-kuma", - name: "Uptime Kuma", - version: "1.23.15", - description: - "Uptime Kuma is a free and open source monitoring tool that allows you to monitor your websites and applications.", - logo: "uptime-kuma.png", - links: { - github: "https://github.com/louislam/uptime-kuma", - website: "https://uptime.kuma.pet/", - docs: "https://github.com/louislam/uptime-kuma/wiki", - }, - tags: ["monitoring"], - load: () => import("./uptime-kuma/index").then((m) => m.generate), - }, - { - id: "n8n", - name: "n8n", - version: "1.48.1", - description: - "n8n is an open source low-code platform for automating workflows and integrations.", - logo: "n8n.png", - links: { - github: "https://github.com/n8n-io/n8n", - website: "https://n8n.io/", - docs: "https://docs.n8n.io/", - }, - tags: ["automation"], - load: () => import("./n8n/index").then((m) => m.generate), - }, - { - id: "wordpress", - name: "Wordpress", - version: "5.8.3", - description: - "Wordpress is a free and open source content management system (CMS) for publishing and managing websites.", - logo: "wordpress.png", - links: { - github: "https://github.com/WordPress/WordPress", - website: "https://wordpress.org/", - docs: "https://wordpress.org/documentation/", - }, - tags: ["cms"], - load: () => import("./wordpress/index").then((m) => m.generate), - }, - { - id: "odoo", - name: "Odoo", - version: "16.0", - description: - "Odoo is a free and open source business management software that helps you manage your company's operations.", - logo: "odoo.png", - links: { - github: "https://github.com/odoo/odoo", - website: "https://odoo.com/", - docs: "https://www.odoo.com/documentation/", - }, - tags: ["cms"], - load: () => import("./odoo/index").then((m) => m.generate), - }, - { - id: "appsmith", - name: "Appsmith", - version: "v1.29", - description: - "Appsmith is a free and open source platform for building internal tools and applications.", - logo: "appsmith.png", - links: { - github: "https://github.com/appsmithorg/appsmith", - website: "https://appsmith.com/", - docs: "https://docs.appsmith.com/", - }, - tags: ["cms"], - load: () => import("./appsmith/index").then((m) => m.generate), - }, - { - id: "excalidraw", - name: "Excalidraw", - version: "latest", - description: - "Excalidraw is a free and open source online diagramming tool that lets you easily create and share beautiful diagrams.", - logo: "excalidraw.jpg", - links: { - github: "https://github.com/excalidraw/excalidraw", - website: "https://excalidraw.com/", - docs: "https://docs.excalidraw.com/", - }, - tags: ["drawing"], - load: () => import("./excalidraw/index").then((m) => m.generate), - }, - { - id: "documenso", - name: "Documenso", - version: "v1.5.6", - description: - "Documenso is the open source alternative to DocuSign for signing documents digitally", - links: { - github: "https://github.com/documenso/documenso", - website: "https://documenso.com/", - docs: "https://documenso.com/docs", - }, - logo: "documenso.png", - tags: ["document-signing"], - load: () => import("./documenso/index").then((m) => m.generate), - }, - { - id: "nocodb", - name: "NocoDB", - version: "0.257.2", - description: - "NocoDB is an opensource Airtable alternative that turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart spreadsheet.", + links: { + github: "https://github.com/calcom/cal.com", + website: "https://cal.com/", + docs: "https://cal.com/docs", + }, + logo: "calcom.jpg", + tags: ["scheduling", "booking"], + load: () => import("./calcom/index").then((m) => m.generate), + }, + { + id: "grafana", + name: "Grafana", + version: "9.5.20", + description: + "Grafana is an open source platform for data visualization and monitoring.", + logo: "grafana.svg", + links: { + github: "https://github.com/grafana/grafana", + website: "https://grafana.com/", + docs: "https://grafana.com/docs/", + }, + tags: ["monitoring"], + load: () => import("./grafana/index").then((m) => m.generate), + }, + { + id: "directus", + name: "Directus", + version: "11.0.2", + description: + "Directus is an open source headless CMS that provides an API-first solution for building custom backends.", + logo: "directus.jpg", + links: { + github: "https://github.com/directus/directus", + website: "https://directus.io/", + docs: "https://docs.directus.io/", + }, + tags: ["cms"], + load: () => import("./directus/index").then((m) => m.generate), + }, + { + id: "baserow", + name: "Baserow", + version: "1.25.2", + description: + "Baserow is an open source database management tool that allows you to create and manage databases.", + logo: "baserow.webp", + links: { + github: "https://github.com/Baserow/baserow", + website: "https://baserow.io/", + docs: "https://baserow.io/docs/index", + }, + tags: ["database"], + load: () => import("./baserow/index").then((m) => m.generate), + }, + { + id: "ghost", + name: "Ghost", + version: "5.0.0", + description: + "Ghost is a free and open source, professional publishing platform built on a modern Node.js technology stack.", + logo: "ghost.jpeg", + links: { + github: "https://github.com/TryGhost/Ghost", + website: "https://ghost.org/", + docs: "https://ghost.org/docs/", + }, + tags: ["cms"], + load: () => import("./ghost/index").then((m) => m.generate), + }, + { + id: "uptime-kuma", + name: "Uptime Kuma", + version: "1.23.15", + description: + "Uptime Kuma is a free and open source monitoring tool that allows you to monitor your websites and applications.", + logo: "uptime-kuma.png", + links: { + github: "https://github.com/louislam/uptime-kuma", + website: "https://uptime.kuma.pet/", + docs: "https://github.com/louislam/uptime-kuma/wiki", + }, + tags: ["monitoring"], + load: () => import("./uptime-kuma/index").then((m) => m.generate), + }, + { + id: "n8n", + name: "n8n", + version: "1.48.1", + description: + "n8n is an open source low-code platform for automating workflows and integrations.", + logo: "n8n.png", + links: { + github: "https://github.com/n8n-io/n8n", + website: "https://n8n.io/", + docs: "https://docs.n8n.io/", + }, + tags: ["automation"], + load: () => import("./n8n/index").then((m) => m.generate), + }, + { + id: "wordpress", + name: "Wordpress", + version: "5.8.3", + description: + "Wordpress is a free and open source content management system (CMS) for publishing and managing websites.", + logo: "wordpress.png", + links: { + github: "https://github.com/WordPress/WordPress", + website: "https://wordpress.org/", + docs: "https://wordpress.org/documentation/", + }, + tags: ["cms"], + load: () => import("./wordpress/index").then((m) => m.generate), + }, + { + id: "odoo", + name: "Odoo", + version: "16.0", + description: + "Odoo is a free and open source business management software that helps you manage your company's operations.", + logo: "odoo.png", + links: { + github: "https://github.com/odoo/odoo", + website: "https://odoo.com/", + docs: "https://www.odoo.com/documentation/", + }, + tags: ["cms"], + load: () => import("./odoo/index").then((m) => m.generate), + }, + { + id: "appsmith", + name: "Appsmith", + version: "v1.29", + description: + "Appsmith is a free and open source platform for building internal tools and applications.", + logo: "appsmith.png", + links: { + github: "https://github.com/appsmithorg/appsmith", + website: "https://appsmith.com/", + docs: "https://docs.appsmith.com/", + }, + tags: ["cms"], + load: () => import("./appsmith/index").then((m) => m.generate), + }, + { + id: "excalidraw", + name: "Excalidraw", + version: "latest", + description: + "Excalidraw is a free and open source online diagramming tool that lets you easily create and share beautiful diagrams.", + logo: "excalidraw.jpg", + links: { + github: "https://github.com/excalidraw/excalidraw", + website: "https://excalidraw.com/", + docs: "https://docs.excalidraw.com/", + }, + tags: ["drawing"], + load: () => import("./excalidraw/index").then((m) => m.generate), + }, + { + id: "documenso", + name: "Documenso", + version: "v1.5.6", + description: + "Documenso is the open source alternative to DocuSign for signing documents digitally", + links: { + github: "https://github.com/documenso/documenso", + website: "https://documenso.com/", + docs: "https://documenso.com/docs", + }, + logo: "documenso.png", + tags: ["document-signing"], + load: () => import("./documenso/index").then((m) => m.generate), + }, + { + id: "nocodb", + name: "NocoDB", + version: "0.257.2", + description: + "NocoDB is an opensource Airtable alternative that turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart spreadsheet.", - links: { - github: "https://github.com/nocodb/nocodb", - website: "https://nocodb.com/", - docs: "https://docs.nocodb.com/", - }, - logo: "nocodb.png", - tags: ["database", "spreadsheet", "low-code", "nocode"], - load: () => import("./nocodb/index").then((m) => m.generate), - }, - { - id: "meilisearch", - name: "Meilisearch", - version: "v1.8.3", - description: - "Meilisearch is a free and open-source search engine that allows you to easily add search functionality to your web applications.", - logo: "meilisearch.png", - links: { - github: "https://github.com/meilisearch/meilisearch", - website: "https://www.meilisearch.com/", - docs: "https://docs.meilisearch.com/", - }, - tags: ["search"], - load: () => import("./meilisearch/index").then((m) => m.generate), - }, - { - id: "phpmyadmin", - name: "Phpmyadmin", - version: "5.2.1", - description: - "Phpmyadmin is a free and open-source web interface for MySQL and MariaDB that allows you to manage your databases.", - logo: "phpmyadmin.png", - links: { - github: "https://github.com/phpmyadmin/phpmyadmin", - website: "https://www.phpmyadmin.net/", - docs: "https://www.phpmyadmin.net/docs/", - }, - tags: ["database"], - load: () => import("./phpmyadmin/index").then((m) => m.generate), - }, - { - id: "rocketchat", - name: "Rocketchat", - version: "6.9.2", - description: - "Rocket.Chat is a free and open-source web chat platform that allows you to build and manage your own chat applications.", - logo: "rocketchat.png", - links: { - github: "https://github.com/RocketChat/Rocket.Chat", - website: "https://rocket.chat/", - docs: "https://rocket.chat/docs/", - }, - tags: ["chat"], - load: () => import("./rocketchat/index").then((m) => m.generate), - }, - { - id: "minio", - name: "Minio", - description: - "Minio is an open source object storage server compatible with Amazon S3 cloud storage service.", - logo: "minio.png", - version: "latest", - links: { - github: "https://github.com/minio/minio", - website: "https://minio.io/", - docs: "https://docs.minio.io/", - }, - tags: ["storage"], - load: () => import("./minio/index").then((m) => m.generate), - }, - { - id: "metabase", - name: "Metabase", - version: "v0.50.8", - description: - "Metabase is an open source business intelligence tool that allows you to ask questions and visualize data.", - logo: "metabase.png", - links: { - github: "https://github.com/metabase/metabase", - website: "https://www.metabase.com/", - docs: "https://www.metabase.com/docs/", - }, - tags: ["database", "dashboard"], - load: () => import("./metabase/index").then((m) => m.generate), - }, - { - id: "glitchtip", - name: "Glitchtip", - version: "v4.0", - description: "Glitchtip is simple, open source error tracking", - logo: "glitchtip.png", - links: { - github: "https://gitlab.com/glitchtip/", - website: "https://glitchtip.com/", - docs: "https://glitchtip.com/documentation", - }, - tags: ["hosting"], - load: () => import("./glitchtip/index").then((m) => m.generate), - }, - { - id: "open-webui", - name: "Open WebUI", - version: "v0.3.7", - description: - "Open WebUI is a free and open source chatgpt alternative. Open WebUI is an extensible, feature-rich, and user-friendly self-hosted WebUI designed to operate entirely offline. It supports various LLM runners, including Ollama and OpenAI-compatible APIs. The template include ollama and webui services.", - logo: "open-webui.png", - links: { - github: "https://github.com/open-webui/open-webui", - website: "https://openwebui.com/", - docs: "https://docs.openwebui.com/", - }, - tags: ["chat"], - load: () => import("./open-webui/index").then((m) => m.generate), - }, - { - id: "listmonk", - name: "Listmonk", - version: "v3.0.0", - description: - "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard.", - logo: "listmonk.png", - links: { - github: "https://github.com/knadh/listmonk", - website: "https://listmonk.app/", - docs: "https://listmonk.app/docs/", - }, - tags: ["email", "newsletter", "mailing-list"], - load: () => import("./listmonk/index").then((m) => m.generate), - }, - { - id: "doublezero", - name: "Double Zero", - version: "v0.2.1", - description: - "00 is a self hostable SES dashboard for sending and monitoring emails with AWS", - logo: "doublezero.svg", - links: { - github: "https://github.com/technomancy-dev/00", - website: "https://www.double-zero.cloud/", - docs: "https://github.com/technomancy-dev/00", - }, - tags: ["email"], - load: () => import("./doublezero/index").then((m) => m.generate), - }, - { - id: "umami", - name: "Umami", - version: "v2.14.0", - description: - "Umami is a simple, fast, privacy-focused alternative to Google Analytics.", - logo: "umami.png", - links: { - github: "https://github.com/umami-software/umami", - website: "https://umami.is", - docs: "https://umami.is/docs", - }, - tags: ["analytics"], - load: () => import("./umami/index").then((m) => m.generate), - }, - { - id: "jellyfin", - name: "jellyfin", - version: "v10.9.7", - description: - "Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. ", - logo: "jellyfin.svg", - links: { - github: "https://github.com/jellyfin/jellyfin", - website: "https://jellyfin.org/", - docs: "https://jellyfin.org/docs/", - }, - tags: ["media system"], - load: () => import("./jellyfin/index").then((m) => m.generate), - }, - { - id: "teable", - name: "teable", - version: "v1.3.1-alpha-build.460", - description: - "Teable is a Super fast, Real-time, Professional, Developer friendly, No-code database built on Postgres. It uses a simple, spreadsheet-like interface to create complex enterprise-level database applications. Unlock efficient app development with no-code, free from the hurdles of data security and scalability.", - logo: "teable.png", - links: { - github: "https://github.com/teableio/teable", - website: "https://teable.io/", - docs: "https://help.teable.io/", - }, - tags: ["database", "spreadsheet", "low-code", "nocode"], - load: () => import("./teable/index").then((m) => m.generate), - }, - { - id: "zipline", - name: "Zipline", - version: "v3.7.9", - description: - "A ShareX/file upload server that is easy to use, packed with features, and with an easy setup!", - logo: "zipline.png", - links: { - github: "https://github.com/diced/zipline", - website: "https://zipline.diced.sh/", - docs: "https://zipline.diced.sh/docs/", - }, - tags: ["media system", "storage"], - load: () => import("./zipline/index").then((m) => m.generate), - }, - { - id: "soketi", - name: "Soketi", - version: "v1.6.1-16", - description: - "Soketi is your simple, fast, and resilient open-source WebSockets server.", - logo: "soketi.png", - links: { - github: "https://github.com/soketi/soketi", - website: "https://soketi.app/", - docs: "https://docs.soketi.app/", - }, - tags: ["chat"], - load: () => import("./soketi/index").then((m) => m.generate), - }, - { - id: "aptabase", - name: "Aptabase", - version: "v1.0.0", - description: - "Aptabase is a self-hosted web analytics platform that lets you track website traffic and user behavior.", - logo: "aptabase.svg", - links: { - github: "https://github.com/aptabase/aptabase", - website: "https://aptabase.com/", - docs: "https://github.com/aptabase/aptabase/blob/main/README.md", - }, - tags: ["analytics", "self-hosted"], - load: () => import("./aptabase/index").then((m) => m.generate), - }, - { - id: "typebot", - name: "Typebot", - version: "2.27.0", - description: "Typebot is an open-source chatbot builder platform.", - logo: "typebot.svg", - links: { - github: "https://github.com/baptisteArno/typebot.io", - website: "https://typebot.io/", - docs: "https://docs.typebot.io/get-started/introduction", - }, - tags: ["chatbot", "builder", "open-source"], - load: () => import("./typebot/index").then((m) => m.generate), - }, - { - id: "gitea", - name: "Gitea", - version: "1.22.3", - description: - "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD.", - logo: "gitea.png", - links: { - github: "https://github.com/go-gitea/gitea.git", - website: "https://gitea.com/", - docs: "https://docs.gitea.com/installation/install-with-docker", - }, - tags: ["self-hosted", "storage"], - load: () => import("./gitea/index").then((m) => m.generate), - }, - { - id: "roundcube", - name: "Roundcube", - version: "1.6.9", - description: - "Free and open source webmail software for the masses, written in PHP.", - logo: "roundcube.svg", - links: { - github: "https://github.com/roundcube/roundcubemail", - website: "https://roundcube.net/", - docs: "https://roundcube.net/about/", - }, - tags: ["self-hosted", "email", "webmail"], - load: () => import("./roundcube/index").then((m) => m.generate), - }, - { - id: "filebrowser", - name: "File Browser", - version: "2.31.2", - description: - "Filebrowser is a standalone file manager for uploading, deleting, previewing, renaming, and editing files, with support for multiple users, each with their own directory.", - logo: "filebrowser.svg", - links: { - github: "https://github.com/filebrowser/filebrowser", - website: "https://filebrowser.org/", - docs: "https://filebrowser.org/", - }, - tags: ["file", "manager"], - load: () => import("./filebrowser/index").then((m) => m.generate), - }, - { - id: "tolgee", - name: "Tolgee", - version: "v3.80.4", - description: - "Developer & translator friendly web-based localization platform", - logo: "tolgee.svg", - links: { - github: "https://github.com/tolgee/tolgee-platform", - website: "https://tolgee.io", - docs: "https://tolgee.io/platform", - }, - tags: ["self-hosted", "i18n", "localization", "translations"], - load: () => import("./tolgee/index").then((m) => m.generate), - }, - { - id: "portainer", - name: "Portainer", - version: "2.21.4", - description: - "Portainer is a container management tool for deploying, troubleshooting, and securing applications across cloud, data centers, and IoT.", - logo: "portainer.svg", - links: { - github: "https://github.com/portainer/portainer", - website: "https://www.portainer.io/", - docs: "https://docs.portainer.io/", - }, - tags: ["cloud", "monitoring"], - load: () => import("./portainer/index").then((m) => m.generate), - }, - { - id: "influxdb", - name: "InfluxDB", - version: "2.7.10", - description: - "InfluxDB 2.7 is the platform purpose-built to collect, store, process and visualize time series data.", - logo: "influxdb.png", - links: { - github: "https://github.com/influxdata/influxdb", - website: "https://www.influxdata.com/", - docs: "https://docs.influxdata.com/influxdb/v2/", - }, - tags: ["self-hosted", "open-source", "storage", "database"], - load: () => import("./influxdb/index").then((m) => m.generate), - }, - { - id: "infisical", - name: "Infisical", - version: "0.90.1", - description: - "All-in-one platform to securely manage application configuration and secrets across your team and infrastructure.", - logo: "infisical.jpg", - links: { - github: "https://github.com/Infisical/infisical", - website: "https://infisical.com/", - docs: "https://infisical.com/docs/documentation/getting-started/introduction", - }, - tags: ["self-hosted", "open-source"], - load: () => import("./infisical/index").then((m) => m.generate), - }, - { - id: "docmost", - name: "Docmost", - version: "0.4.1", - description: - "Docmost, is an open-source collaborative wiki and documentation software.", - logo: "docmost.png", - links: { - github: "https://github.com/docmost/docmost", - website: "https://docmost.com/", - docs: "https://docmost.com/docs/", - }, - tags: ["self-hosted", "open-source", "manager"], - load: () => import("./docmost/index").then((m) => m.generate), - }, - { - id: "vaultwarden", - name: "Vaultwarden", - version: "1.32.3", - description: - "Unofficial Bitwarden compatible server written in Rust, formerly known as bitwarden_rs", - logo: "vaultwarden.svg", - links: { - github: "https://github.com/dani-garcia/vaultwarden", - website: "", - docs: "https://github.com/dani-garcia/vaultwarden/wiki", - }, - tags: ["open-source"], - load: () => import("./vaultwarden/index").then((m) => m.generate), - }, - { - id: "hi-events", - name: "Hi.events", - version: "0.8.0-beta.1", - description: - "Hi.Events is a self-hosted event management and ticket selling platform that allows you to create, manage and promote events easily.", - logo: "hi-events.svg", - links: { - github: "https://github.com/HiEventsDev/hi.events", - website: "https://hi.events/", - docs: "https://hi.events/docs", - }, - tags: ["self-hosted", "open-source", "manager"], - load: () => import("./hi-events/index").then((m) => m.generate), - }, - { - id: "windows", - name: "Windows (dockerized)", - version: "4.00", - description: "Windows inside a Docker container.", - logo: "windows.png", - links: { - github: "https://github.com/dockur/windows", - website: "", - docs: "https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-use-it", - }, - tags: ["self-hosted", "open-source", "os"], - load: () => import("./windows/index").then((m) => m.generate), - }, - { - id: "macos", - name: "MacOS (dockerized)", - version: "1.14", - description: "MacOS inside a Docker container.", - logo: "macos.png", - links: { - github: "https://github.com/dockur/macos", - website: "", - docs: "https://github.com/dockur/macos?tab=readme-ov-file#how-do-i-use-it", - }, - tags: ["self-hosted", "open-source", "os"], - load: () => import("./macos/index").then((m) => m.generate), - }, - { - id: "coder", - name: "Coder", - version: "2.15.3", - description: - "Coder is an open-source cloud development environment (CDE) that you host in your cloud or on-premises.", - logo: "coder.svg", - links: { - github: "https://github.com/coder/coder", - website: "https://coder.com/", - docs: "https://coder.com/docs", - }, - tags: ["self-hosted", "open-source", "builder"], - load: () => import("./coder/index").then((m) => m.generate), - }, - { - id: "stirling", - name: "Stirling PDF", - version: "0.30.1", - description: "A locally hosted one-stop shop for all your PDF needs", - logo: "stirling.svg", - links: { - github: "https://github.com/Stirling-Tools/Stirling-PDF", - website: "https://www.stirlingpdf.com/", - docs: "https://docs.stirlingpdf.com/", - }, - tags: ["pdf", "tools"], - load: () => import("./stirling/index").then((m) => m.generate), - }, - { - id: "lobe-chat", - name: "Lobe Chat", - version: "v1.26.1", - description: "Lobe Chat - an open-source, modern-design AI chat framework.", - logo: "lobe-chat.png", - links: { - github: "https://github.com/lobehub/lobe-chat", - website: "https://chat-preview.lobehub.com/", - docs: "https://lobehub.com/docs/self-hosting/platform/docker-compose", - }, - tags: ["IA", "chat"], - load: () => import("./lobe-chat/index").then((m) => m.generate), - }, - { - id: "peppermint", - name: "Peppermint", - version: "latest", - description: - "Peppermint is a modern, open-source API development platform that helps you build, test and document your APIs.", - logo: "peppermint.svg", - links: { - github: "https://github.com/Peppermint-Lab/peppermint", - website: "https://peppermint.sh/", - docs: "https://docs.peppermint.sh/", - }, - tags: ["api", "development", "documentation"], - load: () => import("./peppermint/index").then((m) => m.generate), - }, - { - id: "windmill", - name: "Windmill", - version: "latest", - description: - "A developer platform to build production-grade workflows and internal apps. Open-source alternative to Airplane, Retool, and GitHub Actions.", - logo: "windmill.svg", - links: { - github: "https://github.com/windmill-labs/windmill", - website: "https://www.windmill.dev/", - docs: "https://docs.windmill.dev/", - }, - tags: ["workflow", "automation", "development"], - load: () => import("./windmill/index").then((m) => m.generate), - }, - { - id: "activepieces", - name: "Activepieces", - version: "0.35.0", - description: - "Open-source no-code business automation tool. An alternative to Zapier, Make.com, and Tray.", - logo: "activepieces.svg", - links: { - github: "https://github.com/activepieces/activepieces", - website: "https://www.activepieces.com/", - docs: "https://www.activepieces.com/docs", - }, - tags: ["automation", "workflow", "no-code"], - load: () => import("./activepieces/index").then((m) => m.generate), - }, - { - id: "invoiceshelf", - name: "InvoiceShelf", - version: "latest", - description: - "InvoiceShelf is a self-hosted open source invoicing system for freelancers and small businesses.", - logo: "invoiceshelf.png", - links: { - github: "https://github.com/InvoiceShelf/invoiceshelf", - website: "https://invoiceshelf.com", - docs: "https://github.com/InvoiceShelf/invoiceshelf#readme", - }, - tags: ["invoice", "business", "finance"], - load: () => import("./invoiceshelf/index").then((m) => m.generate), - }, - { - id: "postiz", - name: "Postiz", - version: "latest", - description: - "Postiz is a modern, open-source platform for managing and publishing content across multiple channels.", - logo: "postiz.png", - links: { - github: "https://github.com/gitroomhq/postiz", - website: "https://postiz.com", - docs: "https://docs.postiz.com", - }, - tags: ["cms", "content-management", "publishing"], - load: () => import("./postiz/index").then((m) => m.generate), - }, - { - id: "slash", - name: "Slash", - version: "latest", - description: - "Slash is a modern, self-hosted bookmarking service and link shortener that helps you organize and share your favorite links.", - logo: "slash.png", - links: { - github: "https://github.com/yourselfhosted/slash", - website: "https://github.com/yourselfhosted/slash#readme", - docs: "https://github.com/yourselfhosted/slash/wiki", - }, - tags: ["bookmarks", "link-shortener", "self-hosted"], - load: () => import("./slash/index").then((m) => m.generate), - }, - { - id: "discord-tickets", - name: "Discord Tickets", - version: "4.0.21", - description: - "An open-source Discord bot for creating and managing support ticket channels.", - logo: "discord-tickets.png", - links: { - github: "https://github.com/discord-tickets/bot", - website: "https://discordtickets.app", - docs: "https://discordtickets.app/self-hosting/installation/docker/", - }, - tags: ["discord", "tickets", "support"], - load: () => import("./discord-tickets/index").then((m) => m.generate), - }, - { - id: "nextcloud-aio", - name: "Nextcloud All in One", - version: "30.0.2", - description: - "Nextcloud (AIO) is a self-hosted file storage and sync platform with powerful collaboration capabilities. It integrates Files, Talk, Groupware, Office, Assistant and more into a single platform for remote work and data protection.", - logo: "nextcloud-aio.svg", - links: { - github: "https://github.com/nextcloud/docker", - website: "https://nextcloud.com/", - docs: "https://docs.nextcloud.com/", - }, - tags: ["file", "sync"], - load: () => import("./nextcloud-aio/index").then((m) => m.generate), - }, - { - id: "blender", - name: "Blender", - version: "latest", - description: - "Blender is a free and open-source 3D creation suite. It supports the entire 3D pipeline—modeling, rigging, animation, simulation, rendering, compositing and motion tracking, video editing and 2D animation pipeline.", - logo: "blender.svg", - links: { - github: "https://github.com/linuxserver/docker-blender", - website: "https://www.blender.org/", - docs: "https://docs.blender.org/", - }, - tags: ["3d", "rendering", "animation"], - load: () => import("./blender/index").then((m) => m.generate), - }, - { - id: "heyform", - name: "HeyForm", - version: "latest", - description: - "Allows anyone to create engaging conversational forms for surveys, questionnaires, quizzes, and polls. No coding skills required.", - logo: "heyform.svg", - links: { - github: "https://github.com/heyform/heyform", - website: "https://heyform.net", - docs: "https://docs.heyform.net", - }, - tags: ["form", "builder", "questionnaire", "quiz", "survey"], - load: () => import("./heyform/index").then((m) => m.generate), - }, - { - id: "chatwoot", - name: "Chatwoot", - version: "v3.14.1", - description: - "Open-source customer engagement platform that provides a shared inbox for teams, live chat, and omnichannel support.", - logo: "chatwoot.svg", - links: { - github: "https://github.com/chatwoot/chatwoot", - website: "https://www.chatwoot.com", - docs: "https://www.chatwoot.com/docs", - }, - tags: ["support", "chat", "customer-service"], - load: () => import("./chatwoot/index").then((m) => m.generate), - }, - { - id: "discourse", - name: "Discourse", - version: "3.3.2", - description: - "Discourse is a modern forum software for your community. Use it as a mailing list, discussion forum, or long-form chat room.", - logo: "discourse.svg", - links: { - github: "https://github.com/discourse/discourse", - website: "https://www.discourse.org/", - docs: "https://meta.discourse.org/", - }, - tags: ["forum", "community", "discussion"], - load: () => import("./discourse/index").then((m) => m.generate), - }, - { - id: "immich", - name: "Immich", - version: "v1.121.0", - description: - "High performance self-hosted photo and video backup solution directly from your mobile phone.", - logo: "immich.svg", - links: { - github: "https://github.com/immich-app/immich", - website: "https://immich.app/", - docs: "https://immich.app/docs/overview/introduction", - }, - tags: ["photos", "videos", "backup", "media"], - load: () => import("./immich/index").then((m) => m.generate), - }, - { - id: "twenty", - name: "Twenty CRM", - version: "latest", - description: - "Twenty is a modern CRM offering a powerful spreadsheet interface and open-source alternative to Salesforce.", - logo: "twenty.svg", - links: { - github: "https://github.com/twentyhq/twenty", - website: "https://twenty.com", - docs: "https://docs.twenty.com", - }, - tags: ["crm", "sales", "business"], - load: () => import("./twenty/index").then((m) => m.generate), - }, - { - id: "yourls", - name: "YOURLS", - version: "1.9.2", - description: - "YOURLS (Your Own URL Shortener) is a set of PHP scripts that will allow you to run your own URL shortening service (a la TinyURL or Bitly).", - logo: "yourls.svg", - links: { - github: "https://github.com/YOURLS/YOURLS", - website: "https://yourls.org/", - docs: "https://yourls.org/#documentation", - }, - tags: ["url-shortener", "php"], - load: () => import("./yourls/index").then((m) => m.generate), - }, - { - id: "ryot", - name: "Ryot", - version: "v7.10", - description: - "A self-hosted platform for tracking various media types including movies, TV shows, video games, books, audiobooks, and more.", - logo: "ryot.png", - links: { - github: "https://github.com/IgnisDa/ryot", - website: "https://ryot.dev/", - docs: "https://ryot.dev/docs/getting-started", - }, - tags: ["media", "tracking", "self-hosted"], - load: () => import("./ryot/index").then((m) => m.generate), - }, - { - id: "photoprism", - name: "Photoprism", - version: "latest", - description: - "PhotoPrism® is an AI-Powered Photos App for the Decentralized Web. It makes use of the latest technologies to tag and find pictures automatically without getting in your way.", - logo: "photoprism.svg", - links: { - github: "https://github.com/photoprism/photoprism", - website: "https://www.photoprism.app/", - docs: "https://docs.photoprism.app/", - }, - tags: ["media", "photos", "self-hosted"], - load: () => import("./photoprism/index").then((m) => m.generate), - }, - { - id: "ontime", - name: "Ontime", - version: "v3.8.0", - description: - "Ontime is browser-based application that manages event rundowns, scheduliing and cuing", - logo: "ontime.png", - links: { - github: "https://github.com/cpvalente/ontime/", - website: "https://getontime.no", - docs: "https://docs.getontime.no", - }, - tags: ["event"], - load: () => import("./ontime/index").then((m) => m.generate), - }, + links: { + github: "https://github.com/nocodb/nocodb", + website: "https://nocodb.com/", + docs: "https://docs.nocodb.com/", + }, + logo: "nocodb.png", + tags: ["database", "spreadsheet", "low-code", "nocode"], + load: () => import("./nocodb/index").then((m) => m.generate), + }, + { + id: "meilisearch", + name: "Meilisearch", + version: "v1.8.3", + description: + "Meilisearch is a free and open-source search engine that allows you to easily add search functionality to your web applications.", + logo: "meilisearch.png", + links: { + github: "https://github.com/meilisearch/meilisearch", + website: "https://www.meilisearch.com/", + docs: "https://docs.meilisearch.com/", + }, + tags: ["search"], + load: () => import("./meilisearch/index").then((m) => m.generate), + }, + { + id: "phpmyadmin", + name: "Phpmyadmin", + version: "5.2.1", + description: + "Phpmyadmin is a free and open-source web interface for MySQL and MariaDB that allows you to manage your databases.", + logo: "phpmyadmin.png", + links: { + github: "https://github.com/phpmyadmin/phpmyadmin", + website: "https://www.phpmyadmin.net/", + docs: "https://www.phpmyadmin.net/docs/", + }, + tags: ["database"], + load: () => import("./phpmyadmin/index").then((m) => m.generate), + }, + { + id: "rocketchat", + name: "Rocketchat", + version: "6.9.2", + description: + "Rocket.Chat is a free and open-source web chat platform that allows you to build and manage your own chat applications.", + logo: "rocketchat.png", + links: { + github: "https://github.com/RocketChat/Rocket.Chat", + website: "https://rocket.chat/", + docs: "https://rocket.chat/docs/", + }, + tags: ["chat"], + load: () => import("./rocketchat/index").then((m) => m.generate), + }, + { + id: "minio", + name: "Minio", + description: + "Minio is an open source object storage server compatible with Amazon S3 cloud storage service.", + logo: "minio.png", + version: "latest", + links: { + github: "https://github.com/minio/minio", + website: "https://minio.io/", + docs: "https://docs.minio.io/", + }, + tags: ["storage"], + load: () => import("./minio/index").then((m) => m.generate), + }, + { + id: "metabase", + name: "Metabase", + version: "v0.50.8", + description: + "Metabase is an open source business intelligence tool that allows you to ask questions and visualize data.", + logo: "metabase.png", + links: { + github: "https://github.com/metabase/metabase", + website: "https://www.metabase.com/", + docs: "https://www.metabase.com/docs/", + }, + tags: ["database", "dashboard"], + load: () => import("./metabase/index").then((m) => m.generate), + }, + { + id: "glitchtip", + name: "Glitchtip", + version: "v4.0", + description: "Glitchtip is simple, open source error tracking", + logo: "glitchtip.png", + links: { + github: "https://gitlab.com/glitchtip/", + website: "https://glitchtip.com/", + docs: "https://glitchtip.com/documentation", + }, + tags: ["hosting"], + load: () => import("./glitchtip/index").then((m) => m.generate), + }, + { + id: "open-webui", + name: "Open WebUI", + version: "v0.3.7", + description: + "Open WebUI is a free and open source chatgpt alternative. Open WebUI is an extensible, feature-rich, and user-friendly self-hosted WebUI designed to operate entirely offline. It supports various LLM runners, including Ollama and OpenAI-compatible APIs. The template include ollama and webui services.", + logo: "open-webui.png", + links: { + github: "https://github.com/open-webui/open-webui", + website: "https://openwebui.com/", + docs: "https://docs.openwebui.com/", + }, + tags: ["chat"], + load: () => import("./open-webui/index").then((m) => m.generate), + }, + { + id: "listmonk", + name: "Listmonk", + version: "v3.0.0", + description: + "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard.", + logo: "listmonk.png", + links: { + github: "https://github.com/knadh/listmonk", + website: "https://listmonk.app/", + docs: "https://listmonk.app/docs/", + }, + tags: ["email", "newsletter", "mailing-list"], + load: () => import("./listmonk/index").then((m) => m.generate), + }, + { + id: "doublezero", + name: "Double Zero", + version: "v0.2.1", + description: + "00 is a self hostable SES dashboard for sending and monitoring emails with AWS", + logo: "doublezero.svg", + links: { + github: "https://github.com/technomancy-dev/00", + website: "https://www.double-zero.cloud/", + docs: "https://github.com/technomancy-dev/00", + }, + tags: ["email"], + load: () => import("./doublezero/index").then((m) => m.generate), + }, + { + id: "umami", + name: "Umami", + version: "v2.14.0", + description: + "Umami is a simple, fast, privacy-focused alternative to Google Analytics.", + logo: "umami.png", + links: { + github: "https://github.com/umami-software/umami", + website: "https://umami.is", + docs: "https://umami.is/docs", + }, + tags: ["analytics"], + load: () => import("./umami/index").then((m) => m.generate), + }, + { + id: "jellyfin", + name: "jellyfin", + version: "v10.9.7", + description: + "Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. ", + logo: "jellyfin.svg", + links: { + github: "https://github.com/jellyfin/jellyfin", + website: "https://jellyfin.org/", + docs: "https://jellyfin.org/docs/", + }, + tags: ["media system"], + load: () => import("./jellyfin/index").then((m) => m.generate), + }, + { + id: "teable", + name: "teable", + version: "v1.3.1-alpha-build.460", + description: + "Teable is a Super fast, Real-time, Professional, Developer friendly, No-code database built on Postgres. It uses a simple, spreadsheet-like interface to create complex enterprise-level database applications. Unlock efficient app development with no-code, free from the hurdles of data security and scalability.", + logo: "teable.png", + links: { + github: "https://github.com/teableio/teable", + website: "https://teable.io/", + docs: "https://help.teable.io/", + }, + tags: ["database", "spreadsheet", "low-code", "nocode"], + load: () => import("./teable/index").then((m) => m.generate), + }, + { + id: "zipline", + name: "Zipline", + version: "v3.7.9", + description: + "A ShareX/file upload server that is easy to use, packed with features, and with an easy setup!", + logo: "zipline.png", + links: { + github: "https://github.com/diced/zipline", + website: "https://zipline.diced.sh/", + docs: "https://zipline.diced.sh/docs/", + }, + tags: ["media system", "storage"], + load: () => import("./zipline/index").then((m) => m.generate), + }, + { + id: "soketi", + name: "Soketi", + version: "v1.6.1-16", + description: + "Soketi is your simple, fast, and resilient open-source WebSockets server.", + logo: "soketi.png", + links: { + github: "https://github.com/soketi/soketi", + website: "https://soketi.app/", + docs: "https://docs.soketi.app/", + }, + tags: ["chat"], + load: () => import("./soketi/index").then((m) => m.generate), + }, + { + id: "aptabase", + name: "Aptabase", + version: "v1.0.0", + description: + "Aptabase is a self-hosted web analytics platform that lets you track website traffic and user behavior.", + logo: "aptabase.svg", + links: { + github: "https://github.com/aptabase/aptabase", + website: "https://aptabase.com/", + docs: "https://github.com/aptabase/aptabase/blob/main/README.md", + }, + tags: ["analytics", "self-hosted"], + load: () => import("./aptabase/index").then((m) => m.generate), + }, + { + id: "typebot", + name: "Typebot", + version: "2.27.0", + description: "Typebot is an open-source chatbot builder platform.", + logo: "typebot.svg", + links: { + github: "https://github.com/baptisteArno/typebot.io", + website: "https://typebot.io/", + docs: "https://docs.typebot.io/get-started/introduction", + }, + tags: ["chatbot", "builder", "open-source"], + load: () => import("./typebot/index").then((m) => m.generate), + }, + { + id: "gitea", + name: "Gitea", + version: "1.22.3", + description: + "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD.", + logo: "gitea.png", + links: { + github: "https://github.com/go-gitea/gitea.git", + website: "https://gitea.com/", + docs: "https://docs.gitea.com/installation/install-with-docker", + }, + tags: ["self-hosted", "storage"], + load: () => import("./gitea/index").then((m) => m.generate), + }, + { + id: "roundcube", + name: "Roundcube", + version: "1.6.9", + description: + "Free and open source webmail software for the masses, written in PHP.", + logo: "roundcube.svg", + links: { + github: "https://github.com/roundcube/roundcubemail", + website: "https://roundcube.net/", + docs: "https://roundcube.net/about/", + }, + tags: ["self-hosted", "email", "webmail"], + load: () => import("./roundcube/index").then((m) => m.generate), + }, + { + id: "filebrowser", + name: "File Browser", + version: "2.31.2", + description: + "Filebrowser is a standalone file manager for uploading, deleting, previewing, renaming, and editing files, with support for multiple users, each with their own directory.", + logo: "filebrowser.svg", + links: { + github: "https://github.com/filebrowser/filebrowser", + website: "https://filebrowser.org/", + docs: "https://filebrowser.org/", + }, + tags: ["file", "manager"], + load: () => import("./filebrowser/index").then((m) => m.generate), + }, + { + id: "tolgee", + name: "Tolgee", + version: "v3.80.4", + description: + "Developer & translator friendly web-based localization platform", + logo: "tolgee.svg", + links: { + github: "https://github.com/tolgee/tolgee-platform", + website: "https://tolgee.io", + docs: "https://tolgee.io/platform", + }, + tags: ["self-hosted", "i18n", "localization", "translations"], + load: () => import("./tolgee/index").then((m) => m.generate), + }, + { + id: "portainer", + name: "Portainer", + version: "2.21.4", + description: + "Portainer is a container management tool for deploying, troubleshooting, and securing applications across cloud, data centers, and IoT.", + logo: "portainer.svg", + links: { + github: "https://github.com/portainer/portainer", + website: "https://www.portainer.io/", + docs: "https://docs.portainer.io/", + }, + tags: ["cloud", "monitoring"], + load: () => import("./portainer/index").then((m) => m.generate), + }, + { + id: "influxdb", + name: "InfluxDB", + version: "2.7.10", + description: + "InfluxDB 2.7 is the platform purpose-built to collect, store, process and visualize time series data.", + logo: "influxdb.png", + links: { + github: "https://github.com/influxdata/influxdb", + website: "https://www.influxdata.com/", + docs: "https://docs.influxdata.com/influxdb/v2/", + }, + tags: ["self-hosted", "open-source", "storage", "database"], + load: () => import("./influxdb/index").then((m) => m.generate), + }, + { + id: "infisical", + name: "Infisical", + version: "0.90.1", + description: + "All-in-one platform to securely manage application configuration and secrets across your team and infrastructure.", + logo: "infisical.jpg", + links: { + github: "https://github.com/Infisical/infisical", + website: "https://infisical.com/", + docs: "https://infisical.com/docs/documentation/getting-started/introduction", + }, + tags: ["self-hosted", "open-source"], + load: () => import("./infisical/index").then((m) => m.generate), + }, + { + id: "docmost", + name: "Docmost", + version: "0.4.1", + description: + "Docmost, is an open-source collaborative wiki and documentation software.", + logo: "docmost.png", + links: { + github: "https://github.com/docmost/docmost", + website: "https://docmost.com/", + docs: "https://docmost.com/docs/", + }, + tags: ["self-hosted", "open-source", "manager"], + load: () => import("./docmost/index").then((m) => m.generate), + }, + { + id: "vaultwarden", + name: "Vaultwarden", + version: "1.32.3", + description: + "Unofficial Bitwarden compatible server written in Rust, formerly known as bitwarden_rs", + logo: "vaultwarden.svg", + links: { + github: "https://github.com/dani-garcia/vaultwarden", + website: "", + docs: "https://github.com/dani-garcia/vaultwarden/wiki", + }, + tags: ["open-source"], + load: () => import("./vaultwarden/index").then((m) => m.generate), + }, + { + id: "hi-events", + name: "Hi.events", + version: "0.8.0-beta.1", + description: + "Hi.Events is a self-hosted event management and ticket selling platform that allows you to create, manage and promote events easily.", + logo: "hi-events.svg", + links: { + github: "https://github.com/HiEventsDev/hi.events", + website: "https://hi.events/", + docs: "https://hi.events/docs", + }, + tags: ["self-hosted", "open-source", "manager"], + load: () => import("./hi-events/index").then((m) => m.generate), + }, + { + id: "windows", + name: "Windows (dockerized)", + version: "4.00", + description: "Windows inside a Docker container.", + logo: "windows.png", + links: { + github: "https://github.com/dockur/windows", + website: "", + docs: "https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-use-it", + }, + tags: ["self-hosted", "open-source", "os"], + load: () => import("./windows/index").then((m) => m.generate), + }, + { + id: "macos", + name: "MacOS (dockerized)", + version: "1.14", + description: "MacOS inside a Docker container.", + logo: "macos.png", + links: { + github: "https://github.com/dockur/macos", + website: "", + docs: "https://github.com/dockur/macos?tab=readme-ov-file#how-do-i-use-it", + }, + tags: ["self-hosted", "open-source", "os"], + load: () => import("./macos/index").then((m) => m.generate), + }, + { + id: "coder", + name: "Coder", + version: "2.15.3", + description: + "Coder is an open-source cloud development environment (CDE) that you host in your cloud or on-premises.", + logo: "coder.svg", + links: { + github: "https://github.com/coder/coder", + website: "https://coder.com/", + docs: "https://coder.com/docs", + }, + tags: ["self-hosted", "open-source", "builder"], + load: () => import("./coder/index").then((m) => m.generate), + }, + { + id: "stirling", + name: "Stirling PDF", + version: "0.30.1", + description: "A locally hosted one-stop shop for all your PDF needs", + logo: "stirling.svg", + links: { + github: "https://github.com/Stirling-Tools/Stirling-PDF", + website: "https://www.stirlingpdf.com/", + docs: "https://docs.stirlingpdf.com/", + }, + tags: ["pdf", "tools"], + load: () => import("./stirling/index").then((m) => m.generate), + }, + { + id: "lobe-chat", + name: "Lobe Chat", + version: "v1.26.1", + description: "Lobe Chat - an open-source, modern-design AI chat framework.", + logo: "lobe-chat.png", + links: { + github: "https://github.com/lobehub/lobe-chat", + website: "https://chat-preview.lobehub.com/", + docs: "https://lobehub.com/docs/self-hosting/platform/docker-compose", + }, + tags: ["IA", "chat"], + load: () => import("./lobe-chat/index").then((m) => m.generate), + }, + { + id: "peppermint", + name: "Peppermint", + version: "latest", + description: + "Peppermint is a modern, open-source API development platform that helps you build, test and document your APIs.", + logo: "peppermint.svg", + links: { + github: "https://github.com/Peppermint-Lab/peppermint", + website: "https://peppermint.sh/", + docs: "https://docs.peppermint.sh/", + }, + tags: ["api", "development", "documentation"], + load: () => import("./peppermint/index").then((m) => m.generate), + }, + { + id: "windmill", + name: "Windmill", + version: "latest", + description: + "A developer platform to build production-grade workflows and internal apps. Open-source alternative to Airplane, Retool, and GitHub Actions.", + logo: "windmill.svg", + links: { + github: "https://github.com/windmill-labs/windmill", + website: "https://www.windmill.dev/", + docs: "https://docs.windmill.dev/", + }, + tags: ["workflow", "automation", "development"], + load: () => import("./windmill/index").then((m) => m.generate), + }, + { + id: "activepieces", + name: "Activepieces", + version: "0.35.0", + description: + "Open-source no-code business automation tool. An alternative to Zapier, Make.com, and Tray.", + logo: "activepieces.svg", + links: { + github: "https://github.com/activepieces/activepieces", + website: "https://www.activepieces.com/", + docs: "https://www.activepieces.com/docs", + }, + tags: ["automation", "workflow", "no-code"], + load: () => import("./activepieces/index").then((m) => m.generate), + }, + { + id: "invoiceshelf", + name: "InvoiceShelf", + version: "latest", + description: + "InvoiceShelf is a self-hosted open source invoicing system for freelancers and small businesses.", + logo: "invoiceshelf.png", + links: { + github: "https://github.com/InvoiceShelf/invoiceshelf", + website: "https://invoiceshelf.com", + docs: "https://github.com/InvoiceShelf/invoiceshelf#readme", + }, + tags: ["invoice", "business", "finance"], + load: () => import("./invoiceshelf/index").then((m) => m.generate), + }, + { + id: "postiz", + name: "Postiz", + version: "latest", + description: + "Postiz is a modern, open-source platform for managing and publishing content across multiple channels.", + logo: "postiz.png", + links: { + github: "https://github.com/gitroomhq/postiz", + website: "https://postiz.com", + docs: "https://docs.postiz.com", + }, + tags: ["cms", "content-management", "publishing"], + load: () => import("./postiz/index").then((m) => m.generate), + }, + { + id: "slash", + name: "Slash", + version: "latest", + description: + "Slash is a modern, self-hosted bookmarking service and link shortener that helps you organize and share your favorite links.", + logo: "slash.png", + links: { + github: "https://github.com/yourselfhosted/slash", + website: "https://github.com/yourselfhosted/slash#readme", + docs: "https://github.com/yourselfhosted/slash/wiki", + }, + tags: ["bookmarks", "link-shortener", "self-hosted"], + load: () => import("./slash/index").then((m) => m.generate), + }, + { + id: "discord-tickets", + name: "Discord Tickets", + version: "4.0.21", + description: + "An open-source Discord bot for creating and managing support ticket channels.", + logo: "discord-tickets.png", + links: { + github: "https://github.com/discord-tickets/bot", + website: "https://discordtickets.app", + docs: "https://discordtickets.app/self-hosting/installation/docker/", + }, + tags: ["discord", "tickets", "support"], + load: () => import("./discord-tickets/index").then((m) => m.generate), + }, + { + id: "nextcloud-aio", + name: "Nextcloud All in One", + version: "30.0.2", + description: + "Nextcloud (AIO) is a self-hosted file storage and sync platform with powerful collaboration capabilities. It integrates Files, Talk, Groupware, Office, Assistant and more into a single platform for remote work and data protection.", + logo: "nextcloud-aio.svg", + links: { + github: "https://github.com/nextcloud/docker", + website: "https://nextcloud.com/", + docs: "https://docs.nextcloud.com/", + }, + tags: ["file", "sync"], + load: () => import("./nextcloud-aio/index").then((m) => m.generate), + }, + { + id: "blender", + name: "Blender", + version: "latest", + description: + "Blender is a free and open-source 3D creation suite. It supports the entire 3D pipeline—modeling, rigging, animation, simulation, rendering, compositing and motion tracking, video editing and 2D animation pipeline.", + logo: "blender.svg", + links: { + github: "https://github.com/linuxserver/docker-blender", + website: "https://www.blender.org/", + docs: "https://docs.blender.org/", + }, + tags: ["3d", "rendering", "animation"], + load: () => import("./blender/index").then((m) => m.generate), + }, + { + id: "heyform", + name: "HeyForm", + version: "latest", + description: + "Allows anyone to create engaging conversational forms for surveys, questionnaires, quizzes, and polls. No coding skills required.", + logo: "heyform.svg", + links: { + github: "https://github.com/heyform/heyform", + website: "https://heyform.net", + docs: "https://docs.heyform.net", + }, + tags: ["form", "builder", "questionnaire", "quiz", "survey"], + load: () => import("./heyform/index").then((m) => m.generate), + }, + { + id: "chatwoot", + name: "Chatwoot", + version: "v3.14.1", + description: + "Open-source customer engagement platform that provides a shared inbox for teams, live chat, and omnichannel support.", + logo: "chatwoot.svg", + links: { + github: "https://github.com/chatwoot/chatwoot", + website: "https://www.chatwoot.com", + docs: "https://www.chatwoot.com/docs", + }, + tags: ["support", "chat", "customer-service"], + load: () => import("./chatwoot/index").then((m) => m.generate), + }, + { + id: "discourse", + name: "Discourse", + version: "3.3.2", + description: + "Discourse is a modern forum software for your community. Use it as a mailing list, discussion forum, or long-form chat room.", + logo: "discourse.svg", + links: { + github: "https://github.com/discourse/discourse", + website: "https://www.discourse.org/", + docs: "https://meta.discourse.org/", + }, + tags: ["forum", "community", "discussion"], + load: () => import("./discourse/index").then((m) => m.generate), + }, + { + id: "immich", + name: "Immich", + version: "v1.121.0", + description: + "High performance self-hosted photo and video backup solution directly from your mobile phone.", + logo: "immich.svg", + links: { + github: "https://github.com/immich-app/immich", + website: "https://immich.app/", + docs: "https://immich.app/docs/overview/introduction", + }, + tags: ["photos", "videos", "backup", "media"], + load: () => import("./immich/index").then((m) => m.generate), + }, + { + id: "twenty", + name: "Twenty CRM", + version: "latest", + description: + "Twenty is a modern CRM offering a powerful spreadsheet interface and open-source alternative to Salesforce.", + logo: "twenty.svg", + links: { + github: "https://github.com/twentyhq/twenty", + website: "https://twenty.com", + docs: "https://docs.twenty.com", + }, + tags: ["crm", "sales", "business"], + load: () => import("./twenty/index").then((m) => m.generate), + }, + { + id: "yourls", + name: "YOURLS", + version: "1.9.2", + description: + "YOURLS (Your Own URL Shortener) is a set of PHP scripts that will allow you to run your own URL shortening service (a la TinyURL or Bitly).", + logo: "yourls.svg", + links: { + github: "https://github.com/YOURLS/YOURLS", + website: "https://yourls.org/", + docs: "https://yourls.org/#documentation", + }, + tags: ["url-shortener", "php"], + load: () => import("./yourls/index").then((m) => m.generate), + }, + { + id: "ryot", + name: "Ryot", + version: "v7.10", + description: + "A self-hosted platform for tracking various media types including movies, TV shows, video games, books, audiobooks, and more.", + logo: "ryot.png", + links: { + github: "https://github.com/IgnisDa/ryot", + website: "https://ryot.dev/", + docs: "https://ryot.dev/docs/getting-started", + }, + tags: ["media", "tracking", "self-hosted"], + load: () => import("./ryot/index").then((m) => m.generate), + }, + { + id: "photoprism", + name: "Photoprism", + version: "latest", + description: + "PhotoPrism® is an AI-Powered Photos App for the Decentralized Web. It makes use of the latest technologies to tag and find pictures automatically without getting in your way.", + logo: "photoprism.svg", + links: { + github: "https://github.com/photoprism/photoprism", + website: "https://www.photoprism.app/", + docs: "https://docs.photoprism.app/", + }, + tags: ["media", "photos", "self-hosted"], + load: () => import("./photoprism/index").then((m) => m.generate), + }, + { + id: "ontime", + name: "Ontime", + version: "v3.8.0", + description: + "Ontime is browser-based application that manages event rundowns, scheduliing and cuing", + logo: "ontime.png", + links: { + github: "https://github.com/cpvalente/ontime/", + website: "https://getontime.no", + docs: "https://docs.getontime.no", + }, + tags: ["event"], + load: () => import("./ontime/index").then((m) => m.generate), + }, + { + id: "trigger", + name: "Trigger", + version: "v3", + description: + "Trigger is a platform for building event-driven applications.", + logo: "trigger.svg", + links: { + github: "https://github.com/triggerdotdev/trigger.dev", + website: "https://trigger.dev/", + docs: "https://trigger.dev/docs", + }, + tags: ["event-driven", "applications"], + load: () => import("./trigger/index").then((m) => m.generate), + }, ]; diff --git a/apps/dokploy/templates/trigger/docker-compose.yml b/apps/dokploy/templates/trigger/docker-compose.yml new file mode 100644 index 000000000..424f912bd --- /dev/null +++ b/apps/dokploy/templates/trigger/docker-compose.yml @@ -0,0 +1,107 @@ +x-webapp-env: &webapp-env + LOGIN_ORIGIN: &trigger-url ${TRIGGER_PROTOCOL:-http}://${TRIGGER_DOMAIN:-localhost:3040} + APP_ORIGIN: *trigger-url + DEV_OTEL_EXPORTER_OTLP_ENDPOINT: &trigger-otel ${TRIGGER_PROTOCOL:-http}://${TRIGGER_DOMAIN:-localhost:3040}/otel + ELECTRIC_ORIGIN: http://electric:3000 + +x-worker-env: &worker-env + PLATFORM_HOST: webapp + PLATFORM_WS_PORT: 3030 + SECURE_CONNECTION: "false" + OTEL_EXPORTER_OTLP_ENDPOINT: *trigger-otel + +volumes: + postgres-data: + redis-data: + +networks: + webapp: + +services: + webapp: + image: ghcr.io/triggerdotdev/trigger.dev:${TRIGGER_IMAGE_TAG:-v3} + restart: ${RESTART_POLICY:-unless-stopped} + env_file: + - .env + environment: + <<: *webapp-env + ports: + - ${WEBAPP_PUBLISH_IP:-127.0.0.1}:3040:3030 + depends_on: + - postgres + - redis + networks: + - webapp + + postgres: + image: postgres:${POSTGRES_IMAGE_TAG:-16} + restart: ${RESTART_POLICY:-unless-stopped} + volumes: + - postgres-data:/var/lib/postgresql/data/ + env_file: + - .env + networks: + - webapp + ports: + - ${DOCKER_PUBLISH_IP:-127.0.0.1}:5433:5432 + command: + - -c + - wal_level=logical + + redis: + image: redis:${REDIS_IMAGE_TAG:-7} + restart: ${RESTART_POLICY:-unless-stopped} + volumes: + - redis-data:/data + networks: + - webapp + ports: + - ${DOCKER_PUBLISH_IP:-127.0.0.1}:6389:6379 + + docker-provider: + image: ghcr.io/triggerdotdev/provider/docker:${TRIGGER_IMAGE_TAG:-v3} + restart: ${RESTART_POLICY:-unless-stopped} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + user: root + networks: + - webapp + depends_on: + - webapp + ports: + - ${DOCKER_PUBLISH_IP:-127.0.0.1}:9021:9020 + env_file: + - .env + environment: + <<: *worker-env + PLATFORM_SECRET: $PROVIDER_SECRET + + coordinator: + image: ghcr.io/triggerdotdev/coordinator:${TRIGGER_IMAGE_TAG:-v3} + restart: ${RESTART_POLICY:-unless-stopped} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + user: root + networks: + - webapp + depends_on: + - webapp + ports: + - ${DOCKER_PUBLISH_IP:-127.0.0.1}:9020:9020 + env_file: + - .env + environment: + <<: *worker-env + PLATFORM_SECRET: $COORDINATOR_SECRET + + electric: + image: electricsql/electric:${ELECTRIC_IMAGE_TAG:-latest} + restart: ${RESTART_POLICY:-unless-stopped} + environment: + DATABASE_URL: ${DATABASE_URL}?sslmode=disable + networks: + - webapp + depends_on: + - postgres + ports: + - ${DOCKER_PUBLISH_IP:-127.0.0.1}:3061:3000 diff --git a/apps/dokploy/templates/trigger/index.ts b/apps/dokploy/templates/trigger/index.ts new file mode 100644 index 000000000..cb6c8a706 --- /dev/null +++ b/apps/dokploy/templates/trigger/index.ts @@ -0,0 +1,56 @@ +import { + type DomainSchema, + type Schema, + type Template, + generateBase64, + generateRandomDomain, +} from "../utils"; + +export function generate(schema: Schema): Template { + const triggerDomain = generateRandomDomain(schema); + const providerSecret = generateBase64(32); + const coordinatorSecret = generateBase64(32); + const dbPassword = generateBase64(24); + const dbUser = "triggeruser"; + const dbName = "triggerdb"; + + const domains: DomainSchema[] = [ + { + host: triggerDomain, + port: 3040, + serviceName: "webapp", + }, + ]; + + const envs = [ + // Database configuration with secure credentials + `POSTGRES_USER=${dbUser}`, + `POSTGRES_PASSWORD=${dbPassword}`, + `POSTGRES_DB=${dbName}`, + `DATABASE_URL=postgresql://${dbUser}:${dbPassword}@postgres:5432/${dbName}`, + + // Trigger configuration + `TRIGGER_DOMAIN=${triggerDomain}`, + "TRIGGER_PROTOCOL=http", + + // Secrets for services + `PROVIDER_SECRET=${providerSecret}`, + `COORDINATOR_SECRET=${coordinatorSecret}`, + + // Optional configurations with defaults + "TRIGGER_IMAGE_TAG=v3", + "POSTGRES_IMAGE_TAG=16", + "REDIS_IMAGE_TAG=7", + "ELECTRIC_IMAGE_TAG=latest", + "RESTART_POLICY=unless-stopped", + + // Network bindings + "WEBAPP_PUBLISH_IP=127.0.0.1", + "DOCKER_PUBLISH_IP=127.0.0.1", + ]; + + return { + envs, + domains, + }; +} From 2f15f34a19a2958966c8142c20933827043e5b9d Mon Sep 17 00:00:00 2001 From: mafrasil Date: Fri, 6 Dec 2024 18:22:04 +0400 Subject: [PATCH 214/243] rename as triggerdotdev --- .../public/templates/{trigger.svg => triggerdotdev.svg} | 0 apps/dokploy/templates/templates.ts | 8 ++++---- .../{trigger => triggerdotdev}/docker-compose.yml | 0 .../dokploy/templates/{trigger => triggerdotdev}/index.ts | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename apps/dokploy/public/templates/{trigger.svg => triggerdotdev.svg} (100%) rename apps/dokploy/templates/{trigger => triggerdotdev}/docker-compose.yml (100%) rename apps/dokploy/templates/{trigger => triggerdotdev}/index.ts (100%) diff --git a/apps/dokploy/public/templates/trigger.svg b/apps/dokploy/public/templates/triggerdotdev.svg similarity index 100% rename from apps/dokploy/public/templates/trigger.svg rename to apps/dokploy/public/templates/triggerdotdev.svg diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 23fb26a96..bbb907a32 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -973,18 +973,18 @@ export const templates: TemplateData[] = [ load: () => import("./ontime/index").then((m) => m.generate), }, { - id: "trigger", - name: "Trigger", + id: "triggerdotdev", + name: "Trigger.dev", version: "v3", description: "Trigger is a platform for building event-driven applications.", - logo: "trigger.svg", + logo: "triggerdotdev.svg", links: { github: "https://github.com/triggerdotdev/trigger.dev", website: "https://trigger.dev/", docs: "https://trigger.dev/docs", }, tags: ["event-driven", "applications"], - load: () => import("./trigger/index").then((m) => m.generate), + load: () => import("./triggerdotdev/index").then((m) => m.generate), }, ]; diff --git a/apps/dokploy/templates/trigger/docker-compose.yml b/apps/dokploy/templates/triggerdotdev/docker-compose.yml similarity index 100% rename from apps/dokploy/templates/trigger/docker-compose.yml rename to apps/dokploy/templates/triggerdotdev/docker-compose.yml diff --git a/apps/dokploy/templates/trigger/index.ts b/apps/dokploy/templates/triggerdotdev/index.ts similarity index 100% rename from apps/dokploy/templates/trigger/index.ts rename to apps/dokploy/templates/triggerdotdev/index.ts From 5e590c1ce82ce9b1a5ec8bff28236837f91765ed Mon Sep 17 00:00:00 2001 From: mafrasil Date: Fri, 6 Dec 2024 20:35:34 +0400 Subject: [PATCH 215/243] add extra env --- apps/dokploy/templates/triggerdotdev/index.ts | 60 ++++++++++++++----- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/apps/dokploy/templates/triggerdotdev/index.ts b/apps/dokploy/templates/triggerdotdev/index.ts index cb6c8a706..7d92543a0 100644 --- a/apps/dokploy/templates/triggerdotdev/index.ts +++ b/apps/dokploy/templates/triggerdotdev/index.ts @@ -1,3 +1,4 @@ +import { Secrets } from "@/components/ui/secrets"; import { type DomainSchema, type Schema, @@ -8,8 +9,13 @@ import { export function generate(schema: Schema): Template { const triggerDomain = generateRandomDomain(schema); + + const magicLinkSecret = generateBase64(16); + const sessionSecret = generateBase64(16); + const encryptionKey = generateBase64(32); const providerSecret = generateBase64(32); const coordinatorSecret = generateBase64(32); + const dbPassword = generateBase64(24); const dbUser = "triggeruser"; const dbName = "triggerdb"; @@ -23,30 +29,56 @@ export function generate(schema: Schema): Template { ]; const envs = [ + `NODE_ENV=production`, + `RUNTIME_PLATFORM=docker-compose`, + `V3_ENABLED=true`, + + // Trigger configuration + `TRIGGER_DOMAIN=${triggerDomain}`, + `TRIGGER_PROTOCOL=http`, + // Database configuration with secure credentials `POSTGRES_USER=${dbUser}`, `POSTGRES_PASSWORD=${dbPassword}`, `POSTGRES_DB=${dbName}`, `DATABASE_URL=postgresql://${dbUser}:${dbPassword}@postgres:5432/${dbName}`, - // Trigger configuration - `TRIGGER_DOMAIN=${triggerDomain}`, - "TRIGGER_PROTOCOL=http", - - // Secrets for services + // Secrets + `MAGIC_LINK_SECRET=${magicLinkSecret}`, + `SESSION_SECRET=${sessionSecret}`, + `ENCRYPTION_KEY=${encryptionKey}`, `PROVIDER_SECRET=${providerSecret}`, `COORDINATOR_SECRET=${coordinatorSecret}`, - // Optional configurations with defaults - "TRIGGER_IMAGE_TAG=v3", - "POSTGRES_IMAGE_TAG=16", - "REDIS_IMAGE_TAG=7", - "ELECTRIC_IMAGE_TAG=latest", - "RESTART_POLICY=unless-stopped", + `# TRIGGER_TELEMETRY_DISABLED=1`, + `INTERNAL_OTEL_TRACE_DISABLED=1`, + `INTERNAL_OTEL_TRACE_LOGGING_ENABLED=0`, - // Network bindings - "WEBAPP_PUBLISH_IP=127.0.0.1", - "DOCKER_PUBLISH_IP=127.0.0.1", + `DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT=300`, + `DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT=100`, + + `# If this is set, emails that are not specified won't be able to log in`, + `# WHITELISTED_EMAILS="authorized@yahoo.com|authorized@gmail.com"`, + `# Accounts with these emails will become admins when signing up and get access to the admin panel`, + `# ADMIN_EMAILS="admin@example.com|another-admin@example.com"`, + + `# If this is set, your users will be able to log in via GitHub`, + `# AUTH_GITHUB_CLIENT_ID=`, + `# AUTH_GITHUB_CLIENT_SECRET=`, + + `# E-mail settings`, + `# Ensure the FROM_EMAIL matches what you setup with Resend.com`, + `# If these are not set, emails will be printed to the console`, + `# FROM_EMAIL=`, + `# REPLY_TO_EMAIL=`, + `# RESEND_API_KEY=`, + + `# Worker settings`, + `HTTP_SERVER_PORT=9020`, + `COORDINATOR_HOST=127.0.0.1`, + `COORDINATOR_PORT=\${HTTP_SERVER_PORT}`, + `# REGISTRY_HOST=\${DEPLOY_REGISTRY_HOST}`, + `# REGISTRY_NAMESPACE=\${DEPLOY_REGISTRY_NAMESPACE}`, ]; return { From 4c34643287a63b6ae20528780e8c34832edcfe4b Mon Sep 17 00:00:00 2001 From: mafrasil Date: Fri, 6 Dec 2024 20:38:02 +0400 Subject: [PATCH 216/243] tweak env --- apps/dokploy/templates/triggerdotdev/index.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/templates/triggerdotdev/index.ts b/apps/dokploy/templates/triggerdotdev/index.ts index 7d92543a0..2411c2b83 100644 --- a/apps/dokploy/templates/triggerdotdev/index.ts +++ b/apps/dokploy/templates/triggerdotdev/index.ts @@ -33,17 +33,17 @@ export function generate(schema: Schema): Template { `RUNTIME_PLATFORM=docker-compose`, `V3_ENABLED=true`, - // Trigger configuration + `# Domain configuration`, `TRIGGER_DOMAIN=${triggerDomain}`, `TRIGGER_PROTOCOL=http`, - // Database configuration with secure credentials + `# Database configuration with secure credentials`, `POSTGRES_USER=${dbUser}`, `POSTGRES_PASSWORD=${dbPassword}`, `POSTGRES_DB=${dbName}`, `DATABASE_URL=postgresql://${dbUser}:${dbPassword}@postgres:5432/${dbName}`, - // Secrets + `# Secrets`, `MAGIC_LINK_SECRET=${magicLinkSecret}`, `SESSION_SECRET=${sessionSecret}`, `ENCRYPTION_KEY=${encryptionKey}`, @@ -57,6 +57,11 @@ export function generate(schema: Schema): Template { `DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT=300`, `DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT=100`, + `DIRECT_URL=\${DATABASE_URL}`, + `REDIS_HOST=redis`, + `REDIS_PORT=6379`, + `REDIS_TLS_DISABLED=true`, + `# If this is set, emails that are not specified won't be able to log in`, `# WHITELISTED_EMAILS="authorized@yahoo.com|authorized@gmail.com"`, `# Accounts with these emails will become admins when signing up and get access to the admin panel`, From 556a8470540af0456d15914fd21a53cdab95f213 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 7 Dec 2024 13:04:15 -0600 Subject: [PATCH 217/243] Apply suggestions from code review --- .../templates/triggerdotdev/docker-compose.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/dokploy/templates/triggerdotdev/docker-compose.yml b/apps/dokploy/templates/triggerdotdev/docker-compose.yml index 424f912bd..f53980277 100644 --- a/apps/dokploy/templates/triggerdotdev/docker-compose.yml +++ b/apps/dokploy/templates/triggerdotdev/docker-compose.yml @@ -26,7 +26,7 @@ services: environment: <<: *webapp-env ports: - - ${WEBAPP_PUBLISH_IP:-127.0.0.1}:3040:3030 + - 3000 depends_on: - postgres - redis @@ -43,7 +43,7 @@ services: networks: - webapp ports: - - ${DOCKER_PUBLISH_IP:-127.0.0.1}:5433:5432 + - 5432 command: - -c - wal_level=logical @@ -56,7 +56,7 @@ services: networks: - webapp ports: - - ${DOCKER_PUBLISH_IP:-127.0.0.1}:6389:6379 + - 6379 docker-provider: image: ghcr.io/triggerdotdev/provider/docker:${TRIGGER_IMAGE_TAG:-v3} @@ -69,7 +69,7 @@ services: depends_on: - webapp ports: - - ${DOCKER_PUBLISH_IP:-127.0.0.1}:9021:9020 + - 9020 env_file: - .env environment: @@ -87,7 +87,7 @@ services: depends_on: - webapp ports: - - ${DOCKER_PUBLISH_IP:-127.0.0.1}:9020:9020 + - 9020 env_file: - .env environment: @@ -104,4 +104,4 @@ services: depends_on: - postgres ports: - - ${DOCKER_PUBLISH_IP:-127.0.0.1}:3061:3000 + - 3000 From 5db407b6743898f7548bdf111feb77b988197361 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 7 Dec 2024 13:05:00 -0600 Subject: [PATCH 218/243] Update apps/dokploy/templates/triggerdotdev/index.ts --- apps/dokploy/templates/triggerdotdev/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/templates/triggerdotdev/index.ts b/apps/dokploy/templates/triggerdotdev/index.ts index 2411c2b83..9a98b46cc 100644 --- a/apps/dokploy/templates/triggerdotdev/index.ts +++ b/apps/dokploy/templates/triggerdotdev/index.ts @@ -23,7 +23,7 @@ export function generate(schema: Schema): Template { const domains: DomainSchema[] = [ { host: triggerDomain, - port: 3040, + port: 3000, serviceName: "webapp", }, ]; From 1056810170ae597e9f56f4fbf82f5b738614beea Mon Sep 17 00:00:00 2001 From: idicesystem <79581397+PiyushDixit96@users.noreply.github.com> Date: Sun, 1 Dec 2024 22:07:51 +0530 Subject: [PATCH 219/243] Add support for configurable Heroku stack version Added a new environment variable HEROKU_STACK_VERSION that can be used to specify the desired Heroku stack version. Updated the buildHeroku and getHerokuCommand functions to use the specified stack version, or default to 24 if the environment variable is not set. Provided information about the available Heroku stack versions and their support details, as per the documentation from the Heroku DevCenter. https://devcenter.heroku.com/articles/stack#stack-support-details --- packages/server/src/utils/builders/heroku.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/builders/heroku.ts b/packages/server/src/utils/builders/heroku.ts index 999b5fe61..21442d9ac 100644 --- a/packages/server/src/utils/builders/heroku.ts +++ b/packages/server/src/utils/builders/heroku.ts @@ -16,13 +16,14 @@ export const buildHeroku = async ( application.project.env, ); try { + const builderVersion = env.HEROKU_STACK_VERSION || '24'; const args = [ "build", appName, "--path", buildAppDirectory, "--builder", - "heroku/builder:24", + `heroku/builder:${builderVersion}`, ]; for (const env of envVariables) { @@ -52,13 +53,14 @@ export const getHerokuCommand = ( application.project.env, ); + const builderVersion = env.HEROKU_STACK_VERSION || '24'; const args = [ "build", appName, "--path", buildAppDirectory, "--builder", - "heroku/builder:24", + `heroku/builder:${builderVersion}`, ]; for (const env of envVariables) { From 4c45be14474e9f5bef230c05366b8493294d7f77 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 7 Dec 2024 13:27:54 -0600 Subject: [PATCH 220/243] feat: add heroku version field --- .../dashboard/application/build/show.tsx | 32 + apps/dokploy/drizzle/0047_tidy_revanche.sql | 1 + apps/dokploy/drizzle/0048_flat_expediter.sql | 1 + apps/dokploy/drizzle/meta/0047_snapshot.json | 3993 ++++++++++++++++ apps/dokploy/drizzle/meta/0048_snapshot.json | 3994 +++++++++++++++++ apps/dokploy/drizzle/meta/_journal.json | 14 + .../dokploy/server/api/routers/application.ts | 1 + packages/server/src/db/schema/application.ts | 3 + packages/server/src/utils/builders/heroku.ts | 6 +- 9 files changed, 8041 insertions(+), 4 deletions(-) create mode 100644 apps/dokploy/drizzle/0047_tidy_revanche.sql create mode 100644 apps/dokploy/drizzle/0048_flat_expediter.sql create mode 100644 apps/dokploy/drizzle/meta/0047_snapshot.json create mode 100644 apps/dokploy/drizzle/meta/0048_snapshot.json diff --git a/apps/dokploy/components/dashboard/application/build/show.tsx b/apps/dokploy/components/dashboard/application/build/show.tsx index 4bd4ddd7b..edfb38ae8 100644 --- a/apps/dokploy/components/dashboard/application/build/show.tsx +++ b/apps/dokploy/components/dashboard/application/build/show.tsx @@ -41,6 +41,7 @@ const mySchema = z.discriminatedUnion("buildType", [ }), z.object({ buildType: z.literal("heroku_buildpacks"), + herokuVersion: z.string().nullable().default(""), }), z.object({ buildType: z.literal("paketo_buildpacks"), @@ -90,6 +91,13 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => { dockerBuildStage: data.dockerBuildStage || "", }), }); + } else if (data.buildType === "heroku_buildpacks") { + form.reset({ + buildType: data.buildType, + ...(data.buildType && { + herokuVersion: data.herokuVersion || "", + }), + }); } else { form.reset({ buildType: data.buildType, @@ -110,6 +118,8 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => { data.buildType === "dockerfile" ? data.dockerContextPath : null, dockerBuildStage: data.buildType === "dockerfile" ? data.dockerBuildStage : null, + herokuVersion: + data.buildType === "heroku_buildpacks" ? data.herokuVersion : null, }) .then(async () => { toast.success("Build type saved"); @@ -200,6 +210,28 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => { ); }} /> + {buildType === "heroku_buildpacks" && ( + { + return ( + + Heroku Version (Optional) + + + + + + + ); + }} + /> + )} {buildType === "dockerfile" && ( <> Date: Sat, 7 Dec 2024 13:45:50 -0600 Subject: [PATCH 221/243] refactor: improve I18N --- .circleci/config.yml | 6 +-- Dockerfile | 2 +- .../dashboard/settings/appearance-form.tsx | 53 +++++-------------- apps/dokploy/lib/languages.ts | 16 ++++++ apps/dokploy/next-i18next.config.cjs | 2 + apps/dokploy/pages/_app.tsx | 16 +----- .../public/locales/pt-br/settings.json | 2 +- apps/dokploy/utils/hooks/use-locale.ts | 22 ++------ apps/dokploy/utils/i18n.ts | 14 +++-- 9 files changed, 53 insertions(+), 80 deletions(-) create mode 100644 apps/dokploy/lib/languages.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index c5e57a973..25982d59b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -99,14 +99,14 @@ workflows: only: - main - canary - - fix/build-i18n + - refactor/enhancement-languages - build-arm64: filters: branches: only: - main - canary - - fix/build-i18n + - refactor/enhancement-languages - combine-manifests: requires: - build-amd64 @@ -116,4 +116,4 @@ workflows: only: - main - canary - - fix/build-i18n + - refactor/enhancement-languages diff --git a/Dockerfile b/Dockerfile index 838fbe4f6..ebc61a2b7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,7 @@ RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var COPY --from=build /prod/dokploy/.next ./.next COPY --from=build /prod/dokploy/dist ./dist COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs -COPY --from=build /prod/dokploy/next-i18next.config.cjs ./next-i18next.config.cjs +# COPY --from=build /prod/dokploy/next-i18next.config.cjs ./next-i18next.config.cjs COPY --from=build /prod/dokploy/public ./public COPY --from=build /prod/dokploy/package.json ./package.json COPY --from=build /prod/dokploy/drizzle ./drizzle diff --git a/apps/dokploy/components/dashboard/settings/appearance-form.tsx b/apps/dokploy/components/dashboard/settings/appearance-form.tsx index 9f740efd8..209ac5421 100644 --- a/apps/dokploy/components/dashboard/settings/appearance-form.tsx +++ b/apps/dokploy/components/dashboard/settings/appearance-form.tsx @@ -32,30 +32,15 @@ import { useTranslation } from "next-i18next"; import { useTheme } from "next-themes"; import { useEffect } from "react"; import { toast } from "sonner"; +import { Languages } from "@/lib/languages"; const appearanceFormSchema = z.object({ theme: z.enum(["light", "dark", "system"], { required_error: "Please select a theme.", }), - language: z.enum( - [ - "en", - "pl", - "ru", - "fr", - "de", - "tr", - "zh-Hant", - "kz", - "zh-Hans", - "fa", - "ko", - "pt-br", - ], - { - required_error: "Please select a language.", - }, - ), + language: z.nativeEnum(Languages, { + required_error: "Please select a language.", + }), }); type AppearanceFormValues = z.infer; @@ -63,7 +48,7 @@ type AppearanceFormValues = z.infer; // This can come from your database or API. const defaultValues: Partial = { theme: "system", - language: "en", + language: Languages.English, }; export function AppearanceForm() { @@ -188,25 +173,15 @@ export function AppearanceForm() { - {[ - { label: "English", value: "en" }, - { label: "Polski", value: "pl" }, - { label: "Русский", value: "ru" }, - { label: "Français", value: "fr" }, - { label: "Deutsch", value: "de" }, - { label: "繁體中文", value: "zh-Hant" }, - { label: "简体中文", value: "zh-Hans" }, - { label: "Türkçe", value: "tr" }, - { label: "Қазақ", value: "tr" }, - { label: "Kazakh", value: "kz" }, - { label: "Persian", value: "fa" }, - { label: "한국어", value: "ko" }, - { label: "Português", value: "pt-br" }, - ].map((preset) => ( - - {preset.label} - - ))} + {Object.keys(Languages).map((preset) => { + const value = + Languages[preset as keyof typeof Languages]; + return ( + + {preset} + + ); + })} diff --git a/apps/dokploy/lib/languages.ts b/apps/dokploy/lib/languages.ts new file mode 100644 index 000000000..59bcc097d --- /dev/null +++ b/apps/dokploy/lib/languages.ts @@ -0,0 +1,16 @@ +export enum Languages { + English = "en", + Polish = "pl", + Russian = "ru", + French = "fr", + German = "de", + ChineseTraditional = "zh-Hant", + ChineseSimplified = "zh-Hans", + Turkish = "tr", + Kazakh = "kz", + Persian = "fa", + Korean = "ko", + Portuguese = "pt-br", +} + +export type Language = keyof typeof Languages; diff --git a/apps/dokploy/next-i18next.config.cjs b/apps/dokploy/next-i18next.config.cjs index fef1df4a2..fe1b53ace 100644 --- a/apps/dokploy/next-i18next.config.cjs +++ b/apps/dokploy/next-i18next.config.cjs @@ -1,3 +1,5 @@ + + /** @type {import('next-i18next').UserConfig} */ module.exports = { fallbackLng: "en", diff --git a/apps/dokploy/pages/_app.tsx b/apps/dokploy/pages/_app.tsx index 511ac93ba..8bea3898b 100644 --- a/apps/dokploy/pages/_app.tsx +++ b/apps/dokploy/pages/_app.tsx @@ -10,6 +10,7 @@ import { Inter } from "next/font/google"; import Head from "next/head"; import Script from "next/script"; import type { ReactElement, ReactNode } from "react"; +import { Languages } from "@/lib/languages"; const inter = Inter({ subsets: ["latin"] }); @@ -71,20 +72,7 @@ export default api.withTRPC( { i18n: { defaultLocale: "en", - locales: [ - "en", - "pl", - "ru", - "fr", - "de", - "tr", - "kz", - "zh-Hant", - "zh-Hans", - "fa", - "ko", - "pt-br", - ], + locales: Object.values(Languages), localeDetection: false, }, fallbackLng: "en", diff --git a/apps/dokploy/public/locales/pt-br/settings.json b/apps/dokploy/public/locales/pt-br/settings.json index fc964be6c..7a00fd388 100644 --- a/apps/dokploy/public/locales/pt-br/settings.json +++ b/apps/dokploy/public/locales/pt-br/settings.json @@ -32,7 +32,7 @@ "settings.profile.password": "Senha", "settings.profile.avatar": "Avatar", - "settings.appearance.title": "Aparência", + "settings.appearance.title": "Aparencia", "settings.appearance.description": "Personalize o tema do seu dashboard.", "settings.appearance.theme": "Tema", "settings.appearance.themeDescription": "Selecione um tema para o dashboard", diff --git a/apps/dokploy/utils/hooks/use-locale.ts b/apps/dokploy/utils/hooks/use-locale.ts index 134418fcf..de979f7bf 100644 --- a/apps/dokploy/utils/hooks/use-locale.ts +++ b/apps/dokploy/utils/hooks/use-locale.ts @@ -1,26 +1,10 @@ +import type { Languages } from "@/lib/languages"; import Cookies from "js-cookie"; -const SUPPORTED_LOCALES = [ - "en", - "pl", - "ru", - "fr", - "de", - "tr", - "kz", - "zh-Hant", - "zh-Hans", - "fa", - "ko", - "pt-br", -] as const; - -type Locale = (typeof SUPPORTED_LOCALES)[number]; - export default function useLocale() { - const currentLocale = (Cookies.get("DOKPLOY_LOCALE") ?? "en") as Locale; + const currentLocale = (Cookies.get("DOKPLOY_LOCALE") ?? "en") as Languages; - const setLocale = (locale: Locale) => { + const setLocale = (locale: Languages) => { Cookies.set("DOKPLOY_LOCALE", locale, { expires: 365 }); window.location.reload(); }; diff --git a/apps/dokploy/utils/i18n.ts b/apps/dokploy/utils/i18n.ts index 4790f1a70..08370766e 100644 --- a/apps/dokploy/utils/i18n.ts +++ b/apps/dokploy/utils/i18n.ts @@ -5,11 +5,19 @@ export function getLocale(cookies: NextApiRequestCookies) { return locale; } -// libs/i18n.js import { serverSideTranslations as originalServerSideTranslations } from "next-i18next/serverSideTranslations"; -import nextI18NextConfig from "../next-i18next.config.cjs"; +import { Languages } from "@/lib/languages"; export const serverSideTranslations = ( locale: string, namespaces = ["common"], -) => originalServerSideTranslations(locale, namespaces, nextI18NextConfig); +) => + originalServerSideTranslations(locale, namespaces, { + fallbackLng: "en", + keySeparator: false, + i18n: { + defaultLocale: "en", + locales: Object.values(Languages), + localeDetection: false, + }, + }); From 1c1e52f77786091c17ee08291a7881440401de63 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 7 Dec 2024 13:50:38 -0600 Subject: [PATCH 222/243] fix: lint --- .../dashboard/compose/general/actions.tsx | 2 +- .../docker/terminal/docker-terminal-modal.tsx | 150 +- .../dashboard/settings/appearance-form.tsx | 2 +- .../web-server/docker-terminal-modal.tsx | 234 +- apps/dokploy/next-i18next.config.cjs | 42 +- apps/dokploy/pages/_app.tsx | 2 +- apps/dokploy/pages/api/deploy/github.ts | 237 +- apps/dokploy/public/locales/ko/settings.json | 78 +- apps/dokploy/public/locales/kz/common.json | 2 +- apps/dokploy/public/locales/kz/settings.json | 78 +- .../public/locales/pt-br/settings.json | 2 +- .../server/api/routers/notification.ts | 2 +- apps/dokploy/templates/templates.ts | 1968 ++++++++--------- apps/dokploy/templates/triggerdotdev/index.ts | 144 +- apps/dokploy/utils/i18n.ts | 2 +- .../utils/notifications/database-backup.ts | 4 +- .../src/utils/notifications/docker-cleanup.ts | 1 - 17 files changed, 1475 insertions(+), 1475 deletions(-) diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx index 0fa5ae38e..439669669 100644 --- a/apps/dokploy/components/dashboard/compose/general/actions.tsx +++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx @@ -68,7 +68,7 @@ export const ComposeActions = ({ composeId }: Props) => { Open Terminal -
+
Autodeploy import("./docker-terminal").then((e) => e.DockerTerminal), - { - ssr: false, - } + () => import("./docker-terminal").then((e) => e.DockerTerminal), + { + ssr: false, + }, ); interface Props { - containerId: string; - serverId?: string; - children?: React.ReactNode; + containerId: string; + serverId?: string; + children?: React.ReactNode; } export const DockerTerminalModal = ({ - children, - containerId, - serverId, + children, + containerId, + serverId, }: Props) => { - const [mainDialogOpen, setMainDialogOpen] = useState(false); - const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); + const [mainDialogOpen, setMainDialogOpen] = useState(false); + const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); - const handleMainDialogOpenChange = (open: boolean) => { - if (!open) { - setConfirmDialogOpen(true); - } else { - setMainDialogOpen(true); - } - }; + const handleMainDialogOpenChange = (open: boolean) => { + if (!open) { + setConfirmDialogOpen(true); + } else { + setMainDialogOpen(true); + } + }; - const handleConfirm = () => { - setConfirmDialogOpen(false); - setMainDialogOpen(false); - }; + const handleConfirm = () => { + setConfirmDialogOpen(false); + setMainDialogOpen(false); + }; - const handleCancel = () => { - setConfirmDialogOpen(false); - }; - return ( - - - e.preventDefault()} - > - {children} - - - - - Docker Terminal - - Easy way to access to docker container - - + const handleCancel = () => { + setConfirmDialogOpen(false); + }; + return ( + + + e.preventDefault()} + > + {children} + + + + + Docker Terminal + + Easy way to access to docker container + + - - - - - Are you sure you want to close the terminal? - - By clicking the confirm button, the terminal will be closed. - - - - - - - - - - - ); + + + + + + Are you sure you want to close the terminal? + + + By clicking the confirm button, the terminal will be closed. + + + + + + + + + + + ); }; diff --git a/apps/dokploy/components/dashboard/settings/appearance-form.tsx b/apps/dokploy/components/dashboard/settings/appearance-form.tsx index 209ac5421..d5b90182d 100644 --- a/apps/dokploy/components/dashboard/settings/appearance-form.tsx +++ b/apps/dokploy/components/dashboard/settings/appearance-form.tsx @@ -27,12 +27,12 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { Languages } from "@/lib/languages"; import useLocale from "@/utils/hooks/use-locale"; import { useTranslation } from "next-i18next"; import { useTheme } from "next-themes"; import { useEffect } from "react"; import { toast } from "sonner"; -import { Languages } from "@/lib/languages"; const appearanceFormSchema = z.object({ theme: z.enum(["light", "dark", "system"], { diff --git a/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx b/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx index ba3253a4a..f81be0adf 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/docker-terminal-modal.tsx @@ -1,22 +1,22 @@ import { Button } from "@/components/ui/button"; import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, } from "@/components/ui/select"; import { api } from "@/utils/api"; import { Loader2 } from "lucide-react"; @@ -25,118 +25,118 @@ import type React from "react"; import { useEffect, useState } from "react"; const Terminal = dynamic( - () => - import("@/components/dashboard/docker/terminal/docker-terminal").then( - (e) => e.DockerTerminal - ), - { - ssr: false, - } + () => + import("@/components/dashboard/docker/terminal/docker-terminal").then( + (e) => e.DockerTerminal, + ), + { + ssr: false, + }, ); interface Props { - appName: string; - children?: React.ReactNode; - serverId?: string; + appName: string; + children?: React.ReactNode; + serverId?: string; } export const DockerTerminalModal = ({ children, appName, serverId }: Props) => { - const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery( - { - appName, - serverId, - }, - { - enabled: !!appName, - } - ); - const [containerId, setContainerId] = useState(); - const [mainDialogOpen, setMainDialogOpen] = useState(false); - const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); + const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery( + { + appName, + serverId, + }, + { + enabled: !!appName, + }, + ); + const [containerId, setContainerId] = useState(); + const [mainDialogOpen, setMainDialogOpen] = useState(false); + const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); - const handleMainDialogOpenChange = (open: boolean) => { - if (!open) { - setConfirmDialogOpen(true); - } else { - setMainDialogOpen(true); - } - }; + const handleMainDialogOpenChange = (open: boolean) => { + if (!open) { + setConfirmDialogOpen(true); + } else { + setMainDialogOpen(true); + } + }; - const handleConfirm = () => { - setConfirmDialogOpen(false); - setMainDialogOpen(false); - }; + const handleConfirm = () => { + setConfirmDialogOpen(false); + setMainDialogOpen(false); + }; - const handleCancel = () => { - setConfirmDialogOpen(false); - }; + const handleCancel = () => { + setConfirmDialogOpen(false); + }; - useEffect(() => { - if (data && data?.length > 0) { - setContainerId(data[0]?.containerId); - } - }, [data]); + useEffect(() => { + if (data && data?.length > 0) { + setContainerId(data[0]?.containerId); + } + }, [data]); - return ( - - {children} - - - Docker Terminal - - Easy way to access to docker container - - - - - - - - - - Are you sure you want to close the terminal? - - - By clicking the confirm button, the terminal will be closed. - - - - - - - - - - - ); + return ( + + {children} + + + Docker Terminal + + Easy way to access to docker container + + + + + + + + + + Are you sure you want to close the terminal? + + + By clicking the confirm button, the terminal will be closed. + + + + + + + + + + + ); }; diff --git a/apps/dokploy/next-i18next.config.cjs b/apps/dokploy/next-i18next.config.cjs index fe1b53ace..9f030ecb2 100644 --- a/apps/dokploy/next-i18next.config.cjs +++ b/apps/dokploy/next-i18next.config.cjs @@ -1,25 +1,23 @@ - - /** @type {import('next-i18next').UserConfig} */ module.exports = { - fallbackLng: "en", - keySeparator: false, - i18n: { - defaultLocale: "en", - locales: [ - "en", - "pl", - "ru", - "fr", - "de", - "tr", - "kz", - "zh-Hant", - "zh-Hans", - "fa", - "ko", - "pt-br", - ], - localeDetection: false, - }, + fallbackLng: "en", + keySeparator: false, + i18n: { + defaultLocale: "en", + locales: [ + "en", + "pl", + "ru", + "fr", + "de", + "tr", + "kz", + "zh-Hant", + "zh-Hans", + "fa", + "ko", + "pt-br", + ], + localeDetection: false, + }, }; diff --git a/apps/dokploy/pages/_app.tsx b/apps/dokploy/pages/_app.tsx index 8bea3898b..e7c0befc3 100644 --- a/apps/dokploy/pages/_app.tsx +++ b/apps/dokploy/pages/_app.tsx @@ -1,6 +1,7 @@ import "@/styles/globals.css"; import { Toaster } from "@/components/ui/sonner"; +import { Languages } from "@/lib/languages"; import { api } from "@/utils/api"; import type { NextPage } from "next"; import { appWithTranslation } from "next-i18next"; @@ -10,7 +11,6 @@ import { Inter } from "next/font/google"; import Head from "next/head"; import Script from "next/script"; import type { ReactElement, ReactNode } from "react"; -import { Languages } from "@/lib/languages"; const inter = Inter({ subsets: ["latin"] }); diff --git a/apps/dokploy/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts index 7f0e7f0b0..e5ed154f8 100644 --- a/apps/dokploy/pages/api/deploy/github.ts +++ b/apps/dokploy/pages/api/deploy/github.ts @@ -10,142 +10,141 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { extractCommitMessage, extractHash } from "./[refreshToken]"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse + req: NextApiRequest, + res: NextApiResponse, ) { - const signature = req.headers["x-hub-signature-256"]; - const githubBody = req.body; + const signature = req.headers["x-hub-signature-256"]; + const githubBody = req.body; - if (!githubBody?.installation?.id) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } + if (!githubBody?.installation?.id) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } - const githubResult = await db.query.github.findFirst({ - where: eq(github.githubInstallationId, githubBody.installation.id), - }); + const githubResult = await db.query.github.findFirst({ + where: eq(github.githubInstallationId, githubBody.installation.id), + }); - if (!githubResult) { - res.status(400).json({ message: "Github Installation not found" }); - return; - } + if (!githubResult) { + res.status(400).json({ message: "Github Installation not found" }); + return; + } - if (!githubResult.githubWebhookSecret) { - res.status(400).json({ message: "Github Webhook Secret not set" }); - return; - } - const webhooks = new Webhooks({ - secret: githubResult.githubWebhookSecret, - }); + if (!githubResult.githubWebhookSecret) { + res.status(400).json({ message: "Github Webhook Secret not set" }); + return; + } + const webhooks = new Webhooks({ + secret: githubResult.githubWebhookSecret, + }); - const verified = await webhooks.verify( - JSON.stringify(githubBody), - signature as string - ); + const verified = await webhooks.verify( + JSON.stringify(githubBody), + signature as string, + ); - if (!verified) { - res.status(401).json({ message: "Unauthorized" }); - return; - } + if (!verified) { + res.status(401).json({ message: "Unauthorized" }); + return; + } - if (req.headers["x-github-event"] === "ping") { - res.status(200).json({ message: "Ping received, webhook is active" }); - return; - } + if (req.headers["x-github-event"] === "ping") { + res.status(200).json({ message: "Ping received, webhook is active" }); + return; + } - if (req.headers["x-github-event"] !== "push") { - res.status(400).json({ message: "We only accept push events" }); - return; - } + if (req.headers["x-github-event"] !== "push") { + res.status(400).json({ message: "We only accept push events" }); + return; + } - try { - const branchName = githubBody?.ref?.replace("refs/heads/", ""); - const repository = githubBody?.repository?.name; - const deploymentTitle = extractCommitMessage(req.headers, req.body); - const deploymentHash = extractHash(req.headers, req.body); - const owner = githubBody?.repository?.owner?.name; + try { + const branchName = githubBody?.ref?.replace("refs/heads/", ""); + const repository = githubBody?.repository?.name; + const deploymentTitle = extractCommitMessage(req.headers, req.body); + const deploymentHash = extractHash(req.headers, req.body); + const owner = githubBody?.repository?.owner?.name; + const apps = await db.query.applications.findMany({ + where: and( + eq(applications.sourceType, "github"), + eq(applications.autoDeploy, true), + eq(applications.branch, branchName), + eq(applications.repository, repository), + eq(applications.owner, owner), + ), + }); - const apps = await db.query.applications.findMany({ - where: and( - eq(applications.sourceType, "github"), - eq(applications.autoDeploy, true), - eq(applications.branch, branchName), - eq(applications.repository, repository), - eq(applications.owner, owner) - ), - }); + for (const app of apps) { + const jobData: DeploymentJob = { + applicationId: app.applicationId as string, + titleLog: deploymentTitle, + descriptionLog: `Hash: ${deploymentHash}`, + type: "deploy", + applicationType: "application", + server: !!app.serverId, + }; - for (const app of apps) { - const jobData: DeploymentJob = { - applicationId: app.applicationId as string, - titleLog: deploymentTitle, - descriptionLog: `Hash: ${deploymentHash}`, - type: "deploy", - applicationType: "application", - server: !!app.serverId, - }; + if (IS_CLOUD && app.serverId) { + jobData.serverId = app.serverId; + await deploy(jobData); + return true; + } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } - if (IS_CLOUD && app.serverId) { - jobData.serverId = app.serverId; - await deploy(jobData); - return true; - } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - } + const composeApps = await db.query.compose.findMany({ + where: and( + eq(compose.sourceType, "github"), + eq(compose.autoDeploy, true), + eq(compose.branch, branchName), + eq(compose.repository, repository), + eq(compose.owner, owner), + ), + }); - const composeApps = await db.query.compose.findMany({ - where: and( - eq(compose.sourceType, "github"), - eq(compose.autoDeploy, true), - eq(compose.branch, branchName), - eq(compose.repository, repository), - eq(compose.owner, owner) - ), - }); + for (const composeApp of composeApps) { + const jobData: DeploymentJob = { + composeId: composeApp.composeId as string, + titleLog: deploymentTitle, + type: "deploy", + applicationType: "compose", + descriptionLog: `Hash: ${deploymentHash}`, + server: !!composeApp.serverId, + }; - for (const composeApp of composeApps) { - const jobData: DeploymentJob = { - composeId: composeApp.composeId as string, - titleLog: deploymentTitle, - type: "deploy", - applicationType: "compose", - descriptionLog: `Hash: ${deploymentHash}`, - server: !!composeApp.serverId, - }; + if (IS_CLOUD && composeApp.serverId) { + jobData.serverId = composeApp.serverId; + await deploy(jobData); + return true; + } - if (IS_CLOUD && composeApp.serverId) { - jobData.serverId = composeApp.serverId; - await deploy(jobData); - return true; - } + await myQueue.add( + "deployments", + { ...jobData }, + { + removeOnComplete: true, + removeOnFail: true, + }, + ); + } - await myQueue.add( - "deployments", - { ...jobData }, - { - removeOnComplete: true, - removeOnFail: true, - } - ); - } + const totalApps = apps.length + composeApps.length; + const emptyApps = totalApps === 0; - const totalApps = apps.length + composeApps.length; - const emptyApps = totalApps === 0; - - if (emptyApps) { - res.status(200).json({ message: "No apps to deploy" }); - return; - } - res.status(200).json({ message: `Deployed ${totalApps} apps` }); - } catch (error) { - res.status(400).json({ message: "Error To Deploy Application", error }); - } + if (emptyApps) { + res.status(200).json({ message: "No apps to deploy" }); + return; + } + res.status(200).json({ message: `Deployed ${totalApps} apps` }); + } catch (error) { + res.status(400).json({ message: "Error To Deploy Application", error }); + } } diff --git a/apps/dokploy/public/locales/ko/settings.json b/apps/dokploy/public/locales/ko/settings.json index f0a99e4e7..db877ee6a 100644 --- a/apps/dokploy/public/locales/ko/settings.json +++ b/apps/dokploy/public/locales/ko/settings.json @@ -1,44 +1,44 @@ { - "settings.common.save": "저장", - "settings.server.domain.title": "서버 도메인", - "settings.server.domain.description": "서버 애플리케이션에 도메인을 추가합니다.", - "settings.server.domain.form.domain": "도메인", - "settings.server.domain.form.letsEncryptEmail": "Let's Encrypt 이메일", - "settings.server.domain.form.certificate.label": "인증서", - "settings.server.domain.form.certificate.placeholder": "인증서 선택", - "settings.server.domain.form.certificateOptions.none": "없음", - "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (기본)", + "settings.common.save": "저장", + "settings.server.domain.title": "서버 도메인", + "settings.server.domain.description": "서버 애플리케이션에 도메인을 추가합니다.", + "settings.server.domain.form.domain": "도메인", + "settings.server.domain.form.letsEncryptEmail": "Let's Encrypt 이메일", + "settings.server.domain.form.certificate.label": "인증서", + "settings.server.domain.form.certificate.placeholder": "인증서 선택", + "settings.server.domain.form.certificateOptions.none": "없음", + "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (기본)", - "settings.server.webServer.title": "웹 서버", - "settings.server.webServer.description": "웹 서버를 재시작하거나 정리합니다.", - "settings.server.webServer.actions": "작업", - "settings.server.webServer.reload": "재시작", - "settings.server.webServer.watchLogs": "로그 보기", - "settings.server.webServer.updateServerIp": "서버 IP 갱신", - "settings.server.webServer.server.label": "서버", - "settings.server.webServer.traefik.label": "Traefik", - "settings.server.webServer.traefik.modifyEnv": "환경 변수 수정", - "settings.server.webServer.storage.label": "저장 공간", - "settings.server.webServer.storage.cleanUnusedImages": "사용하지 않는 이미지 정리", - "settings.server.webServer.storage.cleanUnusedVolumes": "사용하지 않는 볼륨 정리", - "settings.server.webServer.storage.cleanStoppedContainers": "정지된 컨테이너 정리", - "settings.server.webServer.storage.cleanDockerBuilder": "도커 빌더 & 시스템 정리", - "settings.server.webServer.storage.cleanMonitoring": "모니터링 데이터 정리", - "settings.server.webServer.storage.cleanAll": "전체 정리", + "settings.server.webServer.title": "웹 서버", + "settings.server.webServer.description": "웹 서버를 재시작하거나 정리합니다.", + "settings.server.webServer.actions": "작업", + "settings.server.webServer.reload": "재시작", + "settings.server.webServer.watchLogs": "로그 보기", + "settings.server.webServer.updateServerIp": "서버 IP 갱신", + "settings.server.webServer.server.label": "서버", + "settings.server.webServer.traefik.label": "Traefik", + "settings.server.webServer.traefik.modifyEnv": "환경 변수 수정", + "settings.server.webServer.storage.label": "저장 공간", + "settings.server.webServer.storage.cleanUnusedImages": "사용하지 않는 이미지 정리", + "settings.server.webServer.storage.cleanUnusedVolumes": "사용하지 않는 볼륨 정리", + "settings.server.webServer.storage.cleanStoppedContainers": "정지된 컨테이너 정리", + "settings.server.webServer.storage.cleanDockerBuilder": "도커 빌더 & 시스템 정리", + "settings.server.webServer.storage.cleanMonitoring": "모니터링 데이터 정리", + "settings.server.webServer.storage.cleanAll": "전체 정리", - "settings.profile.title": "계정", - "settings.profile.description": "여기에서 프로필 세부 정보를 변경하세요.", - "settings.profile.email": "이메일", - "settings.profile.password": "비밀번호", - "settings.profile.avatar": "아바타", + "settings.profile.title": "계정", + "settings.profile.description": "여기에서 프로필 세부 정보를 변경하세요.", + "settings.profile.email": "이메일", + "settings.profile.password": "비밀번호", + "settings.profile.avatar": "아바타", - "settings.appearance.title": "외관", - "settings.appearance.description": "대시보드의 테마를 사용자 설정합니다.", - "settings.appearance.theme": "테마", - "settings.appearance.themeDescription": "대시보드 테마 선택", - "settings.appearance.themes.light": "라이트", - "settings.appearance.themes.dark": "다크", - "settings.appearance.themes.system": "시스템", - "settings.appearance.language": "언어", - "settings.appearance.languageDescription": "대시보드에서 사용할 언어 선택" + "settings.appearance.title": "외관", + "settings.appearance.description": "대시보드의 테마를 사용자 설정합니다.", + "settings.appearance.theme": "테마", + "settings.appearance.themeDescription": "대시보드 테마 선택", + "settings.appearance.themes.light": "라이트", + "settings.appearance.themes.dark": "다크", + "settings.appearance.themes.system": "시스템", + "settings.appearance.language": "언어", + "settings.appearance.languageDescription": "대시보드에서 사용할 언어 선택" } diff --git a/apps/dokploy/public/locales/kz/common.json b/apps/dokploy/public/locales/kz/common.json index 9e26dfeeb..0967ef424 100644 --- a/apps/dokploy/public/locales/kz/common.json +++ b/apps/dokploy/public/locales/kz/common.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/apps/dokploy/public/locales/kz/settings.json b/apps/dokploy/public/locales/kz/settings.json index 97403d0e2..bf8f41372 100644 --- a/apps/dokploy/public/locales/kz/settings.json +++ b/apps/dokploy/public/locales/kz/settings.json @@ -1,41 +1,41 @@ { - "settings.common.save": "Сақтау", - "settings.server.domain.title": "Сервер домені", - "settings.server.domain.description": "Dokploy сервер қолданбасына домен енгізіңіз.", - "settings.server.domain.form.domain": "Домен", - "settings.server.domain.form.letsEncryptEmail": "Let's Encrypt Эл. поштасы", - "settings.server.domain.form.certificate.label": "Сертификат", - "settings.server.domain.form.certificate.placeholder": "Сертификатты таңдаңыз", - "settings.server.domain.form.certificateOptions.none": "Жоқ", - "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Стандартты)", - "settings.server.webServer.title": "Веб-Сервер", - "settings.server.webServer.description": "Веб-серверді қайта жүктеу немесе тазалау.", - "settings.server.webServer.actions": "Әрекеттер", - "settings.server.webServer.reload": "Қайта жүктеу", - "settings.server.webServer.watchLogs": "Журналдарды қарау", - "settings.server.webServer.updateServerIp": "Сервердің IP жаңарту", - "settings.server.webServer.server.label": "Сервер", - "settings.server.webServer.traefik.label": "Traefik", - "settings.server.webServer.traefik.modifyEnv": "Env Өзгерту", - "settings.server.webServer.storage.label": "Диск кеңістігі", - "settings.server.webServer.storage.cleanUnusedImages": "Пайдаланылмаған образды тазалау", - "settings.server.webServer.storage.cleanUnusedVolumes": "Пайдаланылмаған томды тазалау", - "settings.server.webServer.storage.cleanStoppedContainers": "Тоқтатылған контейнерлерді тазалау", - "settings.server.webServer.storage.cleanDockerBuilder": "Docker Builder & Системаны тазалау", - "settings.server.webServer.storage.cleanMonitoring": "Мониторингті тазалау", - "settings.server.webServer.storage.cleanAll": "Барлығын тазалау", - "settings.profile.title": "Аккаунт", - "settings.profile.description": "Профиль мәліметтерін осы жерден өзгертіңіз.", - "settings.profile.email": "Эл. пошта", - "settings.profile.password": "Құпия сөз", - "settings.profile.avatar": "Аватар", - "settings.appearance.title": "Сыртқы түрі", - "settings.appearance.description": "Dokploy сыртқы келбетін өзгерту.", - "settings.appearance.theme": "Келбеті", - "settings.appearance.themeDescription": "Жүйе тақтасының келбетің таңдаңыз", - "settings.appearance.themes.light": "Жарық", - "settings.appearance.themes.dark": "Қараңғы", - "settings.appearance.themes.system": "Жүйелік", - "settings.appearance.language": "Тіл", - "settings.appearance.languageDescription": "Жүйе тақтасының тілің таңдаңыз" + "settings.common.save": "Сақтау", + "settings.server.domain.title": "Сервер домені", + "settings.server.domain.description": "Dokploy сервер қолданбасына домен енгізіңіз.", + "settings.server.domain.form.domain": "Домен", + "settings.server.domain.form.letsEncryptEmail": "Let's Encrypt Эл. поштасы", + "settings.server.domain.form.certificate.label": "Сертификат", + "settings.server.domain.form.certificate.placeholder": "Сертификатты таңдаңыз", + "settings.server.domain.form.certificateOptions.none": "Жоқ", + "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Стандартты)", + "settings.server.webServer.title": "Веб-Сервер", + "settings.server.webServer.description": "Веб-серверді қайта жүктеу немесе тазалау.", + "settings.server.webServer.actions": "Әрекеттер", + "settings.server.webServer.reload": "Қайта жүктеу", + "settings.server.webServer.watchLogs": "Журналдарды қарау", + "settings.server.webServer.updateServerIp": "Сервердің IP жаңарту", + "settings.server.webServer.server.label": "Сервер", + "settings.server.webServer.traefik.label": "Traefik", + "settings.server.webServer.traefik.modifyEnv": "Env Өзгерту", + "settings.server.webServer.storage.label": "Диск кеңістігі", + "settings.server.webServer.storage.cleanUnusedImages": "Пайдаланылмаған образды тазалау", + "settings.server.webServer.storage.cleanUnusedVolumes": "Пайдаланылмаған томды тазалау", + "settings.server.webServer.storage.cleanStoppedContainers": "Тоқтатылған контейнерлерді тазалау", + "settings.server.webServer.storage.cleanDockerBuilder": "Docker Builder & Системаны тазалау", + "settings.server.webServer.storage.cleanMonitoring": "Мониторингті тазалау", + "settings.server.webServer.storage.cleanAll": "Барлығын тазалау", + "settings.profile.title": "Аккаунт", + "settings.profile.description": "Профиль мәліметтерін осы жерден өзгертіңіз.", + "settings.profile.email": "Эл. пошта", + "settings.profile.password": "Құпия сөз", + "settings.profile.avatar": "Аватар", + "settings.appearance.title": "Сыртқы түрі", + "settings.appearance.description": "Dokploy сыртқы келбетін өзгерту.", + "settings.appearance.theme": "Келбеті", + "settings.appearance.themeDescription": "Жүйе тақтасының келбетің таңдаңыз", + "settings.appearance.themes.light": "Жарық", + "settings.appearance.themes.dark": "Қараңғы", + "settings.appearance.themes.system": "Жүйелік", + "settings.appearance.language": "Тіл", + "settings.appearance.languageDescription": "Жүйе тақтасының тілің таңдаңыз" } diff --git a/apps/dokploy/public/locales/pt-br/settings.json b/apps/dokploy/public/locales/pt-br/settings.json index 7a00fd388..f4d90a2f8 100644 --- a/apps/dokploy/public/locales/pt-br/settings.json +++ b/apps/dokploy/public/locales/pt-br/settings.json @@ -31,7 +31,7 @@ "settings.profile.email": "Email", "settings.profile.password": "Senha", "settings.profile.avatar": "Avatar", - + "settings.appearance.title": "Aparencia", "settings.appearance.description": "Personalize o tema do seu dashboard.", "settings.appearance.theme": "Tema", diff --git a/apps/dokploy/server/api/routers/notification.ts b/apps/dokploy/server/api/routers/notification.ts index 504e33209..951d2a10d 100644 --- a/apps/dokploy/server/api/routers/notification.ts +++ b/apps/dokploy/server/api/routers/notification.ts @@ -190,7 +190,7 @@ export const notificationRouter = createTRPCRouter({ await sendDiscordNotification(input, { title: "> `🤚` - Test Notification", description: "> Hi, From Dokploy 👋", - color: 0xf3f7f4 + color: 0xf3f7f4, }); return true; } catch (error) { diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index bbb907a32..ef418d2b6 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -1,990 +1,990 @@ import type { TemplateData } from "./types/templates-data.type"; export const templates: TemplateData[] = [ - { - id: "supabase", - name: "SupaBase", - version: "1.24.07", - description: - "The open source Firebase alternative. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications. ", - links: { - github: "https://github.com/supabase/supabase", - website: "https://supabase.com/", - docs: "https://supabase.com/docs/guides/self-hosting", - }, - logo: "supabase.svg", - load: () => import("./supabase/index").then((m) => m.generate), - tags: ["database", "firebase", "postgres"], - }, - { - id: "pocketbase", - name: "Pocketbase", - version: "v0.22.12", - description: - "Pocketbase is a self-hosted alternative to Firebase that allows you to build and host your own backend services.", - links: { - github: "https://github.com/pocketbase/pocketbase", - website: "https://pocketbase.io/", - docs: "https://pocketbase.io/docs/", - }, - logo: "pocketbase.svg", - load: () => import("./pocketbase/index").then((m) => m.generate), - tags: ["database", "cms", "headless"], - }, - { - id: "plausible", - name: "Plausible", - version: "v2.1.0", - description: - "Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.", - logo: "plausible.svg", - links: { - github: "https://github.com/plausible/plausible", - website: "https://plausible.io/", - docs: "https://plausible.io/docs", - }, - tags: ["analytics"], - load: () => import("./plausible/index").then((m) => m.generate), - }, - { - id: "calcom", - name: "Calcom", - version: "v2.7.6", - description: - "Calcom is a open source alternative to Calendly that allows to create scheduling and booking services.", + { + id: "supabase", + name: "SupaBase", + version: "1.24.07", + description: + "The open source Firebase alternative. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications. ", + links: { + github: "https://github.com/supabase/supabase", + website: "https://supabase.com/", + docs: "https://supabase.com/docs/guides/self-hosting", + }, + logo: "supabase.svg", + load: () => import("./supabase/index").then((m) => m.generate), + tags: ["database", "firebase", "postgres"], + }, + { + id: "pocketbase", + name: "Pocketbase", + version: "v0.22.12", + description: + "Pocketbase is a self-hosted alternative to Firebase that allows you to build and host your own backend services.", + links: { + github: "https://github.com/pocketbase/pocketbase", + website: "https://pocketbase.io/", + docs: "https://pocketbase.io/docs/", + }, + logo: "pocketbase.svg", + load: () => import("./pocketbase/index").then((m) => m.generate), + tags: ["database", "cms", "headless"], + }, + { + id: "plausible", + name: "Plausible", + version: "v2.1.0", + description: + "Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.", + logo: "plausible.svg", + links: { + github: "https://github.com/plausible/plausible", + website: "https://plausible.io/", + docs: "https://plausible.io/docs", + }, + tags: ["analytics"], + load: () => import("./plausible/index").then((m) => m.generate), + }, + { + id: "calcom", + name: "Calcom", + version: "v2.7.6", + description: + "Calcom is a open source alternative to Calendly that allows to create scheduling and booking services.", - links: { - github: "https://github.com/calcom/cal.com", - website: "https://cal.com/", - docs: "https://cal.com/docs", - }, - logo: "calcom.jpg", - tags: ["scheduling", "booking"], - load: () => import("./calcom/index").then((m) => m.generate), - }, - { - id: "grafana", - name: "Grafana", - version: "9.5.20", - description: - "Grafana is an open source platform for data visualization and monitoring.", - logo: "grafana.svg", - links: { - github: "https://github.com/grafana/grafana", - website: "https://grafana.com/", - docs: "https://grafana.com/docs/", - }, - tags: ["monitoring"], - load: () => import("./grafana/index").then((m) => m.generate), - }, - { - id: "directus", - name: "Directus", - version: "11.0.2", - description: - "Directus is an open source headless CMS that provides an API-first solution for building custom backends.", - logo: "directus.jpg", - links: { - github: "https://github.com/directus/directus", - website: "https://directus.io/", - docs: "https://docs.directus.io/", - }, - tags: ["cms"], - load: () => import("./directus/index").then((m) => m.generate), - }, - { - id: "baserow", - name: "Baserow", - version: "1.25.2", - description: - "Baserow is an open source database management tool that allows you to create and manage databases.", - logo: "baserow.webp", - links: { - github: "https://github.com/Baserow/baserow", - website: "https://baserow.io/", - docs: "https://baserow.io/docs/index", - }, - tags: ["database"], - load: () => import("./baserow/index").then((m) => m.generate), - }, - { - id: "ghost", - name: "Ghost", - version: "5.0.0", - description: - "Ghost is a free and open source, professional publishing platform built on a modern Node.js technology stack.", - logo: "ghost.jpeg", - links: { - github: "https://github.com/TryGhost/Ghost", - website: "https://ghost.org/", - docs: "https://ghost.org/docs/", - }, - tags: ["cms"], - load: () => import("./ghost/index").then((m) => m.generate), - }, - { - id: "uptime-kuma", - name: "Uptime Kuma", - version: "1.23.15", - description: - "Uptime Kuma is a free and open source monitoring tool that allows you to monitor your websites and applications.", - logo: "uptime-kuma.png", - links: { - github: "https://github.com/louislam/uptime-kuma", - website: "https://uptime.kuma.pet/", - docs: "https://github.com/louislam/uptime-kuma/wiki", - }, - tags: ["monitoring"], - load: () => import("./uptime-kuma/index").then((m) => m.generate), - }, - { - id: "n8n", - name: "n8n", - version: "1.48.1", - description: - "n8n is an open source low-code platform for automating workflows and integrations.", - logo: "n8n.png", - links: { - github: "https://github.com/n8n-io/n8n", - website: "https://n8n.io/", - docs: "https://docs.n8n.io/", - }, - tags: ["automation"], - load: () => import("./n8n/index").then((m) => m.generate), - }, - { - id: "wordpress", - name: "Wordpress", - version: "5.8.3", - description: - "Wordpress is a free and open source content management system (CMS) for publishing and managing websites.", - logo: "wordpress.png", - links: { - github: "https://github.com/WordPress/WordPress", - website: "https://wordpress.org/", - docs: "https://wordpress.org/documentation/", - }, - tags: ["cms"], - load: () => import("./wordpress/index").then((m) => m.generate), - }, - { - id: "odoo", - name: "Odoo", - version: "16.0", - description: - "Odoo is a free and open source business management software that helps you manage your company's operations.", - logo: "odoo.png", - links: { - github: "https://github.com/odoo/odoo", - website: "https://odoo.com/", - docs: "https://www.odoo.com/documentation/", - }, - tags: ["cms"], - load: () => import("./odoo/index").then((m) => m.generate), - }, - { - id: "appsmith", - name: "Appsmith", - version: "v1.29", - description: - "Appsmith is a free and open source platform for building internal tools and applications.", - logo: "appsmith.png", - links: { - github: "https://github.com/appsmithorg/appsmith", - website: "https://appsmith.com/", - docs: "https://docs.appsmith.com/", - }, - tags: ["cms"], - load: () => import("./appsmith/index").then((m) => m.generate), - }, - { - id: "excalidraw", - name: "Excalidraw", - version: "latest", - description: - "Excalidraw is a free and open source online diagramming tool that lets you easily create and share beautiful diagrams.", - logo: "excalidraw.jpg", - links: { - github: "https://github.com/excalidraw/excalidraw", - website: "https://excalidraw.com/", - docs: "https://docs.excalidraw.com/", - }, - tags: ["drawing"], - load: () => import("./excalidraw/index").then((m) => m.generate), - }, - { - id: "documenso", - name: "Documenso", - version: "v1.5.6", - description: - "Documenso is the open source alternative to DocuSign for signing documents digitally", - links: { - github: "https://github.com/documenso/documenso", - website: "https://documenso.com/", - docs: "https://documenso.com/docs", - }, - logo: "documenso.png", - tags: ["document-signing"], - load: () => import("./documenso/index").then((m) => m.generate), - }, - { - id: "nocodb", - name: "NocoDB", - version: "0.257.2", - description: - "NocoDB is an opensource Airtable alternative that turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart spreadsheet.", + links: { + github: "https://github.com/calcom/cal.com", + website: "https://cal.com/", + docs: "https://cal.com/docs", + }, + logo: "calcom.jpg", + tags: ["scheduling", "booking"], + load: () => import("./calcom/index").then((m) => m.generate), + }, + { + id: "grafana", + name: "Grafana", + version: "9.5.20", + description: + "Grafana is an open source platform for data visualization and monitoring.", + logo: "grafana.svg", + links: { + github: "https://github.com/grafana/grafana", + website: "https://grafana.com/", + docs: "https://grafana.com/docs/", + }, + tags: ["monitoring"], + load: () => import("./grafana/index").then((m) => m.generate), + }, + { + id: "directus", + name: "Directus", + version: "11.0.2", + description: + "Directus is an open source headless CMS that provides an API-first solution for building custom backends.", + logo: "directus.jpg", + links: { + github: "https://github.com/directus/directus", + website: "https://directus.io/", + docs: "https://docs.directus.io/", + }, + tags: ["cms"], + load: () => import("./directus/index").then((m) => m.generate), + }, + { + id: "baserow", + name: "Baserow", + version: "1.25.2", + description: + "Baserow is an open source database management tool that allows you to create and manage databases.", + logo: "baserow.webp", + links: { + github: "https://github.com/Baserow/baserow", + website: "https://baserow.io/", + docs: "https://baserow.io/docs/index", + }, + tags: ["database"], + load: () => import("./baserow/index").then((m) => m.generate), + }, + { + id: "ghost", + name: "Ghost", + version: "5.0.0", + description: + "Ghost is a free and open source, professional publishing platform built on a modern Node.js technology stack.", + logo: "ghost.jpeg", + links: { + github: "https://github.com/TryGhost/Ghost", + website: "https://ghost.org/", + docs: "https://ghost.org/docs/", + }, + tags: ["cms"], + load: () => import("./ghost/index").then((m) => m.generate), + }, + { + id: "uptime-kuma", + name: "Uptime Kuma", + version: "1.23.15", + description: + "Uptime Kuma is a free and open source monitoring tool that allows you to monitor your websites and applications.", + logo: "uptime-kuma.png", + links: { + github: "https://github.com/louislam/uptime-kuma", + website: "https://uptime.kuma.pet/", + docs: "https://github.com/louislam/uptime-kuma/wiki", + }, + tags: ["monitoring"], + load: () => import("./uptime-kuma/index").then((m) => m.generate), + }, + { + id: "n8n", + name: "n8n", + version: "1.48.1", + description: + "n8n is an open source low-code platform for automating workflows and integrations.", + logo: "n8n.png", + links: { + github: "https://github.com/n8n-io/n8n", + website: "https://n8n.io/", + docs: "https://docs.n8n.io/", + }, + tags: ["automation"], + load: () => import("./n8n/index").then((m) => m.generate), + }, + { + id: "wordpress", + name: "Wordpress", + version: "5.8.3", + description: + "Wordpress is a free and open source content management system (CMS) for publishing and managing websites.", + logo: "wordpress.png", + links: { + github: "https://github.com/WordPress/WordPress", + website: "https://wordpress.org/", + docs: "https://wordpress.org/documentation/", + }, + tags: ["cms"], + load: () => import("./wordpress/index").then((m) => m.generate), + }, + { + id: "odoo", + name: "Odoo", + version: "16.0", + description: + "Odoo is a free and open source business management software that helps you manage your company's operations.", + logo: "odoo.png", + links: { + github: "https://github.com/odoo/odoo", + website: "https://odoo.com/", + docs: "https://www.odoo.com/documentation/", + }, + tags: ["cms"], + load: () => import("./odoo/index").then((m) => m.generate), + }, + { + id: "appsmith", + name: "Appsmith", + version: "v1.29", + description: + "Appsmith is a free and open source platform for building internal tools and applications.", + logo: "appsmith.png", + links: { + github: "https://github.com/appsmithorg/appsmith", + website: "https://appsmith.com/", + docs: "https://docs.appsmith.com/", + }, + tags: ["cms"], + load: () => import("./appsmith/index").then((m) => m.generate), + }, + { + id: "excalidraw", + name: "Excalidraw", + version: "latest", + description: + "Excalidraw is a free and open source online diagramming tool that lets you easily create and share beautiful diagrams.", + logo: "excalidraw.jpg", + links: { + github: "https://github.com/excalidraw/excalidraw", + website: "https://excalidraw.com/", + docs: "https://docs.excalidraw.com/", + }, + tags: ["drawing"], + load: () => import("./excalidraw/index").then((m) => m.generate), + }, + { + id: "documenso", + name: "Documenso", + version: "v1.5.6", + description: + "Documenso is the open source alternative to DocuSign for signing documents digitally", + links: { + github: "https://github.com/documenso/documenso", + website: "https://documenso.com/", + docs: "https://documenso.com/docs", + }, + logo: "documenso.png", + tags: ["document-signing"], + load: () => import("./documenso/index").then((m) => m.generate), + }, + { + id: "nocodb", + name: "NocoDB", + version: "0.257.2", + description: + "NocoDB is an opensource Airtable alternative that turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart spreadsheet.", - links: { - github: "https://github.com/nocodb/nocodb", - website: "https://nocodb.com/", - docs: "https://docs.nocodb.com/", - }, - logo: "nocodb.png", - tags: ["database", "spreadsheet", "low-code", "nocode"], - load: () => import("./nocodb/index").then((m) => m.generate), - }, - { - id: "meilisearch", - name: "Meilisearch", - version: "v1.8.3", - description: - "Meilisearch is a free and open-source search engine that allows you to easily add search functionality to your web applications.", - logo: "meilisearch.png", - links: { - github: "https://github.com/meilisearch/meilisearch", - website: "https://www.meilisearch.com/", - docs: "https://docs.meilisearch.com/", - }, - tags: ["search"], - load: () => import("./meilisearch/index").then((m) => m.generate), - }, - { - id: "phpmyadmin", - name: "Phpmyadmin", - version: "5.2.1", - description: - "Phpmyadmin is a free and open-source web interface for MySQL and MariaDB that allows you to manage your databases.", - logo: "phpmyadmin.png", - links: { - github: "https://github.com/phpmyadmin/phpmyadmin", - website: "https://www.phpmyadmin.net/", - docs: "https://www.phpmyadmin.net/docs/", - }, - tags: ["database"], - load: () => import("./phpmyadmin/index").then((m) => m.generate), - }, - { - id: "rocketchat", - name: "Rocketchat", - version: "6.9.2", - description: - "Rocket.Chat is a free and open-source web chat platform that allows you to build and manage your own chat applications.", - logo: "rocketchat.png", - links: { - github: "https://github.com/RocketChat/Rocket.Chat", - website: "https://rocket.chat/", - docs: "https://rocket.chat/docs/", - }, - tags: ["chat"], - load: () => import("./rocketchat/index").then((m) => m.generate), - }, - { - id: "minio", - name: "Minio", - description: - "Minio is an open source object storage server compatible with Amazon S3 cloud storage service.", - logo: "minio.png", - version: "latest", - links: { - github: "https://github.com/minio/minio", - website: "https://minio.io/", - docs: "https://docs.minio.io/", - }, - tags: ["storage"], - load: () => import("./minio/index").then((m) => m.generate), - }, - { - id: "metabase", - name: "Metabase", - version: "v0.50.8", - description: - "Metabase is an open source business intelligence tool that allows you to ask questions and visualize data.", - logo: "metabase.png", - links: { - github: "https://github.com/metabase/metabase", - website: "https://www.metabase.com/", - docs: "https://www.metabase.com/docs/", - }, - tags: ["database", "dashboard"], - load: () => import("./metabase/index").then((m) => m.generate), - }, - { - id: "glitchtip", - name: "Glitchtip", - version: "v4.0", - description: "Glitchtip is simple, open source error tracking", - logo: "glitchtip.png", - links: { - github: "https://gitlab.com/glitchtip/", - website: "https://glitchtip.com/", - docs: "https://glitchtip.com/documentation", - }, - tags: ["hosting"], - load: () => import("./glitchtip/index").then((m) => m.generate), - }, - { - id: "open-webui", - name: "Open WebUI", - version: "v0.3.7", - description: - "Open WebUI is a free and open source chatgpt alternative. Open WebUI is an extensible, feature-rich, and user-friendly self-hosted WebUI designed to operate entirely offline. It supports various LLM runners, including Ollama and OpenAI-compatible APIs. The template include ollama and webui services.", - logo: "open-webui.png", - links: { - github: "https://github.com/open-webui/open-webui", - website: "https://openwebui.com/", - docs: "https://docs.openwebui.com/", - }, - tags: ["chat"], - load: () => import("./open-webui/index").then((m) => m.generate), - }, - { - id: "listmonk", - name: "Listmonk", - version: "v3.0.0", - description: - "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard.", - logo: "listmonk.png", - links: { - github: "https://github.com/knadh/listmonk", - website: "https://listmonk.app/", - docs: "https://listmonk.app/docs/", - }, - tags: ["email", "newsletter", "mailing-list"], - load: () => import("./listmonk/index").then((m) => m.generate), - }, - { - id: "doublezero", - name: "Double Zero", - version: "v0.2.1", - description: - "00 is a self hostable SES dashboard for sending and monitoring emails with AWS", - logo: "doublezero.svg", - links: { - github: "https://github.com/technomancy-dev/00", - website: "https://www.double-zero.cloud/", - docs: "https://github.com/technomancy-dev/00", - }, - tags: ["email"], - load: () => import("./doublezero/index").then((m) => m.generate), - }, - { - id: "umami", - name: "Umami", - version: "v2.14.0", - description: - "Umami is a simple, fast, privacy-focused alternative to Google Analytics.", - logo: "umami.png", - links: { - github: "https://github.com/umami-software/umami", - website: "https://umami.is", - docs: "https://umami.is/docs", - }, - tags: ["analytics"], - load: () => import("./umami/index").then((m) => m.generate), - }, - { - id: "jellyfin", - name: "jellyfin", - version: "v10.9.7", - description: - "Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. ", - logo: "jellyfin.svg", - links: { - github: "https://github.com/jellyfin/jellyfin", - website: "https://jellyfin.org/", - docs: "https://jellyfin.org/docs/", - }, - tags: ["media system"], - load: () => import("./jellyfin/index").then((m) => m.generate), - }, - { - id: "teable", - name: "teable", - version: "v1.3.1-alpha-build.460", - description: - "Teable is a Super fast, Real-time, Professional, Developer friendly, No-code database built on Postgres. It uses a simple, spreadsheet-like interface to create complex enterprise-level database applications. Unlock efficient app development with no-code, free from the hurdles of data security and scalability.", - logo: "teable.png", - links: { - github: "https://github.com/teableio/teable", - website: "https://teable.io/", - docs: "https://help.teable.io/", - }, - tags: ["database", "spreadsheet", "low-code", "nocode"], - load: () => import("./teable/index").then((m) => m.generate), - }, - { - id: "zipline", - name: "Zipline", - version: "v3.7.9", - description: - "A ShareX/file upload server that is easy to use, packed with features, and with an easy setup!", - logo: "zipline.png", - links: { - github: "https://github.com/diced/zipline", - website: "https://zipline.diced.sh/", - docs: "https://zipline.diced.sh/docs/", - }, - tags: ["media system", "storage"], - load: () => import("./zipline/index").then((m) => m.generate), - }, - { - id: "soketi", - name: "Soketi", - version: "v1.6.1-16", - description: - "Soketi is your simple, fast, and resilient open-source WebSockets server.", - logo: "soketi.png", - links: { - github: "https://github.com/soketi/soketi", - website: "https://soketi.app/", - docs: "https://docs.soketi.app/", - }, - tags: ["chat"], - load: () => import("./soketi/index").then((m) => m.generate), - }, - { - id: "aptabase", - name: "Aptabase", - version: "v1.0.0", - description: - "Aptabase is a self-hosted web analytics platform that lets you track website traffic and user behavior.", - logo: "aptabase.svg", - links: { - github: "https://github.com/aptabase/aptabase", - website: "https://aptabase.com/", - docs: "https://github.com/aptabase/aptabase/blob/main/README.md", - }, - tags: ["analytics", "self-hosted"], - load: () => import("./aptabase/index").then((m) => m.generate), - }, - { - id: "typebot", - name: "Typebot", - version: "2.27.0", - description: "Typebot is an open-source chatbot builder platform.", - logo: "typebot.svg", - links: { - github: "https://github.com/baptisteArno/typebot.io", - website: "https://typebot.io/", - docs: "https://docs.typebot.io/get-started/introduction", - }, - tags: ["chatbot", "builder", "open-source"], - load: () => import("./typebot/index").then((m) => m.generate), - }, - { - id: "gitea", - name: "Gitea", - version: "1.22.3", - description: - "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD.", - logo: "gitea.png", - links: { - github: "https://github.com/go-gitea/gitea.git", - website: "https://gitea.com/", - docs: "https://docs.gitea.com/installation/install-with-docker", - }, - tags: ["self-hosted", "storage"], - load: () => import("./gitea/index").then((m) => m.generate), - }, - { - id: "roundcube", - name: "Roundcube", - version: "1.6.9", - description: - "Free and open source webmail software for the masses, written in PHP.", - logo: "roundcube.svg", - links: { - github: "https://github.com/roundcube/roundcubemail", - website: "https://roundcube.net/", - docs: "https://roundcube.net/about/", - }, - tags: ["self-hosted", "email", "webmail"], - load: () => import("./roundcube/index").then((m) => m.generate), - }, - { - id: "filebrowser", - name: "File Browser", - version: "2.31.2", - description: - "Filebrowser is a standalone file manager for uploading, deleting, previewing, renaming, and editing files, with support for multiple users, each with their own directory.", - logo: "filebrowser.svg", - links: { - github: "https://github.com/filebrowser/filebrowser", - website: "https://filebrowser.org/", - docs: "https://filebrowser.org/", - }, - tags: ["file", "manager"], - load: () => import("./filebrowser/index").then((m) => m.generate), - }, - { - id: "tolgee", - name: "Tolgee", - version: "v3.80.4", - description: - "Developer & translator friendly web-based localization platform", - logo: "tolgee.svg", - links: { - github: "https://github.com/tolgee/tolgee-platform", - website: "https://tolgee.io", - docs: "https://tolgee.io/platform", - }, - tags: ["self-hosted", "i18n", "localization", "translations"], - load: () => import("./tolgee/index").then((m) => m.generate), - }, - { - id: "portainer", - name: "Portainer", - version: "2.21.4", - description: - "Portainer is a container management tool for deploying, troubleshooting, and securing applications across cloud, data centers, and IoT.", - logo: "portainer.svg", - links: { - github: "https://github.com/portainer/portainer", - website: "https://www.portainer.io/", - docs: "https://docs.portainer.io/", - }, - tags: ["cloud", "monitoring"], - load: () => import("./portainer/index").then((m) => m.generate), - }, - { - id: "influxdb", - name: "InfluxDB", - version: "2.7.10", - description: - "InfluxDB 2.7 is the platform purpose-built to collect, store, process and visualize time series data.", - logo: "influxdb.png", - links: { - github: "https://github.com/influxdata/influxdb", - website: "https://www.influxdata.com/", - docs: "https://docs.influxdata.com/influxdb/v2/", - }, - tags: ["self-hosted", "open-source", "storage", "database"], - load: () => import("./influxdb/index").then((m) => m.generate), - }, - { - id: "infisical", - name: "Infisical", - version: "0.90.1", - description: - "All-in-one platform to securely manage application configuration and secrets across your team and infrastructure.", - logo: "infisical.jpg", - links: { - github: "https://github.com/Infisical/infisical", - website: "https://infisical.com/", - docs: "https://infisical.com/docs/documentation/getting-started/introduction", - }, - tags: ["self-hosted", "open-source"], - load: () => import("./infisical/index").then((m) => m.generate), - }, - { - id: "docmost", - name: "Docmost", - version: "0.4.1", - description: - "Docmost, is an open-source collaborative wiki and documentation software.", - logo: "docmost.png", - links: { - github: "https://github.com/docmost/docmost", - website: "https://docmost.com/", - docs: "https://docmost.com/docs/", - }, - tags: ["self-hosted", "open-source", "manager"], - load: () => import("./docmost/index").then((m) => m.generate), - }, - { - id: "vaultwarden", - name: "Vaultwarden", - version: "1.32.3", - description: - "Unofficial Bitwarden compatible server written in Rust, formerly known as bitwarden_rs", - logo: "vaultwarden.svg", - links: { - github: "https://github.com/dani-garcia/vaultwarden", - website: "", - docs: "https://github.com/dani-garcia/vaultwarden/wiki", - }, - tags: ["open-source"], - load: () => import("./vaultwarden/index").then((m) => m.generate), - }, - { - id: "hi-events", - name: "Hi.events", - version: "0.8.0-beta.1", - description: - "Hi.Events is a self-hosted event management and ticket selling platform that allows you to create, manage and promote events easily.", - logo: "hi-events.svg", - links: { - github: "https://github.com/HiEventsDev/hi.events", - website: "https://hi.events/", - docs: "https://hi.events/docs", - }, - tags: ["self-hosted", "open-source", "manager"], - load: () => import("./hi-events/index").then((m) => m.generate), - }, - { - id: "windows", - name: "Windows (dockerized)", - version: "4.00", - description: "Windows inside a Docker container.", - logo: "windows.png", - links: { - github: "https://github.com/dockur/windows", - website: "", - docs: "https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-use-it", - }, - tags: ["self-hosted", "open-source", "os"], - load: () => import("./windows/index").then((m) => m.generate), - }, - { - id: "macos", - name: "MacOS (dockerized)", - version: "1.14", - description: "MacOS inside a Docker container.", - logo: "macos.png", - links: { - github: "https://github.com/dockur/macos", - website: "", - docs: "https://github.com/dockur/macos?tab=readme-ov-file#how-do-i-use-it", - }, - tags: ["self-hosted", "open-source", "os"], - load: () => import("./macos/index").then((m) => m.generate), - }, - { - id: "coder", - name: "Coder", - version: "2.15.3", - description: - "Coder is an open-source cloud development environment (CDE) that you host in your cloud or on-premises.", - logo: "coder.svg", - links: { - github: "https://github.com/coder/coder", - website: "https://coder.com/", - docs: "https://coder.com/docs", - }, - tags: ["self-hosted", "open-source", "builder"], - load: () => import("./coder/index").then((m) => m.generate), - }, - { - id: "stirling", - name: "Stirling PDF", - version: "0.30.1", - description: "A locally hosted one-stop shop for all your PDF needs", - logo: "stirling.svg", - links: { - github: "https://github.com/Stirling-Tools/Stirling-PDF", - website: "https://www.stirlingpdf.com/", - docs: "https://docs.stirlingpdf.com/", - }, - tags: ["pdf", "tools"], - load: () => import("./stirling/index").then((m) => m.generate), - }, - { - id: "lobe-chat", - name: "Lobe Chat", - version: "v1.26.1", - description: "Lobe Chat - an open-source, modern-design AI chat framework.", - logo: "lobe-chat.png", - links: { - github: "https://github.com/lobehub/lobe-chat", - website: "https://chat-preview.lobehub.com/", - docs: "https://lobehub.com/docs/self-hosting/platform/docker-compose", - }, - tags: ["IA", "chat"], - load: () => import("./lobe-chat/index").then((m) => m.generate), - }, - { - id: "peppermint", - name: "Peppermint", - version: "latest", - description: - "Peppermint is a modern, open-source API development platform that helps you build, test and document your APIs.", - logo: "peppermint.svg", - links: { - github: "https://github.com/Peppermint-Lab/peppermint", - website: "https://peppermint.sh/", - docs: "https://docs.peppermint.sh/", - }, - tags: ["api", "development", "documentation"], - load: () => import("./peppermint/index").then((m) => m.generate), - }, - { - id: "windmill", - name: "Windmill", - version: "latest", - description: - "A developer platform to build production-grade workflows and internal apps. Open-source alternative to Airplane, Retool, and GitHub Actions.", - logo: "windmill.svg", - links: { - github: "https://github.com/windmill-labs/windmill", - website: "https://www.windmill.dev/", - docs: "https://docs.windmill.dev/", - }, - tags: ["workflow", "automation", "development"], - load: () => import("./windmill/index").then((m) => m.generate), - }, - { - id: "activepieces", - name: "Activepieces", - version: "0.35.0", - description: - "Open-source no-code business automation tool. An alternative to Zapier, Make.com, and Tray.", - logo: "activepieces.svg", - links: { - github: "https://github.com/activepieces/activepieces", - website: "https://www.activepieces.com/", - docs: "https://www.activepieces.com/docs", - }, - tags: ["automation", "workflow", "no-code"], - load: () => import("./activepieces/index").then((m) => m.generate), - }, - { - id: "invoiceshelf", - name: "InvoiceShelf", - version: "latest", - description: - "InvoiceShelf is a self-hosted open source invoicing system for freelancers and small businesses.", - logo: "invoiceshelf.png", - links: { - github: "https://github.com/InvoiceShelf/invoiceshelf", - website: "https://invoiceshelf.com", - docs: "https://github.com/InvoiceShelf/invoiceshelf#readme", - }, - tags: ["invoice", "business", "finance"], - load: () => import("./invoiceshelf/index").then((m) => m.generate), - }, - { - id: "postiz", - name: "Postiz", - version: "latest", - description: - "Postiz is a modern, open-source platform for managing and publishing content across multiple channels.", - logo: "postiz.png", - links: { - github: "https://github.com/gitroomhq/postiz", - website: "https://postiz.com", - docs: "https://docs.postiz.com", - }, - tags: ["cms", "content-management", "publishing"], - load: () => import("./postiz/index").then((m) => m.generate), - }, - { - id: "slash", - name: "Slash", - version: "latest", - description: - "Slash is a modern, self-hosted bookmarking service and link shortener that helps you organize and share your favorite links.", - logo: "slash.png", - links: { - github: "https://github.com/yourselfhosted/slash", - website: "https://github.com/yourselfhosted/slash#readme", - docs: "https://github.com/yourselfhosted/slash/wiki", - }, - tags: ["bookmarks", "link-shortener", "self-hosted"], - load: () => import("./slash/index").then((m) => m.generate), - }, - { - id: "discord-tickets", - name: "Discord Tickets", - version: "4.0.21", - description: - "An open-source Discord bot for creating and managing support ticket channels.", - logo: "discord-tickets.png", - links: { - github: "https://github.com/discord-tickets/bot", - website: "https://discordtickets.app", - docs: "https://discordtickets.app/self-hosting/installation/docker/", - }, - tags: ["discord", "tickets", "support"], - load: () => import("./discord-tickets/index").then((m) => m.generate), - }, - { - id: "nextcloud-aio", - name: "Nextcloud All in One", - version: "30.0.2", - description: - "Nextcloud (AIO) is a self-hosted file storage and sync platform with powerful collaboration capabilities. It integrates Files, Talk, Groupware, Office, Assistant and more into a single platform for remote work and data protection.", - logo: "nextcloud-aio.svg", - links: { - github: "https://github.com/nextcloud/docker", - website: "https://nextcloud.com/", - docs: "https://docs.nextcloud.com/", - }, - tags: ["file", "sync"], - load: () => import("./nextcloud-aio/index").then((m) => m.generate), - }, - { - id: "blender", - name: "Blender", - version: "latest", - description: - "Blender is a free and open-source 3D creation suite. It supports the entire 3D pipeline—modeling, rigging, animation, simulation, rendering, compositing and motion tracking, video editing and 2D animation pipeline.", - logo: "blender.svg", - links: { - github: "https://github.com/linuxserver/docker-blender", - website: "https://www.blender.org/", - docs: "https://docs.blender.org/", - }, - tags: ["3d", "rendering", "animation"], - load: () => import("./blender/index").then((m) => m.generate), - }, - { - id: "heyform", - name: "HeyForm", - version: "latest", - description: - "Allows anyone to create engaging conversational forms for surveys, questionnaires, quizzes, and polls. No coding skills required.", - logo: "heyform.svg", - links: { - github: "https://github.com/heyform/heyform", - website: "https://heyform.net", - docs: "https://docs.heyform.net", - }, - tags: ["form", "builder", "questionnaire", "quiz", "survey"], - load: () => import("./heyform/index").then((m) => m.generate), - }, - { - id: "chatwoot", - name: "Chatwoot", - version: "v3.14.1", - description: - "Open-source customer engagement platform that provides a shared inbox for teams, live chat, and omnichannel support.", - logo: "chatwoot.svg", - links: { - github: "https://github.com/chatwoot/chatwoot", - website: "https://www.chatwoot.com", - docs: "https://www.chatwoot.com/docs", - }, - tags: ["support", "chat", "customer-service"], - load: () => import("./chatwoot/index").then((m) => m.generate), - }, - { - id: "discourse", - name: "Discourse", - version: "3.3.2", - description: - "Discourse is a modern forum software for your community. Use it as a mailing list, discussion forum, or long-form chat room.", - logo: "discourse.svg", - links: { - github: "https://github.com/discourse/discourse", - website: "https://www.discourse.org/", - docs: "https://meta.discourse.org/", - }, - tags: ["forum", "community", "discussion"], - load: () => import("./discourse/index").then((m) => m.generate), - }, - { - id: "immich", - name: "Immich", - version: "v1.121.0", - description: - "High performance self-hosted photo and video backup solution directly from your mobile phone.", - logo: "immich.svg", - links: { - github: "https://github.com/immich-app/immich", - website: "https://immich.app/", - docs: "https://immich.app/docs/overview/introduction", - }, - tags: ["photos", "videos", "backup", "media"], - load: () => import("./immich/index").then((m) => m.generate), - }, - { - id: "twenty", - name: "Twenty CRM", - version: "latest", - description: - "Twenty is a modern CRM offering a powerful spreadsheet interface and open-source alternative to Salesforce.", - logo: "twenty.svg", - links: { - github: "https://github.com/twentyhq/twenty", - website: "https://twenty.com", - docs: "https://docs.twenty.com", - }, - tags: ["crm", "sales", "business"], - load: () => import("./twenty/index").then((m) => m.generate), - }, - { - id: "yourls", - name: "YOURLS", - version: "1.9.2", - description: - "YOURLS (Your Own URL Shortener) is a set of PHP scripts that will allow you to run your own URL shortening service (a la TinyURL or Bitly).", - logo: "yourls.svg", - links: { - github: "https://github.com/YOURLS/YOURLS", - website: "https://yourls.org/", - docs: "https://yourls.org/#documentation", - }, - tags: ["url-shortener", "php"], - load: () => import("./yourls/index").then((m) => m.generate), - }, - { - id: "ryot", - name: "Ryot", - version: "v7.10", - description: - "A self-hosted platform for tracking various media types including movies, TV shows, video games, books, audiobooks, and more.", - logo: "ryot.png", - links: { - github: "https://github.com/IgnisDa/ryot", - website: "https://ryot.dev/", - docs: "https://ryot.dev/docs/getting-started", - }, - tags: ["media", "tracking", "self-hosted"], - load: () => import("./ryot/index").then((m) => m.generate), - }, - { - id: "photoprism", - name: "Photoprism", - version: "latest", - description: - "PhotoPrism® is an AI-Powered Photos App for the Decentralized Web. It makes use of the latest technologies to tag and find pictures automatically without getting in your way.", - logo: "photoprism.svg", - links: { - github: "https://github.com/photoprism/photoprism", - website: "https://www.photoprism.app/", - docs: "https://docs.photoprism.app/", - }, - tags: ["media", "photos", "self-hosted"], - load: () => import("./photoprism/index").then((m) => m.generate), - }, - { - id: "ontime", - name: "Ontime", - version: "v3.8.0", - description: - "Ontime is browser-based application that manages event rundowns, scheduliing and cuing", - logo: "ontime.png", - links: { - github: "https://github.com/cpvalente/ontime/", - website: "https://getontime.no", - docs: "https://docs.getontime.no", - }, - tags: ["event"], - load: () => import("./ontime/index").then((m) => m.generate), - }, - { - id: "triggerdotdev", - name: "Trigger.dev", - version: "v3", - description: - "Trigger is a platform for building event-driven applications.", - logo: "triggerdotdev.svg", - links: { - github: "https://github.com/triggerdotdev/trigger.dev", - website: "https://trigger.dev/", - docs: "https://trigger.dev/docs", - }, - tags: ["event-driven", "applications"], - load: () => import("./triggerdotdev/index").then((m) => m.generate), - }, + links: { + github: "https://github.com/nocodb/nocodb", + website: "https://nocodb.com/", + docs: "https://docs.nocodb.com/", + }, + logo: "nocodb.png", + tags: ["database", "spreadsheet", "low-code", "nocode"], + load: () => import("./nocodb/index").then((m) => m.generate), + }, + { + id: "meilisearch", + name: "Meilisearch", + version: "v1.8.3", + description: + "Meilisearch is a free and open-source search engine that allows you to easily add search functionality to your web applications.", + logo: "meilisearch.png", + links: { + github: "https://github.com/meilisearch/meilisearch", + website: "https://www.meilisearch.com/", + docs: "https://docs.meilisearch.com/", + }, + tags: ["search"], + load: () => import("./meilisearch/index").then((m) => m.generate), + }, + { + id: "phpmyadmin", + name: "Phpmyadmin", + version: "5.2.1", + description: + "Phpmyadmin is a free and open-source web interface for MySQL and MariaDB that allows you to manage your databases.", + logo: "phpmyadmin.png", + links: { + github: "https://github.com/phpmyadmin/phpmyadmin", + website: "https://www.phpmyadmin.net/", + docs: "https://www.phpmyadmin.net/docs/", + }, + tags: ["database"], + load: () => import("./phpmyadmin/index").then((m) => m.generate), + }, + { + id: "rocketchat", + name: "Rocketchat", + version: "6.9.2", + description: + "Rocket.Chat is a free and open-source web chat platform that allows you to build and manage your own chat applications.", + logo: "rocketchat.png", + links: { + github: "https://github.com/RocketChat/Rocket.Chat", + website: "https://rocket.chat/", + docs: "https://rocket.chat/docs/", + }, + tags: ["chat"], + load: () => import("./rocketchat/index").then((m) => m.generate), + }, + { + id: "minio", + name: "Minio", + description: + "Minio is an open source object storage server compatible with Amazon S3 cloud storage service.", + logo: "minio.png", + version: "latest", + links: { + github: "https://github.com/minio/minio", + website: "https://minio.io/", + docs: "https://docs.minio.io/", + }, + tags: ["storage"], + load: () => import("./minio/index").then((m) => m.generate), + }, + { + id: "metabase", + name: "Metabase", + version: "v0.50.8", + description: + "Metabase is an open source business intelligence tool that allows you to ask questions and visualize data.", + logo: "metabase.png", + links: { + github: "https://github.com/metabase/metabase", + website: "https://www.metabase.com/", + docs: "https://www.metabase.com/docs/", + }, + tags: ["database", "dashboard"], + load: () => import("./metabase/index").then((m) => m.generate), + }, + { + id: "glitchtip", + name: "Glitchtip", + version: "v4.0", + description: "Glitchtip is simple, open source error tracking", + logo: "glitchtip.png", + links: { + github: "https://gitlab.com/glitchtip/", + website: "https://glitchtip.com/", + docs: "https://glitchtip.com/documentation", + }, + tags: ["hosting"], + load: () => import("./glitchtip/index").then((m) => m.generate), + }, + { + id: "open-webui", + name: "Open WebUI", + version: "v0.3.7", + description: + "Open WebUI is a free and open source chatgpt alternative. Open WebUI is an extensible, feature-rich, and user-friendly self-hosted WebUI designed to operate entirely offline. It supports various LLM runners, including Ollama and OpenAI-compatible APIs. The template include ollama and webui services.", + logo: "open-webui.png", + links: { + github: "https://github.com/open-webui/open-webui", + website: "https://openwebui.com/", + docs: "https://docs.openwebui.com/", + }, + tags: ["chat"], + load: () => import("./open-webui/index").then((m) => m.generate), + }, + { + id: "listmonk", + name: "Listmonk", + version: "v3.0.0", + description: + "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard.", + logo: "listmonk.png", + links: { + github: "https://github.com/knadh/listmonk", + website: "https://listmonk.app/", + docs: "https://listmonk.app/docs/", + }, + tags: ["email", "newsletter", "mailing-list"], + load: () => import("./listmonk/index").then((m) => m.generate), + }, + { + id: "doublezero", + name: "Double Zero", + version: "v0.2.1", + description: + "00 is a self hostable SES dashboard for sending and monitoring emails with AWS", + logo: "doublezero.svg", + links: { + github: "https://github.com/technomancy-dev/00", + website: "https://www.double-zero.cloud/", + docs: "https://github.com/technomancy-dev/00", + }, + tags: ["email"], + load: () => import("./doublezero/index").then((m) => m.generate), + }, + { + id: "umami", + name: "Umami", + version: "v2.14.0", + description: + "Umami is a simple, fast, privacy-focused alternative to Google Analytics.", + logo: "umami.png", + links: { + github: "https://github.com/umami-software/umami", + website: "https://umami.is", + docs: "https://umami.is/docs", + }, + tags: ["analytics"], + load: () => import("./umami/index").then((m) => m.generate), + }, + { + id: "jellyfin", + name: "jellyfin", + version: "v10.9.7", + description: + "Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. ", + logo: "jellyfin.svg", + links: { + github: "https://github.com/jellyfin/jellyfin", + website: "https://jellyfin.org/", + docs: "https://jellyfin.org/docs/", + }, + tags: ["media system"], + load: () => import("./jellyfin/index").then((m) => m.generate), + }, + { + id: "teable", + name: "teable", + version: "v1.3.1-alpha-build.460", + description: + "Teable is a Super fast, Real-time, Professional, Developer friendly, No-code database built on Postgres. It uses a simple, spreadsheet-like interface to create complex enterprise-level database applications. Unlock efficient app development with no-code, free from the hurdles of data security and scalability.", + logo: "teable.png", + links: { + github: "https://github.com/teableio/teable", + website: "https://teable.io/", + docs: "https://help.teable.io/", + }, + tags: ["database", "spreadsheet", "low-code", "nocode"], + load: () => import("./teable/index").then((m) => m.generate), + }, + { + id: "zipline", + name: "Zipline", + version: "v3.7.9", + description: + "A ShareX/file upload server that is easy to use, packed with features, and with an easy setup!", + logo: "zipline.png", + links: { + github: "https://github.com/diced/zipline", + website: "https://zipline.diced.sh/", + docs: "https://zipline.diced.sh/docs/", + }, + tags: ["media system", "storage"], + load: () => import("./zipline/index").then((m) => m.generate), + }, + { + id: "soketi", + name: "Soketi", + version: "v1.6.1-16", + description: + "Soketi is your simple, fast, and resilient open-source WebSockets server.", + logo: "soketi.png", + links: { + github: "https://github.com/soketi/soketi", + website: "https://soketi.app/", + docs: "https://docs.soketi.app/", + }, + tags: ["chat"], + load: () => import("./soketi/index").then((m) => m.generate), + }, + { + id: "aptabase", + name: "Aptabase", + version: "v1.0.0", + description: + "Aptabase is a self-hosted web analytics platform that lets you track website traffic and user behavior.", + logo: "aptabase.svg", + links: { + github: "https://github.com/aptabase/aptabase", + website: "https://aptabase.com/", + docs: "https://github.com/aptabase/aptabase/blob/main/README.md", + }, + tags: ["analytics", "self-hosted"], + load: () => import("./aptabase/index").then((m) => m.generate), + }, + { + id: "typebot", + name: "Typebot", + version: "2.27.0", + description: "Typebot is an open-source chatbot builder platform.", + logo: "typebot.svg", + links: { + github: "https://github.com/baptisteArno/typebot.io", + website: "https://typebot.io/", + docs: "https://docs.typebot.io/get-started/introduction", + }, + tags: ["chatbot", "builder", "open-source"], + load: () => import("./typebot/index").then((m) => m.generate), + }, + { + id: "gitea", + name: "Gitea", + version: "1.22.3", + description: + "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD.", + logo: "gitea.png", + links: { + github: "https://github.com/go-gitea/gitea.git", + website: "https://gitea.com/", + docs: "https://docs.gitea.com/installation/install-with-docker", + }, + tags: ["self-hosted", "storage"], + load: () => import("./gitea/index").then((m) => m.generate), + }, + { + id: "roundcube", + name: "Roundcube", + version: "1.6.9", + description: + "Free and open source webmail software for the masses, written in PHP.", + logo: "roundcube.svg", + links: { + github: "https://github.com/roundcube/roundcubemail", + website: "https://roundcube.net/", + docs: "https://roundcube.net/about/", + }, + tags: ["self-hosted", "email", "webmail"], + load: () => import("./roundcube/index").then((m) => m.generate), + }, + { + id: "filebrowser", + name: "File Browser", + version: "2.31.2", + description: + "Filebrowser is a standalone file manager for uploading, deleting, previewing, renaming, and editing files, with support for multiple users, each with their own directory.", + logo: "filebrowser.svg", + links: { + github: "https://github.com/filebrowser/filebrowser", + website: "https://filebrowser.org/", + docs: "https://filebrowser.org/", + }, + tags: ["file", "manager"], + load: () => import("./filebrowser/index").then((m) => m.generate), + }, + { + id: "tolgee", + name: "Tolgee", + version: "v3.80.4", + description: + "Developer & translator friendly web-based localization platform", + logo: "tolgee.svg", + links: { + github: "https://github.com/tolgee/tolgee-platform", + website: "https://tolgee.io", + docs: "https://tolgee.io/platform", + }, + tags: ["self-hosted", "i18n", "localization", "translations"], + load: () => import("./tolgee/index").then((m) => m.generate), + }, + { + id: "portainer", + name: "Portainer", + version: "2.21.4", + description: + "Portainer is a container management tool for deploying, troubleshooting, and securing applications across cloud, data centers, and IoT.", + logo: "portainer.svg", + links: { + github: "https://github.com/portainer/portainer", + website: "https://www.portainer.io/", + docs: "https://docs.portainer.io/", + }, + tags: ["cloud", "monitoring"], + load: () => import("./portainer/index").then((m) => m.generate), + }, + { + id: "influxdb", + name: "InfluxDB", + version: "2.7.10", + description: + "InfluxDB 2.7 is the platform purpose-built to collect, store, process and visualize time series data.", + logo: "influxdb.png", + links: { + github: "https://github.com/influxdata/influxdb", + website: "https://www.influxdata.com/", + docs: "https://docs.influxdata.com/influxdb/v2/", + }, + tags: ["self-hosted", "open-source", "storage", "database"], + load: () => import("./influxdb/index").then((m) => m.generate), + }, + { + id: "infisical", + name: "Infisical", + version: "0.90.1", + description: + "All-in-one platform to securely manage application configuration and secrets across your team and infrastructure.", + logo: "infisical.jpg", + links: { + github: "https://github.com/Infisical/infisical", + website: "https://infisical.com/", + docs: "https://infisical.com/docs/documentation/getting-started/introduction", + }, + tags: ["self-hosted", "open-source"], + load: () => import("./infisical/index").then((m) => m.generate), + }, + { + id: "docmost", + name: "Docmost", + version: "0.4.1", + description: + "Docmost, is an open-source collaborative wiki and documentation software.", + logo: "docmost.png", + links: { + github: "https://github.com/docmost/docmost", + website: "https://docmost.com/", + docs: "https://docmost.com/docs/", + }, + tags: ["self-hosted", "open-source", "manager"], + load: () => import("./docmost/index").then((m) => m.generate), + }, + { + id: "vaultwarden", + name: "Vaultwarden", + version: "1.32.3", + description: + "Unofficial Bitwarden compatible server written in Rust, formerly known as bitwarden_rs", + logo: "vaultwarden.svg", + links: { + github: "https://github.com/dani-garcia/vaultwarden", + website: "", + docs: "https://github.com/dani-garcia/vaultwarden/wiki", + }, + tags: ["open-source"], + load: () => import("./vaultwarden/index").then((m) => m.generate), + }, + { + id: "hi-events", + name: "Hi.events", + version: "0.8.0-beta.1", + description: + "Hi.Events is a self-hosted event management and ticket selling platform that allows you to create, manage and promote events easily.", + logo: "hi-events.svg", + links: { + github: "https://github.com/HiEventsDev/hi.events", + website: "https://hi.events/", + docs: "https://hi.events/docs", + }, + tags: ["self-hosted", "open-source", "manager"], + load: () => import("./hi-events/index").then((m) => m.generate), + }, + { + id: "windows", + name: "Windows (dockerized)", + version: "4.00", + description: "Windows inside a Docker container.", + logo: "windows.png", + links: { + github: "https://github.com/dockur/windows", + website: "", + docs: "https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-use-it", + }, + tags: ["self-hosted", "open-source", "os"], + load: () => import("./windows/index").then((m) => m.generate), + }, + { + id: "macos", + name: "MacOS (dockerized)", + version: "1.14", + description: "MacOS inside a Docker container.", + logo: "macos.png", + links: { + github: "https://github.com/dockur/macos", + website: "", + docs: "https://github.com/dockur/macos?tab=readme-ov-file#how-do-i-use-it", + }, + tags: ["self-hosted", "open-source", "os"], + load: () => import("./macos/index").then((m) => m.generate), + }, + { + id: "coder", + name: "Coder", + version: "2.15.3", + description: + "Coder is an open-source cloud development environment (CDE) that you host in your cloud or on-premises.", + logo: "coder.svg", + links: { + github: "https://github.com/coder/coder", + website: "https://coder.com/", + docs: "https://coder.com/docs", + }, + tags: ["self-hosted", "open-source", "builder"], + load: () => import("./coder/index").then((m) => m.generate), + }, + { + id: "stirling", + name: "Stirling PDF", + version: "0.30.1", + description: "A locally hosted one-stop shop for all your PDF needs", + logo: "stirling.svg", + links: { + github: "https://github.com/Stirling-Tools/Stirling-PDF", + website: "https://www.stirlingpdf.com/", + docs: "https://docs.stirlingpdf.com/", + }, + tags: ["pdf", "tools"], + load: () => import("./stirling/index").then((m) => m.generate), + }, + { + id: "lobe-chat", + name: "Lobe Chat", + version: "v1.26.1", + description: "Lobe Chat - an open-source, modern-design AI chat framework.", + logo: "lobe-chat.png", + links: { + github: "https://github.com/lobehub/lobe-chat", + website: "https://chat-preview.lobehub.com/", + docs: "https://lobehub.com/docs/self-hosting/platform/docker-compose", + }, + tags: ["IA", "chat"], + load: () => import("./lobe-chat/index").then((m) => m.generate), + }, + { + id: "peppermint", + name: "Peppermint", + version: "latest", + description: + "Peppermint is a modern, open-source API development platform that helps you build, test and document your APIs.", + logo: "peppermint.svg", + links: { + github: "https://github.com/Peppermint-Lab/peppermint", + website: "https://peppermint.sh/", + docs: "https://docs.peppermint.sh/", + }, + tags: ["api", "development", "documentation"], + load: () => import("./peppermint/index").then((m) => m.generate), + }, + { + id: "windmill", + name: "Windmill", + version: "latest", + description: + "A developer platform to build production-grade workflows and internal apps. Open-source alternative to Airplane, Retool, and GitHub Actions.", + logo: "windmill.svg", + links: { + github: "https://github.com/windmill-labs/windmill", + website: "https://www.windmill.dev/", + docs: "https://docs.windmill.dev/", + }, + tags: ["workflow", "automation", "development"], + load: () => import("./windmill/index").then((m) => m.generate), + }, + { + id: "activepieces", + name: "Activepieces", + version: "0.35.0", + description: + "Open-source no-code business automation tool. An alternative to Zapier, Make.com, and Tray.", + logo: "activepieces.svg", + links: { + github: "https://github.com/activepieces/activepieces", + website: "https://www.activepieces.com/", + docs: "https://www.activepieces.com/docs", + }, + tags: ["automation", "workflow", "no-code"], + load: () => import("./activepieces/index").then((m) => m.generate), + }, + { + id: "invoiceshelf", + name: "InvoiceShelf", + version: "latest", + description: + "InvoiceShelf is a self-hosted open source invoicing system for freelancers and small businesses.", + logo: "invoiceshelf.png", + links: { + github: "https://github.com/InvoiceShelf/invoiceshelf", + website: "https://invoiceshelf.com", + docs: "https://github.com/InvoiceShelf/invoiceshelf#readme", + }, + tags: ["invoice", "business", "finance"], + load: () => import("./invoiceshelf/index").then((m) => m.generate), + }, + { + id: "postiz", + name: "Postiz", + version: "latest", + description: + "Postiz is a modern, open-source platform for managing and publishing content across multiple channels.", + logo: "postiz.png", + links: { + github: "https://github.com/gitroomhq/postiz", + website: "https://postiz.com", + docs: "https://docs.postiz.com", + }, + tags: ["cms", "content-management", "publishing"], + load: () => import("./postiz/index").then((m) => m.generate), + }, + { + id: "slash", + name: "Slash", + version: "latest", + description: + "Slash is a modern, self-hosted bookmarking service and link shortener that helps you organize and share your favorite links.", + logo: "slash.png", + links: { + github: "https://github.com/yourselfhosted/slash", + website: "https://github.com/yourselfhosted/slash#readme", + docs: "https://github.com/yourselfhosted/slash/wiki", + }, + tags: ["bookmarks", "link-shortener", "self-hosted"], + load: () => import("./slash/index").then((m) => m.generate), + }, + { + id: "discord-tickets", + name: "Discord Tickets", + version: "4.0.21", + description: + "An open-source Discord bot for creating and managing support ticket channels.", + logo: "discord-tickets.png", + links: { + github: "https://github.com/discord-tickets/bot", + website: "https://discordtickets.app", + docs: "https://discordtickets.app/self-hosting/installation/docker/", + }, + tags: ["discord", "tickets", "support"], + load: () => import("./discord-tickets/index").then((m) => m.generate), + }, + { + id: "nextcloud-aio", + name: "Nextcloud All in One", + version: "30.0.2", + description: + "Nextcloud (AIO) is a self-hosted file storage and sync platform with powerful collaboration capabilities. It integrates Files, Talk, Groupware, Office, Assistant and more into a single platform for remote work and data protection.", + logo: "nextcloud-aio.svg", + links: { + github: "https://github.com/nextcloud/docker", + website: "https://nextcloud.com/", + docs: "https://docs.nextcloud.com/", + }, + tags: ["file", "sync"], + load: () => import("./nextcloud-aio/index").then((m) => m.generate), + }, + { + id: "blender", + name: "Blender", + version: "latest", + description: + "Blender is a free and open-source 3D creation suite. It supports the entire 3D pipeline—modeling, rigging, animation, simulation, rendering, compositing and motion tracking, video editing and 2D animation pipeline.", + logo: "blender.svg", + links: { + github: "https://github.com/linuxserver/docker-blender", + website: "https://www.blender.org/", + docs: "https://docs.blender.org/", + }, + tags: ["3d", "rendering", "animation"], + load: () => import("./blender/index").then((m) => m.generate), + }, + { + id: "heyform", + name: "HeyForm", + version: "latest", + description: + "Allows anyone to create engaging conversational forms for surveys, questionnaires, quizzes, and polls. No coding skills required.", + logo: "heyform.svg", + links: { + github: "https://github.com/heyform/heyform", + website: "https://heyform.net", + docs: "https://docs.heyform.net", + }, + tags: ["form", "builder", "questionnaire", "quiz", "survey"], + load: () => import("./heyform/index").then((m) => m.generate), + }, + { + id: "chatwoot", + name: "Chatwoot", + version: "v3.14.1", + description: + "Open-source customer engagement platform that provides a shared inbox for teams, live chat, and omnichannel support.", + logo: "chatwoot.svg", + links: { + github: "https://github.com/chatwoot/chatwoot", + website: "https://www.chatwoot.com", + docs: "https://www.chatwoot.com/docs", + }, + tags: ["support", "chat", "customer-service"], + load: () => import("./chatwoot/index").then((m) => m.generate), + }, + { + id: "discourse", + name: "Discourse", + version: "3.3.2", + description: + "Discourse is a modern forum software for your community. Use it as a mailing list, discussion forum, or long-form chat room.", + logo: "discourse.svg", + links: { + github: "https://github.com/discourse/discourse", + website: "https://www.discourse.org/", + docs: "https://meta.discourse.org/", + }, + tags: ["forum", "community", "discussion"], + load: () => import("./discourse/index").then((m) => m.generate), + }, + { + id: "immich", + name: "Immich", + version: "v1.121.0", + description: + "High performance self-hosted photo and video backup solution directly from your mobile phone.", + logo: "immich.svg", + links: { + github: "https://github.com/immich-app/immich", + website: "https://immich.app/", + docs: "https://immich.app/docs/overview/introduction", + }, + tags: ["photos", "videos", "backup", "media"], + load: () => import("./immich/index").then((m) => m.generate), + }, + { + id: "twenty", + name: "Twenty CRM", + version: "latest", + description: + "Twenty is a modern CRM offering a powerful spreadsheet interface and open-source alternative to Salesforce.", + logo: "twenty.svg", + links: { + github: "https://github.com/twentyhq/twenty", + website: "https://twenty.com", + docs: "https://docs.twenty.com", + }, + tags: ["crm", "sales", "business"], + load: () => import("./twenty/index").then((m) => m.generate), + }, + { + id: "yourls", + name: "YOURLS", + version: "1.9.2", + description: + "YOURLS (Your Own URL Shortener) is a set of PHP scripts that will allow you to run your own URL shortening service (a la TinyURL or Bitly).", + logo: "yourls.svg", + links: { + github: "https://github.com/YOURLS/YOURLS", + website: "https://yourls.org/", + docs: "https://yourls.org/#documentation", + }, + tags: ["url-shortener", "php"], + load: () => import("./yourls/index").then((m) => m.generate), + }, + { + id: "ryot", + name: "Ryot", + version: "v7.10", + description: + "A self-hosted platform for tracking various media types including movies, TV shows, video games, books, audiobooks, and more.", + logo: "ryot.png", + links: { + github: "https://github.com/IgnisDa/ryot", + website: "https://ryot.dev/", + docs: "https://ryot.dev/docs/getting-started", + }, + tags: ["media", "tracking", "self-hosted"], + load: () => import("./ryot/index").then((m) => m.generate), + }, + { + id: "photoprism", + name: "Photoprism", + version: "latest", + description: + "PhotoPrism® is an AI-Powered Photos App for the Decentralized Web. It makes use of the latest technologies to tag and find pictures automatically without getting in your way.", + logo: "photoprism.svg", + links: { + github: "https://github.com/photoprism/photoprism", + website: "https://www.photoprism.app/", + docs: "https://docs.photoprism.app/", + }, + tags: ["media", "photos", "self-hosted"], + load: () => import("./photoprism/index").then((m) => m.generate), + }, + { + id: "ontime", + name: "Ontime", + version: "v3.8.0", + description: + "Ontime is browser-based application that manages event rundowns, scheduliing and cuing", + logo: "ontime.png", + links: { + github: "https://github.com/cpvalente/ontime/", + website: "https://getontime.no", + docs: "https://docs.getontime.no", + }, + tags: ["event"], + load: () => import("./ontime/index").then((m) => m.generate), + }, + { + id: "triggerdotdev", + name: "Trigger.dev", + version: "v3", + description: + "Trigger is a platform for building event-driven applications.", + logo: "triggerdotdev.svg", + links: { + github: "https://github.com/triggerdotdev/trigger.dev", + website: "https://trigger.dev/", + docs: "https://trigger.dev/docs", + }, + tags: ["event-driven", "applications"], + load: () => import("./triggerdotdev/index").then((m) => m.generate), + }, ]; diff --git a/apps/dokploy/templates/triggerdotdev/index.ts b/apps/dokploy/templates/triggerdotdev/index.ts index 9a98b46cc..7b894acba 100644 --- a/apps/dokploy/templates/triggerdotdev/index.ts +++ b/apps/dokploy/templates/triggerdotdev/index.ts @@ -1,93 +1,93 @@ import { Secrets } from "@/components/ui/secrets"; import { - type DomainSchema, - type Schema, - type Template, - generateBase64, - generateRandomDomain, + type DomainSchema, + type Schema, + type Template, + generateBase64, + generateRandomDomain, } from "../utils"; export function generate(schema: Schema): Template { - const triggerDomain = generateRandomDomain(schema); + const triggerDomain = generateRandomDomain(schema); - const magicLinkSecret = generateBase64(16); - const sessionSecret = generateBase64(16); - const encryptionKey = generateBase64(32); - const providerSecret = generateBase64(32); - const coordinatorSecret = generateBase64(32); + const magicLinkSecret = generateBase64(16); + const sessionSecret = generateBase64(16); + const encryptionKey = generateBase64(32); + const providerSecret = generateBase64(32); + const coordinatorSecret = generateBase64(32); - const dbPassword = generateBase64(24); - const dbUser = "triggeruser"; - const dbName = "triggerdb"; + const dbPassword = generateBase64(24); + const dbUser = "triggeruser"; + const dbName = "triggerdb"; - const domains: DomainSchema[] = [ - { - host: triggerDomain, - port: 3000, - serviceName: "webapp", - }, - ]; + const domains: DomainSchema[] = [ + { + host: triggerDomain, + port: 3000, + serviceName: "webapp", + }, + ]; - const envs = [ - `NODE_ENV=production`, - `RUNTIME_PLATFORM=docker-compose`, - `V3_ENABLED=true`, + const envs = [ + "NODE_ENV=production", + "RUNTIME_PLATFORM=docker-compose", + "V3_ENABLED=true", - `# Domain configuration`, - `TRIGGER_DOMAIN=${triggerDomain}`, - `TRIGGER_PROTOCOL=http`, + "# Domain configuration", + `TRIGGER_DOMAIN=${triggerDomain}`, + "TRIGGER_PROTOCOL=http", - `# Database configuration with secure credentials`, - `POSTGRES_USER=${dbUser}`, - `POSTGRES_PASSWORD=${dbPassword}`, - `POSTGRES_DB=${dbName}`, - `DATABASE_URL=postgresql://${dbUser}:${dbPassword}@postgres:5432/${dbName}`, + "# Database configuration with secure credentials", + `POSTGRES_USER=${dbUser}`, + `POSTGRES_PASSWORD=${dbPassword}`, + `POSTGRES_DB=${dbName}`, + `DATABASE_URL=postgresql://${dbUser}:${dbPassword}@postgres:5432/${dbName}`, - `# Secrets`, - `MAGIC_LINK_SECRET=${magicLinkSecret}`, - `SESSION_SECRET=${sessionSecret}`, - `ENCRYPTION_KEY=${encryptionKey}`, - `PROVIDER_SECRET=${providerSecret}`, - `COORDINATOR_SECRET=${coordinatorSecret}`, + "# Secrets", + `MAGIC_LINK_SECRET=${magicLinkSecret}`, + `SESSION_SECRET=${sessionSecret}`, + `ENCRYPTION_KEY=${encryptionKey}`, + `PROVIDER_SECRET=${providerSecret}`, + `COORDINATOR_SECRET=${coordinatorSecret}`, - `# TRIGGER_TELEMETRY_DISABLED=1`, - `INTERNAL_OTEL_TRACE_DISABLED=1`, - `INTERNAL_OTEL_TRACE_LOGGING_ENABLED=0`, + "# TRIGGER_TELEMETRY_DISABLED=1", + "INTERNAL_OTEL_TRACE_DISABLED=1", + "INTERNAL_OTEL_TRACE_LOGGING_ENABLED=0", - `DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT=300`, - `DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT=100`, + "DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT=300", + "DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT=100", - `DIRECT_URL=\${DATABASE_URL}`, - `REDIS_HOST=redis`, - `REDIS_PORT=6379`, - `REDIS_TLS_DISABLED=true`, + "DIRECT_URL=${DATABASE_URL}", + "REDIS_HOST=redis", + "REDIS_PORT=6379", + "REDIS_TLS_DISABLED=true", - `# If this is set, emails that are not specified won't be able to log in`, - `# WHITELISTED_EMAILS="authorized@yahoo.com|authorized@gmail.com"`, - `# Accounts with these emails will become admins when signing up and get access to the admin panel`, - `# ADMIN_EMAILS="admin@example.com|another-admin@example.com"`, + "# If this is set, emails that are not specified won't be able to log in", + '# WHITELISTED_EMAILS="authorized@yahoo.com|authorized@gmail.com"', + "# Accounts with these emails will become admins when signing up and get access to the admin panel", + '# ADMIN_EMAILS="admin@example.com|another-admin@example.com"', - `# If this is set, your users will be able to log in via GitHub`, - `# AUTH_GITHUB_CLIENT_ID=`, - `# AUTH_GITHUB_CLIENT_SECRET=`, + "# If this is set, your users will be able to log in via GitHub", + "# AUTH_GITHUB_CLIENT_ID=", + "# AUTH_GITHUB_CLIENT_SECRET=", - `# E-mail settings`, - `# Ensure the FROM_EMAIL matches what you setup with Resend.com`, - `# If these are not set, emails will be printed to the console`, - `# FROM_EMAIL=`, - `# REPLY_TO_EMAIL=`, - `# RESEND_API_KEY=`, + "# E-mail settings", + "# Ensure the FROM_EMAIL matches what you setup with Resend.com", + "# If these are not set, emails will be printed to the console", + "# FROM_EMAIL=", + "# REPLY_TO_EMAIL=", + "# RESEND_API_KEY=", - `# Worker settings`, - `HTTP_SERVER_PORT=9020`, - `COORDINATOR_HOST=127.0.0.1`, - `COORDINATOR_PORT=\${HTTP_SERVER_PORT}`, - `# REGISTRY_HOST=\${DEPLOY_REGISTRY_HOST}`, - `# REGISTRY_NAMESPACE=\${DEPLOY_REGISTRY_NAMESPACE}`, - ]; + "# Worker settings", + "HTTP_SERVER_PORT=9020", + "COORDINATOR_HOST=127.0.0.1", + "COORDINATOR_PORT=${HTTP_SERVER_PORT}", + "# REGISTRY_HOST=${DEPLOY_REGISTRY_HOST}", + "# REGISTRY_NAMESPACE=${DEPLOY_REGISTRY_NAMESPACE}", + ]; - return { - envs, - domains, - }; + return { + envs, + domains, + }; } diff --git a/apps/dokploy/utils/i18n.ts b/apps/dokploy/utils/i18n.ts index 08370766e..47466d1f5 100644 --- a/apps/dokploy/utils/i18n.ts +++ b/apps/dokploy/utils/i18n.ts @@ -5,8 +5,8 @@ export function getLocale(cookies: NextApiRequestCookies) { return locale; } -import { serverSideTranslations as originalServerSideTranslations } from "next-i18next/serverSideTranslations"; import { Languages } from "@/lib/languages"; +import { serverSideTranslations as originalServerSideTranslations } from "next-i18next/serverSideTranslations"; export const serverSideTranslations = ( locale: string, diff --git a/packages/server/src/utils/notifications/database-backup.ts b/packages/server/src/utils/notifications/database-backup.ts index 115d8b206..717c965f6 100644 --- a/packages/server/src/utils/notifications/database-backup.ts +++ b/packages/server/src/utils/notifications/database-backup.ts @@ -95,7 +95,9 @@ export const sendDatabaseBackupNotifications = async ({ }, { name: "`❓`・Type", - value: type.replace("error", "Failed").replace("success", "Successful"), + value: type + .replace("error", "Failed") + .replace("success", "Successful"), inline: true, }, ...(type === "error" && errorMessage diff --git a/packages/server/src/utils/notifications/docker-cleanup.ts b/packages/server/src/utils/notifications/docker-cleanup.ts index 31c1c6c9d..e8bbc6dbe 100644 --- a/packages/server/src/utils/notifications/docker-cleanup.ts +++ b/packages/server/src/utils/notifications/docker-cleanup.ts @@ -48,7 +48,6 @@ export const sendDockerCleanupNotifications = async ( title: "> `✅` - Docker Cleanup", color: 0x57f287, fields: [ - { name: "`📅`・Date", value: date.toLocaleDateString(), From 2960d818295f4881b4f9b122111d054a52e9c150 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 7 Dec 2024 13:55:00 -0600 Subject: [PATCH 223/243] fix: show mount path when is not compose --- .../dashboard/application/advanced/volumes/update-volume.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx index 91e9befed..590cd1141 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx @@ -119,7 +119,7 @@ export const UpdateVolume = ({ } else if (typeForm === "file") { form.reset({ content: data.content || "", - mountPath: "/", + mountPath: serviceType === "compose" ? "/" : data.mountPath, filePath: data.filePath || "", type: "file", }); From f4bd729f65494ffee48e97ce99f17c734ef98e8f Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 7 Dec 2024 14:01:48 -0600 Subject: [PATCH 224/243] test: add missing fields --- apps/dokploy/__test__/drop/drop.test.test.ts | 1 + apps/dokploy/__test__/traefik/traefik.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/dokploy/__test__/drop/drop.test.test.ts b/apps/dokploy/__test__/drop/drop.test.test.ts index 53ab02f2b..9a6473afd 100644 --- a/apps/dokploy/__test__/drop/drop.test.test.ts +++ b/apps/dokploy/__test__/drop/drop.test.test.ts @@ -26,6 +26,7 @@ if (typeof window === "undefined") { const baseApp: ApplicationNested = { applicationId: "", + herokuVersion: "", applicationStatus: "done", appName: "", autoDeploy: true, diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index 7e11160bd..d7ad29ab0 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -6,6 +6,7 @@ import { expect, test } from "vitest"; const baseApp: ApplicationNested = { applicationId: "", + herokuVersion: "", applicationStatus: "done", appName: "", autoDeploy: true, From 32b19a0fb648628e305b58a181e3b0390298e16c Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 7 Dec 2024 14:09:31 -0600 Subject: [PATCH 225/243] refactor: add code editor in volumes edit --- Dockerfile | 1 - Dockerfile.cloud | 1 - .../application/advanced/volumes/add-volumes.tsx | 13 ++++++++----- .../application/advanced/volumes/update-volume.tsx | 14 ++++++++------ 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index ebc61a2b7..74b70db0e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,6 @@ RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var COPY --from=build /prod/dokploy/.next ./.next COPY --from=build /prod/dokploy/dist ./dist COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs -# COPY --from=build /prod/dokploy/next-i18next.config.cjs ./next-i18next.config.cjs COPY --from=build /prod/dokploy/public ./public COPY --from=build /prod/dokploy/package.json ./package.json COPY --from=build /prod/dokploy/drizzle ./drizzle diff --git a/Dockerfile.cloud b/Dockerfile.cloud index 0f8427d46..020ea3d69 100644 --- a/Dockerfile.cloud +++ b/Dockerfile.cloud @@ -44,7 +44,6 @@ RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var COPY --from=build /prod/dokploy/.next ./.next COPY --from=build /prod/dokploy/dist ./dist COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs -COPY --from=build /prod/dokploy/next-i18next.config.cjs ./next-i18next.config.cjs COPY --from=build /prod/dokploy/public ./public COPY --from=build /prod/dokploy/package.json ./package.json COPY --from=build /prod/dokploy/drizzle ./drizzle diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx index d9dc16e70..5c6b95ca1 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx @@ -1,3 +1,4 @@ +import { CodeEditor } from "@/components/shared/code-editor"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -18,7 +19,6 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -150,7 +150,7 @@ export const AddVolumes = ({ - + Volumes / Mounts @@ -303,9 +303,12 @@ export const AddVolumes = ({ Content -