diff --git a/components/dashboard/application/advanced/traefik/show-traefik-config.tsx b/components/dashboard/application/advanced/traefik/show-traefik-config.tsx index 6b5415044..a3fb4f302 100644 --- a/components/dashboard/application/advanced/traefik/show-traefik-config.tsx +++ b/components/dashboard/application/advanced/traefik/show-traefik-config.tsx @@ -29,7 +29,7 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => { Traefik Modify the traefik config, in rare cases you may need to add - specific config, becarefull because modifying incorrectly can break + specific config, be careful because modifying incorrectly can break traefik and your application diff --git a/package.json b/package.json index 5883df66e..502ff347c 100644 --- a/package.json +++ b/package.json @@ -1,159 +1,159 @@ { - "name": "dokploy", - "version": "v0.3.1", - "private": true, - "license": "Apache-2.0", - "type": "module", - "scripts": { - "build": "npm run build-server && npm run build-next", - "start": "node dist/server.mjs", - "build-server": "tsx esbuild.config.ts", - "build-next": "next build", - "setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run", - "reset-password": "node dist/reset-password.mjs", - "dev": "tsx watch -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", - "migration:up": "drizzle-kit up --config ./server/db/drizzle.config.ts", - "migration:drop": "drizzle-kit drop --config ./server/db/drizzle.config.ts", - "db:push": "drizzle-kit --config ./server/db/drizzle.config.ts", - "db:truncate": "tsx -r dotenv/config ./server/db/reset.ts", - "db:studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts", - "lint": "biome lint", - "db:seed": "tsx -r dotenv/config ./server/db/seed.ts", - "db:clean": "tsx -r dotenv/config ./server/db/reset.ts", - "docker:build": "./docker/build.sh", - "docker:push": "./docker/push.sh", - "docker:build:canary": "./docker/build.sh canary", - "docker:push:canary": "./docker/push.sh canary", - "version": "echo $(node -p \"require('./package.json').version\")", - "test": "vitest --config __test__/vitest.config.ts" - }, - "dependencies": { - "@aws-sdk/client-s3": "3.515.0", - "@codemirror/lang-json": "^6.0.1", - "@codemirror/lang-yaml": "^6.1.1", - "@codemirror/language": "^6.10.1", - "@codemirror/legacy-modes": "6.4.0", - "@dokploy/trpc-openapi": "0.0.4", - "@faker-js/faker": "^8.4.1", - "@hookform/resolvers": "^3.3.4", - "@lucia-auth/adapter-drizzle": "1.0.7", - "@octokit/auth-app": "^6.0.4", - "@octokit/webhooks": "^13.2.7", - "@radix-ui/react-accordion": "1.1.2", - "@radix-ui/react-alert-dialog": "^1.0.5", - "@radix-ui/react-avatar": "^1.0.4", - "@radix-ui/react-checkbox": "^1.0.4", - "@radix-ui/react-dialog": "^1.0.5", - "@radix-ui/react-dropdown-menu": "^2.0.6", - "@radix-ui/react-label": "^2.0.2", - "@radix-ui/react-popover": "^1.0.7", - "@radix-ui/react-progress": "^1.0.3", - "@radix-ui/react-radio-group": "^1.1.3", - "@radix-ui/react-scroll-area": "^1.0.5", - "@radix-ui/react-select": "^2.0.0", - "@radix-ui/react-separator": "^1.0.3", - "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-switch": "^1.0.3", - "@radix-ui/react-tabs": "^1.0.4", - "@radix-ui/react-toggle": "^1.0.3", - "@radix-ui/react-tooltip": "^1.0.7", - "@tanstack/react-query": "^4.36.1", - "@tanstack/react-table": "^8.16.0", - "@trpc/client": "^10.43.6", - "@trpc/next": "^10.43.6", - "@trpc/react-query": "^10.43.6", - "@trpc/server": "^10.43.6", - "@uiw/codemirror-theme-github": "^4.22.1", - "@uiw/react-codemirror": "^4.22.1", - "@xterm/addon-attach": "0.10.0", - "@xterm/xterm": "^5.4.0", - "bcrypt": "5.1.1", - "bl": "6.0.11", - "boxen": "^7.1.1", - "bullmq": "5.4.2", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", - "cmdk": "^0.2.0", - "copy-to-clipboard": "^3.3.3", - "copy-webpack-plugin": "^12.0.2", - "date-fns": "3.6.0", - "dockerode": "4.0.2", - "dockerode-compose": "^1.4.0", - "dockerstats": "2.4.2", - "dotenv": "16.4.5", - "drizzle-orm": "^0.30.8", - "drizzle-zod": "0.5.1", - "hi-base32": "^0.5.1", - "input-otp": "^1.2.4", - "js-yaml": "4.1.0", - "k6": "^0.0.0", - "lodash": "4.17.21", - "lucia": "^3.0.1", - "lucide-react": "^0.312.0", - "nanoid": "3", - "next": "^14.1.3", - "next-themes": "^0.2.1", - "node-os-utils": "1.3.7", - "node-pty": "1.0.0", - "node-schedule": "2.1.1", - "octokit": "3.1.2", - "otpauth": "^9.2.3", - "postgres": "3.4.4", - "public-ip": "6.0.2", - "qrcode": "^1.5.3", - "react": "18.2.0", - "react-dom": "18.2.0", - "react-hook-form": "^7.49.3", - "recharts": "^2.12.3", - "slugify": "^1.6.6", - "sonner": "^1.4.0", - "superjson": "^2.2.1", - "swagger-ui-react": "^5.17.14", - "tailwind-merge": "^2.2.0", - "tailwindcss-animate": "^1.0.7", - "tar-fs": "3.0.5", - "use-resize-observer": "9.1.0", - "ws": "8.16.0", - "xterm-addon-fit": "^0.8.0", - "zod": "^3.23.4" - }, - "devDependencies": { - "@biomejs/biome": "1.7.1", - "@types/bcrypt": "5.0.2", - "@types/dockerode": "3.3.23", - "@types/js-yaml": "4.0.9", - "@types/lodash": "4.17.4", - "@types/node": "^18.17.0", - "@types/node-os-utils": "1.3.4", - "@types/node-schedule": "2.1.6", - "@types/qrcode": "^1.5.5", - "@types/react": "^18.2.37", - "@types/react-dom": "^18.2.15", - "@types/swagger-ui-react": "^4.18.3", - "@types/tar-fs": "2.0.4", - "@types/ws": "8.5.10", - "autoprefixer": "^10.4.14", - "drizzle-kit": "^0.21.1", - "esbuild": "0.20.2", - "localtunnel": "2.0.2", - "postcss": "^8.4.31", - "prettier": "^3.2.4", - "prettier-plugin-tailwindcss": "^0.5.11", - "tailwindcss": "^3.4.1", - "tsx": "^4.7.0", - "typescript": "^5.4.2", - "vite-tsconfig-paths": "4.3.2", - "vitest": "^1.6.0", - "xterm-readline": "1.1.1" - }, - "ct3aMetadata": { - "initVersion": "7.25.2" - }, - "engines": { - "node": "^18.18.0", - "pnpm": ">=8.15.4" - } + "name": "dokploy", + "version": "v0.3.2", + "private": true, + "license": "Apache-2.0", + "type": "module", + "scripts": { + "build": "npm run build-server && npm run build-next", + "start": "node dist/server.mjs", + "build-server": "tsx esbuild.config.ts", + "build-next": "next build", + "setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run", + "reset-password": "node dist/reset-password.mjs", + "dev": "tsx watch -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", + "migration:up": "drizzle-kit up --config ./server/db/drizzle.config.ts", + "migration:drop": "drizzle-kit drop --config ./server/db/drizzle.config.ts", + "db:push": "drizzle-kit --config ./server/db/drizzle.config.ts", + "db:truncate": "tsx -r dotenv/config ./server/db/reset.ts", + "db:studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts", + "lint": "biome lint", + "db:seed": "tsx -r dotenv/config ./server/db/seed.ts", + "db:clean": "tsx -r dotenv/config ./server/db/reset.ts", + "docker:build": "./docker/build.sh", + "docker:push": "./docker/push.sh", + "docker:build:canary": "./docker/build.sh canary", + "docker:push:canary": "./docker/push.sh canary", + "version": "echo $(node -p \"require('./package.json').version\")", + "test": "vitest --config __test__/vitest.config.ts" + }, + "dependencies": { + "@aws-sdk/client-s3": "3.515.0", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/lang-yaml": "^6.1.1", + "@codemirror/language": "^6.10.1", + "@codemirror/legacy-modes": "6.4.0", + "@dokploy/trpc-openapi": "0.0.4", + "@faker-js/faker": "^8.4.1", + "@hookform/resolvers": "^3.3.4", + "@lucia-auth/adapter-drizzle": "1.0.7", + "@octokit/auth-app": "^6.0.4", + "@octokit/webhooks": "^13.2.7", + "@radix-ui/react-accordion": "1.1.2", + "@radix-ui/react-alert-dialog": "^1.0.5", + "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-progress": "^1.0.3", + "@radix-ui/react-radio-group": "^1.1.3", + "@radix-ui/react-scroll-area": "^1.0.5", + "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-separator": "^1.0.3", + "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.0.3", + "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-toggle": "^1.0.3", + "@radix-ui/react-tooltip": "^1.0.7", + "@tanstack/react-query": "^4.36.1", + "@tanstack/react-table": "^8.16.0", + "@trpc/client": "^10.43.6", + "@trpc/next": "^10.43.6", + "@trpc/react-query": "^10.43.6", + "@trpc/server": "^10.43.6", + "@uiw/codemirror-theme-github": "^4.22.1", + "@uiw/react-codemirror": "^4.22.1", + "@xterm/addon-attach": "0.10.0", + "@xterm/xterm": "^5.4.0", + "bcrypt": "5.1.1", + "bl": "6.0.11", + "boxen": "^7.1.1", + "bullmq": "5.4.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", + "cmdk": "^0.2.0", + "copy-to-clipboard": "^3.3.3", + "copy-webpack-plugin": "^12.0.2", + "date-fns": "3.6.0", + "dockerode": "4.0.2", + "dockerode-compose": "^1.4.0", + "dockerstats": "2.4.2", + "dotenv": "16.4.5", + "drizzle-orm": "^0.30.8", + "drizzle-zod": "0.5.1", + "hi-base32": "^0.5.1", + "input-otp": "^1.2.4", + "js-yaml": "4.1.0", + "k6": "^0.0.0", + "lodash": "4.17.21", + "lucia": "^3.0.1", + "lucide-react": "^0.312.0", + "nanoid": "3", + "next": "^14.1.3", + "next-themes": "^0.2.1", + "node-os-utils": "1.3.7", + "node-pty": "1.0.0", + "node-schedule": "2.1.1", + "octokit": "3.1.2", + "otpauth": "^9.2.3", + "postgres": "3.4.4", + "public-ip": "6.0.2", + "qrcode": "^1.5.3", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-hook-form": "^7.49.3", + "recharts": "^2.12.3", + "slugify": "^1.6.6", + "sonner": "^1.4.0", + "superjson": "^2.2.1", + "swagger-ui-react": "^5.17.14", + "tailwind-merge": "^2.2.0", + "tailwindcss-animate": "^1.0.7", + "tar-fs": "3.0.5", + "use-resize-observer": "9.1.0", + "ws": "8.16.0", + "xterm-addon-fit": "^0.8.0", + "zod": "^3.23.4" + }, + "devDependencies": { + "@biomejs/biome": "1.7.1", + "@types/bcrypt": "5.0.2", + "@types/dockerode": "3.3.23", + "@types/js-yaml": "4.0.9", + "@types/lodash": "4.17.4", + "@types/node": "^18.17.0", + "@types/node-os-utils": "1.3.4", + "@types/node-schedule": "2.1.6", + "@types/qrcode": "^1.5.5", + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@types/swagger-ui-react": "^4.18.3", + "@types/tar-fs": "2.0.4", + "@types/ws": "8.5.10", + "autoprefixer": "^10.4.14", + "drizzle-kit": "^0.21.1", + "esbuild": "0.20.2", + "localtunnel": "2.0.2", + "postcss": "^8.4.31", + "prettier": "^3.2.4", + "prettier-plugin-tailwindcss": "^0.5.11", + "tailwindcss": "^3.4.1", + "tsx": "^4.7.0", + "typescript": "^5.4.2", + "vite-tsconfig-paths": "4.3.2", + "vitest": "^1.6.0", + "xterm-readline": "1.1.1" + }, + "ct3aMetadata": { + "initVersion": "7.25.2" + }, + "engines": { + "node": "^18.18.0", + "pnpm": ">=8.15.4" + } } diff --git a/public/templates/doublezero.svg b/public/templates/doublezero.svg new file mode 100644 index 000000000..e28cbeb09 --- /dev/null +++ b/public/templates/doublezero.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/templates/listmonk.png b/public/templates/listmonk.png new file mode 100644 index 000000000..cd6f5618e Binary files /dev/null and b/public/templates/listmonk.png differ diff --git a/server/setup/postgres-setup.ts b/server/setup/postgres-setup.ts index ce5bfccc2..d292f877e 100644 --- a/server/setup/postgres-setup.ts +++ b/server/setup/postgres-setup.ts @@ -26,6 +26,9 @@ export const initializePostgres = async () => { RestartPolicy: { Condition: "on-failure", }, + Placement: { + Constraints: ["node.role==manager"], + }, }, Mode: { Replicated: { diff --git a/server/setup/redis-setup.ts b/server/setup/redis-setup.ts index 40886bd09..82f568e46 100644 --- a/server/setup/redis-setup.ts +++ b/server/setup/redis-setup.ts @@ -23,6 +23,9 @@ export const initializeRedis = async () => { RestartPolicy: { Condition: "on-failure", }, + Placement: { + Constraints: ["node.role==manager"], + }, }, Mode: { Replicated: { diff --git a/server/setup/registry-setup.ts b/server/setup/registry-setup.ts index 94d592f7f..085ed365d 100644 --- a/server/setup/registry-setup.ts +++ b/server/setup/registry-setup.ts @@ -43,6 +43,9 @@ export const initializeRegistry = async ( RestartPolicy: { Condition: "on-failure", }, + Placement: { + Constraints: ["node.role==manager"], + }, }, Mode: { Replicated: { diff --git a/server/setup/traefik-setup.ts b/server/setup/traefik-setup.ts index 889988d66..4cd011cd4 100644 --- a/server/setup/traefik-setup.ts +++ b/server/setup/traefik-setup.ts @@ -41,6 +41,9 @@ export const initializeTraefik = async () => { RestartPolicy: { Condition: "on-failure", }, + Placement: { + Constraints: ["node.role==manager"], + }, }, Mode: { Replicated: { diff --git a/templates/doublezero/docker-compose.yml b/templates/doublezero/docker-compose.yml new file mode 100644 index 000000000..bb7b1d2b3 --- /dev/null +++ b/templates/doublezero/docker-compose.yml @@ -0,0 +1,31 @@ +services: + doublezero: + restart: always + image: liltechnomancer/double-zero:0.2.1 + ports: + - ${DOUBLEZERO_PORT} + networks: + - dokploy-network + volumes: + - db-data:/var/lib/doublezero/data + environment: + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_REGION: ${AWS_REGION} + SQS_URL: ${SQS_URL} + SYSTEM_EMAIL: ${SYSTEM_EMAIL} + SECRET_KEY_BASE: ${SECRET_KEY_BASE} + PHX_HOST: ${DOUBLEZERO_HOST} + DATABASE_PATH: ./00.db + labels: + - "traefik.enable=true" + - "traefik.http.routers.${HASH}.rule=Host(`${DOUBLEZERO_HOST}`)" + - "traefik.http.services.${HASH}.loadbalancer.server.port=${DOUBLEZERO_PORT}" + +volumes: + db-data: + driver: local + +networks: + dokploy-network: + external: true diff --git a/templates/doublezero/index.ts b/templates/doublezero/index.ts new file mode 100644 index 000000000..72304728f --- /dev/null +++ b/templates/doublezero/index.ts @@ -0,0 +1,29 @@ +import { + generateHash, + generateRandomDomain, + type Template, + type Schema, + generateBase64, +} from "../utils"; + +export function generate(schema: Schema): Template { + const mainServiceHash = generateHash(schema.projectName); + const randomDomain = generateRandomDomain(schema); + const secretKeyBase = generateBase64(64); + + const envs = [ + `DOUBLEZERO_HOST=${randomDomain}`, + "DOUBLEZERO_PORT=4000", + `HASH=${mainServiceHash}`, + `SECRET_KEY_BASE=${secretKeyBase}`, + "AWS_ACCESS_KEY_ID=your-aws-access-key", + "AWS_SECRET_ACCESS_KEY=your-aws-secret-key", + "AWS_REGION=your-aws-region", + "SQS_URL=your-aws-sqs-url", + "SYSTEM_EMAIL=", + ]; + + return { + envs, + }; +} diff --git a/templates/listmonk/docker-compose.yml b/templates/listmonk/docker-compose.yml new file mode 100644 index 000000000..e17b76577 --- /dev/null +++ b/templates/listmonk/docker-compose.yml @@ -0,0 +1,56 @@ +services: + db: + image: postgres:13 + ports: + - 5432 + networks: + - dokploy-network + environment: + - POSTGRES_PASSWORD=listmonk + - POSTGRES_USER=listmonk + - POSTGRES_DB=listmonk + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U listmonk"] + interval: 10s + timeout: 5s + retries: 6 + volumes: + - listmonk-data:/var/lib/postgresql/data + + setup: + image: listmonk/listmonk:v3.0.0 + networks: + - dokploy-network + volumes: + - ./config.toml:/listmonk/config.toml + depends_on: + - db + command: [sh, -c, "sleep 3 && ./listmonk --install --idempotent --yes --config config.toml"] + + app: + restart: unless-stopped + image: listmonk/listmonk:v3.0.0 + ports: + - "${LISTMONK_PORT}" + networks: + - dokploy-network + environment: + - TZ=Etc/UTC + depends_on: + - db + - setup + volumes: + - ./config.toml:/listmonk/config.toml + labels: + - "traefik.enable=true" + - "traefik.http.routers.${HASH}.rule=Host(`${LISTMONK_HOST}`)" + - "traefik.http.services.${HASH}.loadbalancer.server.port=${LISTMONK_PORT}" + +volumes: + listmonk-data: + driver: local + +networks: + dokploy-network: + external: true diff --git a/templates/listmonk/index.ts b/templates/listmonk/index.ts new file mode 100644 index 000000000..e9df62223 --- /dev/null +++ b/templates/listmonk/index.ts @@ -0,0 +1,52 @@ +import { + generateHash, + generateRandomDomain, + type Template, + type Schema, + generatePassword, +} from "../utils"; + +export function generate(schema: Schema): Template { + const mainServiceHash = generateHash(schema.projectName); + const randomDomain = generateRandomDomain(schema); + const adminPassword = generatePassword(32); + + const envs = [ + `LISTMONK_HOST=${randomDomain}`, + "LISTMONK_PORT=9000", + `HASH=${mainServiceHash}`, + `# login with admin:${adminPassword}`, + "# check config.toml in Advanced / Volumes for more options", + ]; + + const mounts: Template["mounts"] = [ + { + mountPath: "./config.toml", + content: `[app] +address = "0.0.0.0:9000" + +admin_username = "admin" +admin_password = "${adminPassword}" + +[db] +host = "db" +port = 5432 +user = "listmonk" +password = "listmonk" +database = "listmonk" + +ssl_mode = "disable" +max_open = 25 +max_idle = 25 +max_lifetime = "300s" + +params = "" +`, + }, + ]; + + return { + envs, + mounts, + }; +} diff --git a/templates/templates.ts b/templates/templates.ts index 49bd596c4..b41a2028b 100644 --- a/templates/templates.ts +++ b/templates/templates.ts @@ -331,5 +331,33 @@ export const templates: TemplateData[] = [ }, 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), } ];