mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44e6a117dd | ||
|
|
86bb119052 | ||
|
|
54c70ff177 | ||
|
|
eabe14e4c3 | ||
|
|
9d0edd810e | ||
|
|
35ff65a871 | ||
|
|
675fbb7692 | ||
|
|
295bd06918 | ||
|
|
89635fa27b | ||
|
|
60b19616c1 | ||
|
|
3884dc9259 | ||
|
|
e8648732be | ||
|
|
0f0f32a40d | ||
|
|
1b91376f5e | ||
|
|
aa347abfcd | ||
|
|
e49b190c32 | ||
|
|
bfdc73f8d1 | ||
|
|
525a711c55 | ||
|
|
e0a0f97f70 | ||
|
|
66017c8623 | ||
|
|
bab93f8145 | ||
|
|
45aae520d8 | ||
|
|
c8f14c322e | ||
|
|
334466f28f | ||
|
|
22328eeb83 | ||
|
|
c2201a304e | ||
|
|
f77de1bc52 | ||
|
|
74d6b2d591 | ||
|
|
b7cc3283e2 | ||
|
|
5dfa6c5194 | ||
|
|
59d662396e | ||
|
|
cbfcae4ab6 | ||
|
|
152a54b251 | ||
|
|
1113669bfd | ||
|
|
4d251271b9 | ||
|
|
21263a217c | ||
|
|
3322fdf937 | ||
|
|
b415e2fba1 | ||
|
|
8888a4ce5c | ||
|
|
dd3ba7e836 | ||
|
|
cb6d8fceb4 | ||
|
|
d8641b7b4e | ||
|
|
997a8395b1 | ||
|
|
d2420ed6e8 |
@@ -29,7 +29,7 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
|
||||
<CardTitle className="text-xl">Traefik</CardTitle>
|
||||
<CardDescription>
|
||||
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
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api } from "@/utils/api";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
@@ -26,8 +25,6 @@ export const DeployApplication = ({ applicationId }: Props) => {
|
||||
{ enabled: !!applicationId },
|
||||
);
|
||||
|
||||
const { mutateAsync: markRunning } =
|
||||
api.application.markRunning.useMutation();
|
||||
const { mutateAsync: deploy } = api.application.deploy.useMutation();
|
||||
|
||||
return (
|
||||
@@ -48,24 +45,16 @@ export const DeployApplication = ({ applicationId }: Props) => {
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await markRunning({
|
||||
toast.success("Deploying Application....");
|
||||
|
||||
await refetch();
|
||||
await deploy({
|
||||
applicationId,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Deploying Application....");
|
||||
}).catch(() => {
|
||||
toast.error("Error to deploy Application");
|
||||
});
|
||||
|
||||
await refetch();
|
||||
await deploy({
|
||||
applicationId,
|
||||
}).catch(() => {
|
||||
toast.error("Error to deploy Application");
|
||||
});
|
||||
|
||||
await refetch();
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error(e.message || "Error to deploy Application");
|
||||
});
|
||||
await refetch();
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
|
||||
@@ -25,8 +25,7 @@ export const RedbuildApplication = ({ applicationId }: Props) => {
|
||||
},
|
||||
{ enabled: !!applicationId },
|
||||
);
|
||||
const { mutateAsync: markRunning } =
|
||||
api.application.markRunning.useMutation();
|
||||
|
||||
const { mutateAsync } = api.application.redeploy.useMutation();
|
||||
const utils = api.useUtils();
|
||||
return (
|
||||
@@ -54,22 +53,14 @@ export const RedbuildApplication = ({ applicationId }: Props) => {
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await markRunning({
|
||||
toast.success("Redeploying Application....");
|
||||
await mutateAsync({
|
||||
applicationId,
|
||||
})
|
||||
.then(async () => {
|
||||
await mutateAsync({
|
||||
await utils.application.one.invalidate({
|
||||
applicationId,
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.application.one.invalidate({
|
||||
applicationId,
|
||||
});
|
||||
toast.success("Application rebuild succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to rebuild the application");
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to rebuild the application");
|
||||
|
||||
@@ -9,14 +9,11 @@ import {
|
||||
import { api } from "@/utils/api";
|
||||
import { RocketIcon } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
// import { CancelQueues } from "./cancel-queues";
|
||||
// import { ShowDeployment } from "./show-deployment-compose";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { ShowDeploymentCompose } from "./show-deployment-compose";
|
||||
import { RefreshTokenCompose } from "./refresh-token-compose";
|
||||
import { CancelQueuesCompose } from "./cancel-queues-compose";
|
||||
// import { RefreshToken } from "./refresh-token";//
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
@@ -90,6 +87,11 @@ export const ShowDeploymentsCompose = ({ composeId }: Props) => {
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{deployment.title}
|
||||
</span>
|
||||
{deployment.description && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{deployment.description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-2">
|
||||
<div className="text-sm capitalize text-muted-foreground">
|
||||
|
||||
@@ -25,7 +25,6 @@ export const DeployCompose = ({ composeId }: Props) => {
|
||||
{ enabled: !!composeId },
|
||||
);
|
||||
|
||||
const { mutateAsync: markRunning } = api.compose.update.useMutation();
|
||||
const { mutateAsync: deploy } = api.compose.deploy.useMutation();
|
||||
|
||||
return (
|
||||
@@ -44,25 +43,16 @@ export const DeployCompose = ({ composeId }: Props) => {
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await markRunning({
|
||||
toast.success("Deploying Compose....");
|
||||
|
||||
await refetch();
|
||||
await deploy({
|
||||
composeId,
|
||||
composeStatus: "running",
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Deploying Compose....");
|
||||
}).catch(() => {
|
||||
toast.error("Error to deploy Compose");
|
||||
});
|
||||
|
||||
await refetch();
|
||||
await deploy({
|
||||
composeId,
|
||||
}).catch(() => {
|
||||
toast.error("Error to deploy Compose");
|
||||
});
|
||||
|
||||
await refetch();
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error(e.message || "Error to deploy Compose");
|
||||
});
|
||||
await refetch();
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
|
||||
@@ -25,7 +25,6 @@ export const RedbuildCompose = ({ composeId }: Props) => {
|
||||
},
|
||||
{ enabled: !!composeId },
|
||||
);
|
||||
const { mutateAsync: markRunning } = api.compose.update.useMutation();
|
||||
const { mutateAsync } = api.compose.redeploy.useMutation();
|
||||
const utils = api.useUtils();
|
||||
return (
|
||||
@@ -53,23 +52,14 @@ export const RedbuildCompose = ({ composeId }: Props) => {
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await markRunning({
|
||||
toast.success("Redeploying Compose....");
|
||||
await mutateAsync({
|
||||
composeId,
|
||||
composeStatus: "running",
|
||||
})
|
||||
.then(async () => {
|
||||
await mutateAsync({
|
||||
await utils.compose.one.invalidate({
|
||||
composeId,
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.compose.one.invalidate({
|
||||
composeId,
|
||||
});
|
||||
toast.success("Compose rebuild succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to rebuild the compose");
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to rebuild the compose");
|
||||
|
||||
@@ -25,7 +25,6 @@ export const StopCompose = ({ composeId }: Props) => {
|
||||
},
|
||||
{ enabled: !!composeId },
|
||||
);
|
||||
const { mutateAsync: markRunning } = api.compose.update.useMutation();
|
||||
const { mutateAsync, isLoading } = api.compose.stop.useMutation();
|
||||
const utils = api.useUtils();
|
||||
return (
|
||||
@@ -47,23 +46,14 @@ export const StopCompose = ({ composeId }: Props) => {
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await markRunning({
|
||||
await mutateAsync({
|
||||
composeId,
|
||||
composeStatus: "running",
|
||||
})
|
||||
.then(async () => {
|
||||
await mutateAsync({
|
||||
await utils.compose.one.invalidate({
|
||||
composeId,
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.compose.one.invalidate({
|
||||
composeId,
|
||||
});
|
||||
toast.success("Compose rebuild succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to stop the compose");
|
||||
});
|
||||
});
|
||||
toast.success("Compose stopped succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to stop the compose");
|
||||
|
||||
@@ -54,7 +54,7 @@ export const AddTemplate = ({ projectId }: Props) => {
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl p-0">
|
||||
<div className="sticky top-0 z-10 flex flex-col gap-4 dark:bg-black p-6 border-b">
|
||||
<div className="sticky top-0 z-10 flex flex-col gap-4 bg-background p-6 border-b">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create Template</DialogTitle>
|
||||
<DialogDescription>
|
||||
|
||||
@@ -48,6 +48,7 @@ export const GithubSetup = () => {
|
||||
const [organizationName, setOrganization] = useState<string>("");
|
||||
const { data } = api.admin.one.useQuery();
|
||||
useEffect(() => {
|
||||
const url = document.location.origin;
|
||||
const manifest = JSON.stringify(
|
||||
{
|
||||
redirect_url: `${origin}/api/redirect?authId=${data?.authId}`,
|
||||
@@ -55,7 +56,7 @@ export const GithubSetup = () => {
|
||||
url: origin,
|
||||
hook_attributes: {
|
||||
// JUST FOR TESTING
|
||||
url: "https://webhook.site/b6a167c0-ceb5-4f0c-a257-97c0fd163977",
|
||||
url: `${url}/api/deploy/github`,
|
||||
// url: `${origin}/api/webhook`, // Aquí especificas la URL del endpoint de tu webhook
|
||||
},
|
||||
callback_urls: [`${origin}/api/redirect`], // Los URLs de callback para procesos de autenticación
|
||||
@@ -92,8 +93,18 @@ export const GithubSetup = () => {
|
||||
</span>
|
||||
<BadgeCheck className="size-4 text-green-700" />
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<div className="flex items-end gap-4 flex-wrap">
|
||||
<RemoveGithubApp />
|
||||
{/* <Link
|
||||
href={`https://github.com/settings/apps/${data?.githubAppName}`}
|
||||
target="_blank"
|
||||
className={buttonVariants({
|
||||
className: "w-fit",
|
||||
variant: "secondary",
|
||||
})}
|
||||
>
|
||||
<span className="text-sm">Manage Github App</span>
|
||||
</Link> */}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -13,6 +13,13 @@ import {
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
TooltipProvider,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
|
||||
export const RemoveGithubApp = () => {
|
||||
const { refetch } = api.auth.get.useQuery();
|
||||
@@ -22,7 +29,20 @@ export const RemoveGithubApp = () => {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive">Remove Current Github App</Button>
|
||||
<Button variant="destructive">
|
||||
Remove Current Github App
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon className="size-4 fill-muted-destructive text-muted-destructive" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
We recommend deleting the GitHub app first, and then removing
|
||||
the current one from here.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
|
||||
1
drizzle/0018_careful_killmonger.sql
Normal file
1
drizzle/0018_careful_killmonger.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "admin" ADD COLUMN "githubWebhookSecret" text;
|
||||
2632
drizzle/meta/0018_snapshot.json
Normal file
2632
drizzle/meta/0018_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -127,6 +127,13 @@
|
||||
"when": 1719547174326,
|
||||
"tag": "0017_minor_post",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 18,
|
||||
"version": "6",
|
||||
"when": 1719928377858,
|
||||
"tag": "0018_careful_killmonger",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
313
package.json
313
package.json
@@ -1,158 +1,159 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.3.0",
|
||||
"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",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@lucia-auth/adapter-drizzle": "1.0.7",
|
||||
"@octokit/auth-app": "^6.0.4",
|
||||
"@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",
|
||||
"@dokploy/trpc-openapi": "0.0.4",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { updateApplicationStatus } from "@/server/api/services/application";
|
||||
import { db } from "@/server/db";
|
||||
import { applications } from "@/server/db/schema";
|
||||
import type { DeploymentJob } from "@/server/queues/deployments-queue";
|
||||
@@ -68,11 +67,6 @@ export default async function handler(
|
||||
}
|
||||
|
||||
try {
|
||||
await updateApplicationStatus(
|
||||
application.applicationId as string,
|
||||
"running",
|
||||
);
|
||||
|
||||
const jobData: DeploymentJob = {
|
||||
applicationId: application.applicationId as string,
|
||||
titleLog: deploymentTitle,
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
extractCommitMessage,
|
||||
extractHash,
|
||||
} from "../[refreshToken]";
|
||||
import { updateCompose } from "@/server/api/services/compose";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
@@ -56,10 +55,6 @@ export default async function handler(
|
||||
}
|
||||
|
||||
try {
|
||||
await updateCompose(composeResult.composeId, {
|
||||
composeStatus: "running",
|
||||
});
|
||||
|
||||
const jobData: DeploymentJob = {
|
||||
composeId: composeResult.composeId as string,
|
||||
titleLog: deploymentTitle,
|
||||
|
||||
126
pages/api/deploy/github.ts
Normal file
126
pages/api/deploy/github.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { db } from "@/server/db";
|
||||
import { Webhooks } from "@octokit/webhooks";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { applications, compose } from "@/server/db/schema";
|
||||
import { extractCommitMessage, extractHash } from "./[refreshToken]";
|
||||
import type { DeploymentJob } from "@/server/queues/deployments-queue";
|
||||
import { myQueue } from "@/server/queues/queueSetup";
|
||||
import { findAdmin } from "@/server/api/services/admin";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const admin = await findAdmin();
|
||||
|
||||
if (!admin) {
|
||||
res.status(200).json({ message: "Could not find admin" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!admin.githubWebhookSecret) {
|
||||
res.status(200).json({ message: "Github Webhook Secret not set" });
|
||||
return;
|
||||
}
|
||||
|
||||
const webhooks = new Webhooks({
|
||||
secret: admin.githubWebhookSecret,
|
||||
});
|
||||
|
||||
const signature = req.headers["x-hub-signature-256"];
|
||||
const github = req.body;
|
||||
|
||||
const verified = await webhooks.verify(
|
||||
JSON.stringify(github),
|
||||
signature as string,
|
||||
);
|
||||
|
||||
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"] !== "push") {
|
||||
res.status(400).json({ message: "We only accept push events" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const branchName = github?.ref?.replace("refs/heads/", "");
|
||||
const repository = github?.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),
|
||||
),
|
||||
});
|
||||
|
||||
for (const app of apps) {
|
||||
const jobData: DeploymentJob = {
|
||||
applicationId: app.applicationId as string,
|
||||
titleLog: deploymentTitle,
|
||||
descriptionLog: `Hash: ${deploymentHash}`,
|
||||
type: "deploy",
|
||||
applicationType: "application",
|
||||
};
|
||||
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),
|
||||
),
|
||||
});
|
||||
|
||||
for (const composeApp of composeApps) {
|
||||
const jobData: DeploymentJob = {
|
||||
composeId: composeApp.composeId as string,
|
||||
titleLog: deploymentTitle,
|
||||
type: "deploy",
|
||||
applicationType: "compose",
|
||||
descriptionLog: `Hash: ${deploymentHash}`,
|
||||
};
|
||||
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
{ ...jobData },
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ export default async function handler(
|
||||
githubAppName: data.name,
|
||||
githubClientId: data.client_id,
|
||||
githubClientSecret: data.client_secret,
|
||||
githubWebhookSecret: data.webhook_secret,
|
||||
githubPrivateKey: data.pem,
|
||||
})
|
||||
.where(eq(admins.authId, authId as string))
|
||||
|
||||
54
pnpm-lock.yaml
generated
54
pnpm-lock.yaml
generated
@@ -35,6 +35,9 @@ dependencies:
|
||||
'@octokit/auth-app':
|
||||
specifier: ^6.0.4
|
||||
version: 6.0.4
|
||||
'@octokit/webhooks':
|
||||
specifier: ^13.2.7
|
||||
version: 13.2.7
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: 1.1.2
|
||||
version: 1.1.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
|
||||
@@ -2604,6 +2607,14 @@ packages:
|
||||
resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==}
|
||||
dev: false
|
||||
|
||||
/@octokit/openapi-types@22.2.0:
|
||||
resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==}
|
||||
dev: false
|
||||
|
||||
/@octokit/openapi-webhooks-types@8.2.1:
|
||||
resolution: {integrity: sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA==}
|
||||
dev: false
|
||||
|
||||
/@octokit/plugin-paginate-graphql@4.0.1(@octokit/core@5.1.0):
|
||||
resolution: {integrity: sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -2665,6 +2676,13 @@ packages:
|
||||
once: 1.4.0
|
||||
dev: false
|
||||
|
||||
/@octokit/request-error@6.1.1:
|
||||
resolution: {integrity: sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==}
|
||||
engines: {node: '>= 18'}
|
||||
dependencies:
|
||||
'@octokit/types': 13.5.0
|
||||
dev: false
|
||||
|
||||
/@octokit/request@8.2.0:
|
||||
resolution: {integrity: sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -2681,11 +2699,22 @@ packages:
|
||||
'@octokit/openapi-types': 20.0.0
|
||||
dev: false
|
||||
|
||||
/@octokit/types@13.5.0:
|
||||
resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==}
|
||||
dependencies:
|
||||
'@octokit/openapi-types': 22.2.0
|
||||
dev: false
|
||||
|
||||
/@octokit/webhooks-methods@4.1.0:
|
||||
resolution: {integrity: sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==}
|
||||
engines: {node: '>= 18'}
|
||||
dev: false
|
||||
|
||||
/@octokit/webhooks-methods@5.1.0:
|
||||
resolution: {integrity: sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==}
|
||||
engines: {node: '>= 18'}
|
||||
dev: false
|
||||
|
||||
/@octokit/webhooks-types@7.3.2:
|
||||
resolution: {integrity: sha512-JWOoOgtWTFnTSAamPXXyjTY5/apttvNxF+vPBnwdSu5cj5snrd7FO0fyw4+wTXy8fHduq626JjhO+TwCyyA6vA==}
|
||||
dev: false
|
||||
@@ -2700,6 +2729,16 @@ packages:
|
||||
aggregate-error: 3.1.0
|
||||
dev: false
|
||||
|
||||
/@octokit/webhooks@13.2.7:
|
||||
resolution: {integrity: sha512-sPHCyi9uZuCs1gg0yF53FFocM+GsiiBEhQQV/itGzzQ8gjyv2GMJ1YvgdDY4lC0ePZeiV3juEw4GbS6w1VHhRw==}
|
||||
engines: {node: '>= 18'}
|
||||
dependencies:
|
||||
'@octokit/openapi-webhooks-types': 8.2.1
|
||||
'@octokit/request-error': 6.1.1
|
||||
'@octokit/webhooks-methods': 5.1.0
|
||||
aggregate-error: 5.0.0
|
||||
dev: false
|
||||
|
||||
/@pkgjs/parseargs@0.11.0:
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -5566,6 +5605,14 @@ packages:
|
||||
indent-string: 5.0.0
|
||||
dev: false
|
||||
|
||||
/aggregate-error@5.0.0:
|
||||
resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
clean-stack: 5.2.0
|
||||
indent-string: 5.0.0
|
||||
dev: false
|
||||
|
||||
/ajv-formats@2.1.1(ajv@8.14.0):
|
||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||
peerDependencies:
|
||||
@@ -6072,6 +6119,13 @@ packages:
|
||||
escape-string-regexp: 5.0.0
|
||||
dev: false
|
||||
|
||||
/clean-stack@5.2.0:
|
||||
resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==}
|
||||
engines: {node: '>=14.16'}
|
||||
dependencies:
|
||||
escape-string-regexp: 5.0.0
|
||||
dev: false
|
||||
|
||||
/cli-boxes@3.0.0:
|
||||
resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
4
public/templates/doublezero.svg
Normal file
4
public/templates/doublezero.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="128" height="128" fill="white"/>
|
||||
<path d="M41.0227 89.0227C37.1136 89.0076 33.75 88.0455 30.9318 86.1364C28.1288 84.2273 25.9697 81.4621 24.4545 77.8409C22.9545 74.2197 22.2121 69.8636 22.2273 64.7727C22.2273 59.697 22.9773 55.3712 24.4773 51.7955C25.9924 48.2197 28.1515 45.5 30.9545 43.6364C33.7727 41.7576 37.1288 40.8182 41.0227 40.8182C44.9167 40.8182 48.2652 41.7576 51.0682 43.6364C53.8864 45.5152 56.053 48.2424 57.5682 51.8182C59.0833 55.3788 59.8333 59.697 59.8182 64.7727C59.8182 69.8788 59.0606 74.2424 57.5455 77.8636C56.0455 81.4848 53.8939 84.25 51.0909 86.1591C48.2879 88.0682 44.9318 89.0227 41.0227 89.0227ZM41.0227 80.8636C43.6894 80.8636 45.8182 79.5227 47.4091 76.8409C49 74.1591 49.7879 70.1364 49.7727 64.7727C49.7727 61.2424 49.4091 58.303 48.6818 55.9545C47.9697 53.6061 46.9545 51.8409 45.6364 50.6591C44.3333 49.4773 42.7955 48.8864 41.0227 48.8864C38.3712 48.8864 36.25 50.2121 34.6591 52.8636C33.0682 55.5152 32.2652 59.4848 32.25 64.7727C32.25 68.3485 32.6061 71.3333 33.3182 73.7273C34.0455 76.1061 35.0682 77.8939 36.3864 79.0909C37.7045 80.2727 39.25 80.8636 41.0227 80.8636ZM85.0852 89.0227C81.1761 89.0076 77.8125 88.0455 74.9943 86.1364C72.1913 84.2273 70.0322 81.4621 68.517 77.8409C67.017 74.2197 66.2746 69.8636 66.2898 64.7727C66.2898 59.697 67.0398 55.3712 68.5398 51.7955C70.0549 48.2197 72.214 45.5 75.017 43.6364C77.8352 41.7576 81.1913 40.8182 85.0852 40.8182C88.9792 40.8182 92.3277 41.7576 95.1307 43.6364C97.9489 45.5152 100.116 48.2424 101.631 51.8182C103.146 55.3788 103.896 59.697 103.881 64.7727C103.881 69.8788 103.123 74.2424 101.608 77.8636C100.108 81.4848 97.9564 84.25 95.1534 86.1591C92.3504 88.0682 88.9943 89.0227 85.0852 89.0227ZM85.0852 80.8636C87.7519 80.8636 89.8807 79.5227 91.4716 76.8409C93.0625 74.1591 93.8504 70.1364 93.8352 64.7727C93.8352 61.2424 93.4716 58.303 92.7443 55.9545C92.0322 53.6061 91.017 51.8409 89.6989 50.6591C88.3958 49.4773 86.858 48.8864 85.0852 48.8864C82.4337 48.8864 80.3125 50.2121 78.7216 52.8636C77.1307 55.5152 76.3277 59.4848 76.3125 64.7727C76.3125 68.3485 76.6686 71.3333 77.3807 73.7273C78.108 76.1061 79.1307 77.8939 80.4489 79.0909C81.767 80.2727 83.3125 80.8636 85.0852 80.8636Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/templates/listmonk.png
Normal file
BIN
public/templates/listmonk.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
public/templates/open-webui.png
Normal file
BIN
public/templates/open-webui.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -33,8 +33,10 @@ export const createRegistry = async (input: typeof apiCreateRegistry._type) => {
|
||||
});
|
||||
}
|
||||
|
||||
const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`;
|
||||
await execAsync(loginCommand);
|
||||
if (newRegistry.registryType === "cloud") {
|
||||
const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`;
|
||||
await execAsync(loginCommand);
|
||||
}
|
||||
|
||||
return newRegistry;
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ export const admins = pgTable("admin", {
|
||||
githubClientSecret: text("githubClientSecret"),
|
||||
githubInstallationId: text("githubInstallationId"),
|
||||
githubPrivateKey: text("githubPrivateKey"),
|
||||
githubWebhookSecret: text("githubWebhookSecret"),
|
||||
letsEncryptEmail: text("letsEncryptEmail"),
|
||||
sshPrivateKey: text("sshPrivateKey"),
|
||||
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
|
||||
|
||||
@@ -2,9 +2,14 @@ import { type Job, Worker } from "bullmq";
|
||||
import {
|
||||
deployApplication,
|
||||
rebuildApplication,
|
||||
updateApplicationStatus,
|
||||
} from "../api/services/application";
|
||||
import { myQueue, redisConfig } from "./queueSetup";
|
||||
import { deployCompose, rebuildCompose } from "../api/services/compose";
|
||||
import {
|
||||
deployCompose,
|
||||
rebuildCompose,
|
||||
updateCompose,
|
||||
} from "../api/services/compose";
|
||||
|
||||
type DeployJob =
|
||||
| {
|
||||
@@ -29,6 +34,7 @@ export const deploymentWorker = new Worker(
|
||||
async (job: Job<DeploymentJob>) => {
|
||||
try {
|
||||
if (job.data.applicationType === "application") {
|
||||
await updateApplicationStatus(job.data.applicationId, "running");
|
||||
if (job.data.type === "redeploy") {
|
||||
await rebuildApplication({
|
||||
applicationId: job.data.applicationId,
|
||||
@@ -43,6 +49,9 @@ export const deploymentWorker = new Worker(
|
||||
});
|
||||
}
|
||||
} else if (job.data.applicationType === "compose") {
|
||||
await updateCompose(job.data.composeId, {
|
||||
composeStatus: "running",
|
||||
});
|
||||
if (job.data.type === "deploy") {
|
||||
await deployCompose({
|
||||
composeId: job.data.composeId,
|
||||
|
||||
@@ -26,6 +26,9 @@ export const initializePostgres = async () => {
|
||||
RestartPolicy: {
|
||||
Condition: "on-failure",
|
||||
},
|
||||
Placement: {
|
||||
Constraints: ["node.role==manager"],
|
||||
},
|
||||
},
|
||||
Mode: {
|
||||
Replicated: {
|
||||
|
||||
@@ -23,6 +23,9 @@ export const initializeRedis = async () => {
|
||||
RestartPolicy: {
|
||||
Condition: "on-failure",
|
||||
},
|
||||
Placement: {
|
||||
Constraints: ["node.role==manager"],
|
||||
},
|
||||
},
|
||||
Mode: {
|
||||
Replicated: {
|
||||
|
||||
@@ -43,6 +43,9 @@ export const initializeRegistry = async (
|
||||
RestartPolicy: {
|
||||
Condition: "on-failure",
|
||||
},
|
||||
Placement: {
|
||||
Constraints: ["node.role==manager"],
|
||||
},
|
||||
},
|
||||
Mode: {
|
||||
Replicated: {
|
||||
|
||||
@@ -41,6 +41,9 @@ export const initializeTraefik = async () => {
|
||||
RestartPolicy: {
|
||||
Condition: "on-failure",
|
||||
},
|
||||
Placement: {
|
||||
Constraints: ["node.role==manager"],
|
||||
},
|
||||
},
|
||||
Mode: {
|
||||
Replicated: {
|
||||
|
||||
@@ -64,8 +64,8 @@ export const containerExists = async (containerName: string) => {
|
||||
export const stopService = async (appName: string) => {
|
||||
try {
|
||||
await execAsync(`docker service scale ${appName}=0 `);
|
||||
console.log("Service stopped ✅");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return error;
|
||||
}
|
||||
};
|
||||
@@ -93,6 +93,7 @@ export const cleanUpUnusedImages = async () => {
|
||||
try {
|
||||
await execAsync("docker image prune --all --force");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -101,6 +102,7 @@ export const cleanStoppedContainers = async () => {
|
||||
try {
|
||||
await execAsync("docker container prune --force");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -109,6 +111,7 @@ export const cleanUpUnusedVolumes = async () => {
|
||||
try {
|
||||
await execAsync("docker volume prune --force");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -142,6 +145,7 @@ export const startService = async (appName: string) => {
|
||||
try {
|
||||
await execAsync(`docker service scale ${appName}=1 `);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -87,7 +87,6 @@ const addHostToKnownHosts = async (repositoryURL: string) => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const sanitizeRepoPathSSH = (input: string) => {
|
||||
const SSH_PATH_RE = new RegExp(
|
||||
[
|
||||
@@ -113,7 +112,7 @@ const sanitizeRepoPathSSH = (input: string) => {
|
||||
return {
|
||||
user: found.groups?.user ?? "git",
|
||||
domain: found.groups?.domain,
|
||||
port: Number(found.groups?.port ?? 22),
|
||||
port: 22,
|
||||
owner: found.groups?.owner ?? "",
|
||||
repo: found.groups?.repo,
|
||||
get repoPath() {
|
||||
|
||||
31
templates/doublezero/docker-compose.yml
Normal file
31
templates/doublezero/docker-compose.yml
Normal file
@@ -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
|
||||
29
templates/doublezero/index.ts
Normal file
29
templates/doublezero/index.ts
Normal file
@@ -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,
|
||||
};
|
||||
}
|
||||
56
templates/listmonk/docker-compose.yml
Normal file
56
templates/listmonk/docker-compose.yml
Normal file
@@ -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
|
||||
52
templates/listmonk/index.ts
Normal file
52
templates/listmonk/index.ts
Normal file
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
nocodb:
|
||||
image: nocodb/nocodb:0.251.0
|
||||
image: nocodb/nocodb:0.251.1
|
||||
restart: always
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||
37
templates/open-webui/docker-compose.yml
Normal file
37
templates/open-webui/docker-compose.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
|
||||
ollama:
|
||||
volumes:
|
||||
- ollama:/root/.ollama
|
||||
networks:
|
||||
- dokploy-network
|
||||
pull_policy: always
|
||||
tty: true
|
||||
restart: unless-stopped
|
||||
image: ollama/ollama:${OLLAMA_DOCKER_TAG-latest}
|
||||
|
||||
open-webui:
|
||||
image: ghcr.io/open-webui/open-webui:${WEBUI_DOCKER_TAG-main}
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- open-webui:/app/backend/data
|
||||
depends_on:
|
||||
- ollama
|
||||
environment:
|
||||
- 'OLLAMA_BASE_URL=http://ollama:11434'
|
||||
- 'WEBUI_SECRET_KEY='
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.${HASH}.rule=Host(`${OPEN_WEBUI_HOST}`)"
|
||||
- "traefik.http.services.${HASH}.loadbalancer.server.port=${OPEN_WEBUI_PORT}"
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
ollama: {}
|
||||
open-webui: {}
|
||||
22
templates/open-webui/index.ts
Normal file
22
templates/open-webui/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
generateHash,
|
||||
generateRandomDomain,
|
||||
type Template,
|
||||
type Schema,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mainServiceHash = generateHash(schema.projectName);
|
||||
const randomDomain = generateRandomDomain(schema);
|
||||
const envs = [
|
||||
`OPEN_WEBUI_HOST=${randomDomain}`,
|
||||
"OPEN_WEBUI_PORT=8080",
|
||||
`HASH=${mainServiceHash}`,
|
||||
'OLLAMA_DOCKER_TAG=0.1.47',
|
||||
'WEBUI_DOCKER_TAG=0.3.7'
|
||||
];
|
||||
|
||||
return {
|
||||
envs,
|
||||
};
|
||||
}
|
||||
@@ -216,7 +216,7 @@ export const templates: TemplateData[] = [
|
||||
{
|
||||
id: "nocodb",
|
||||
name: "NocoDB",
|
||||
version: "0.251.0",
|
||||
version: "0.251.1",
|
||||
description:
|
||||
"NocoDB is an opensource Airtable alternative that turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart spreadsheet.",
|
||||
|
||||
@@ -318,4 +318,46 @@ export const templates: TemplateData[] = [
|
||||
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),
|
||||
}
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user