mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acd722678e | ||
|
|
3750977f41 | ||
|
|
9b401059b0 | ||
|
|
6a3ef5c860 | ||
|
|
a5eb4b0a72 | ||
|
|
b5c0876dd4 | ||
|
|
9745d12ac8 | ||
|
|
4aaf04ce74 | ||
|
|
ecfca9419a | ||
|
|
dfd6764320 | ||
|
|
73bf5274f5 | ||
|
|
fc38a42587 | ||
|
|
9b255964fe | ||
|
|
29f55ca1a0 | ||
|
|
6a5fb8faff | ||
|
|
5c225c8d42 | ||
|
|
c1c5fc978b | ||
|
|
18b4b23f79 | ||
|
|
727e50648e | ||
|
|
0b2b20caeb | ||
|
|
6cc64b4454 | ||
|
|
7027f39c48 |
@@ -17,7 +17,7 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input, NumberInput } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -125,28 +125,14 @@ export const UpdatePort = ({ portId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Published Port</FormLabel>
|
<FormLabel>Published Port</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<NumberInput placeholder="1-65535" {...field} />
|
||||||
placeholder="1-65535"
|
|
||||||
{...field}
|
|
||||||
value={field.value?.toString() || ""}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
if (value === "") {
|
|
||||||
field.onChange(0);
|
|
||||||
} else {
|
|
||||||
const number = Number.parseInt(value, 10);
|
|
||||||
if (!Number.isNaN(number)) {
|
|
||||||
field.onChange(number);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="targetPort"
|
name="targetPort"
|
||||||
@@ -154,22 +140,7 @@ export const UpdatePort = ({ portId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Target Port</FormLabel>
|
<FormLabel>Target Port</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input placeholder="1-65535" {...field} />
|
||||||
placeholder="1-65535"
|
|
||||||
{...field}
|
|
||||||
value={field.value?.toString() || ""}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
if (value === "") {
|
|
||||||
field.onChange(0);
|
|
||||||
} else {
|
|
||||||
const number = Number.parseInt(value, 10);
|
|
||||||
if (!Number.isNaN(number)) {
|
|
||||||
field.onChange(number);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input, NumberInput } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -228,13 +228,7 @@ export const AddDomain = ({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Container Port</FormLabel>
|
<FormLabel>Container Port</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<NumberInput placeholder={"3000"} {...field} />
|
||||||
placeholder={"3000"}
|
|
||||||
{...field}
|
|
||||||
onChange={(e) => {
|
|
||||||
field.onChange(Number.parseInt(e.target.value));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input, NumberInput } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -364,13 +364,7 @@ export const AddDomainCompose = ({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Container Port</FormLabel>
|
<FormLabel>Container Port</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<NumberInput placeholder={"3000"} {...field} />
|
||||||
placeholder={"3000"}
|
|
||||||
{...field}
|
|
||||||
onChange={(e) => {
|
|
||||||
field.onChange(Number.parseInt(e.target.value));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
|
|||||||
const { data, refetch } = api.mariadb.one.useQuery({ mariadbId });
|
const { data, refetch } = api.mariadb.one.useQuery({ mariadbId });
|
||||||
const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation();
|
const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation();
|
||||||
const [connectionUrl, setConnectionUrl] = useState("");
|
const [connectionUrl, setConnectionUrl] = useState("");
|
||||||
|
const getIp = data?.server?.ipAddress || ip;
|
||||||
const form = useForm<DockerProvider>({
|
const form = useForm<DockerProvider>({
|
||||||
defaultValues: {},
|
defaultValues: {},
|
||||||
resolver: zodResolver(DockerProviderSchema),
|
resolver: zodResolver(DockerProviderSchema),
|
||||||
@@ -79,7 +80,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
|
|||||||
const buildConnectionUrl = () => {
|
const buildConnectionUrl = () => {
|
||||||
const port = form.watch("externalPort") || data?.externalPort;
|
const port = form.watch("externalPort") || data?.externalPort;
|
||||||
|
|
||||||
return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${ip}:${port}/${data?.databaseName}`;
|
return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
setConnectionUrl(buildConnectionUrl());
|
setConnectionUrl(buildConnectionUrl());
|
||||||
@@ -90,7 +91,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
|
|||||||
form,
|
form,
|
||||||
data?.databaseName,
|
data?.databaseName,
|
||||||
data?.databaseUser,
|
data?.databaseUser,
|
||||||
ip,
|
getIp,
|
||||||
]);
|
]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
|
|||||||
const { data, refetch } = api.mongo.one.useQuery({ mongoId });
|
const { data, refetch } = api.mongo.one.useQuery({ mongoId });
|
||||||
const { mutateAsync, isLoading } = api.mongo.saveExternalPort.useMutation();
|
const { mutateAsync, isLoading } = api.mongo.saveExternalPort.useMutation();
|
||||||
const [connectionUrl, setConnectionUrl] = useState("");
|
const [connectionUrl, setConnectionUrl] = useState("");
|
||||||
|
const getIp = data?.server?.ipAddress || ip;
|
||||||
const form = useForm<DockerProvider>({
|
const form = useForm<DockerProvider>({
|
||||||
defaultValues: {},
|
defaultValues: {},
|
||||||
resolver: zodResolver(DockerProviderSchema),
|
resolver: zodResolver(DockerProviderSchema),
|
||||||
@@ -80,7 +80,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
|
|||||||
const buildConnectionUrl = () => {
|
const buildConnectionUrl = () => {
|
||||||
const port = form.watch("externalPort") || data?.externalPort;
|
const port = form.watch("externalPort") || data?.externalPort;
|
||||||
|
|
||||||
return `mongodb://${data?.databaseUser}:${data?.databasePassword}@${ip}:${port}`;
|
return `mongodb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
setConnectionUrl(buildConnectionUrl());
|
setConnectionUrl(buildConnectionUrl());
|
||||||
@@ -90,7 +90,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
|
|||||||
data?.databasePassword,
|
data?.databasePassword,
|
||||||
form,
|
form,
|
||||||
data?.databaseUser,
|
data?.databaseUser,
|
||||||
ip,
|
getIp,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
|
|||||||
const { data, refetch } = api.mysql.one.useQuery({ mysqlId });
|
const { data, refetch } = api.mysql.one.useQuery({ mysqlId });
|
||||||
const { mutateAsync, isLoading } = api.mysql.saveExternalPort.useMutation();
|
const { mutateAsync, isLoading } = api.mysql.saveExternalPort.useMutation();
|
||||||
const [connectionUrl, setConnectionUrl] = useState("");
|
const [connectionUrl, setConnectionUrl] = useState("");
|
||||||
|
const getIp = data?.server?.ipAddress || ip;
|
||||||
const form = useForm<DockerProvider>({
|
const form = useForm<DockerProvider>({
|
||||||
defaultValues: {},
|
defaultValues: {},
|
||||||
resolver: zodResolver(DockerProviderSchema),
|
resolver: zodResolver(DockerProviderSchema),
|
||||||
@@ -80,7 +80,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
|
|||||||
const buildConnectionUrl = () => {
|
const buildConnectionUrl = () => {
|
||||||
const port = form.watch("externalPort") || data?.externalPort;
|
const port = form.watch("externalPort") || data?.externalPort;
|
||||||
|
|
||||||
return `mysql://${data?.databaseUser}:${data?.databasePassword}@${ip}:${port}/${data?.databaseName}`;
|
return `mysql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
setConnectionUrl(buildConnectionUrl());
|
setConnectionUrl(buildConnectionUrl());
|
||||||
@@ -91,7 +91,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
|
|||||||
data?.databaseName,
|
data?.databaseName,
|
||||||
data?.databaseUser,
|
data?.databaseUser,
|
||||||
form,
|
form,
|
||||||
ip,
|
getIp,
|
||||||
]);
|
]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
|
|||||||
const { data, refetch } = api.postgres.one.useQuery({ postgresId });
|
const { data, refetch } = api.postgres.one.useQuery({ postgresId });
|
||||||
const { mutateAsync, isLoading } =
|
const { mutateAsync, isLoading } =
|
||||||
api.postgres.saveExternalPort.useMutation();
|
api.postgres.saveExternalPort.useMutation();
|
||||||
|
const getIp = data?.server?.ipAddress || ip;
|
||||||
const [connectionUrl, setConnectionUrl] = useState("");
|
const [connectionUrl, setConnectionUrl] = useState("");
|
||||||
|
|
||||||
const form = useForm<DockerProvider>({
|
const form = useForm<DockerProvider>({
|
||||||
@@ -79,10 +80,9 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const buildConnectionUrl = () => {
|
const buildConnectionUrl = () => {
|
||||||
const hostname = window.location.hostname;
|
|
||||||
const port = form.watch("externalPort") || data?.externalPort;
|
const port = form.watch("externalPort") || data?.externalPort;
|
||||||
|
|
||||||
return `postgresql://${data?.databaseUser}:${data?.databasePassword}@${ip}:${port}/${data?.databaseName}`;
|
return `postgresql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
setConnectionUrl(buildConnectionUrl());
|
setConnectionUrl(buildConnectionUrl());
|
||||||
@@ -92,7 +92,7 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
|
|||||||
data?.databasePassword,
|
data?.databasePassword,
|
||||||
form,
|
form,
|
||||||
data?.databaseName,
|
data?.databaseName,
|
||||||
ip,
|
getIp,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import { slugify } from "@/lib/slug";
|
|||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { CircuitBoard, HelpCircle } from "lucide-react";
|
import { CircuitBoard, HelpCircle } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -71,6 +71,7 @@ interface Props {
|
|||||||
|
|
||||||
export const AddCompose = ({ projectId, projectName }: Props) => {
|
export const AddCompose = ({ projectId, projectName }: Props) => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
const slug = slugify(projectName);
|
const slug = slugify(projectName);
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
const { mutateAsync, isLoading, error, isError } =
|
const { mutateAsync, isLoading, error, isError } =
|
||||||
@@ -101,6 +102,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
|
|||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Compose Created");
|
toast.success("Compose Created");
|
||||||
|
setVisible(false);
|
||||||
await utils.project.one.invalidate({
|
await utils.project.one.invalidate({
|
||||||
projectId,
|
projectId,
|
||||||
});
|
});
|
||||||
@@ -111,7 +113,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog open={visible} onOpenChange={setVisible}>
|
||||||
<DialogTrigger className="w-full">
|
<DialogTrigger className="w-full">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="w-full cursor-pointer space-x-3"
|
className="w-full cursor-pointer space-x-3"
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
|
|||||||
const { data, refetch } = api.redis.one.useQuery({ redisId });
|
const { data, refetch } = api.redis.one.useQuery({ redisId });
|
||||||
const { mutateAsync, isLoading } = api.redis.saveExternalPort.useMutation();
|
const { mutateAsync, isLoading } = api.redis.saveExternalPort.useMutation();
|
||||||
const [connectionUrl, setConnectionUrl] = useState("");
|
const [connectionUrl, setConnectionUrl] = useState("");
|
||||||
|
const getIp = data?.server?.ipAddress || ip;
|
||||||
|
|
||||||
const form = useForm<DockerProvider>({
|
const form = useForm<DockerProvider>({
|
||||||
defaultValues: {},
|
defaultValues: {},
|
||||||
@@ -81,11 +82,11 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
|
|||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
const port = form.watch("externalPort") || data?.externalPort;
|
const port = form.watch("externalPort") || data?.externalPort;
|
||||||
|
|
||||||
return `redis://default:${data?.databasePassword}@${ip}:${port}`;
|
return `redis://default:${data?.databasePassword}@${getIp}:${port}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
setConnectionUrl(buildConnectionUrl());
|
setConnectionUrl(buildConnectionUrl());
|
||||||
}, [data?.appName, data?.externalPort, data?.databasePassword, form, ip]);
|
}, [data?.appName, data?.externalPort, data?.databasePassword, form, getIp]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex w-full flex-col gap-5 ">
|
<div className="flex w-full flex-col gap-5 ">
|
||||||
|
|||||||
@@ -17,10 +17,18 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { AlertTriangle, Container } from "lucide-react";
|
import { AlertTriangle, Container } from "lucide-react";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -36,10 +44,9 @@ const AddRegistrySchema = z.object({
|
|||||||
password: z.string().min(1, {
|
password: z.string().min(1, {
|
||||||
message: "Password is required",
|
message: "Password is required",
|
||||||
}),
|
}),
|
||||||
registryUrl: z.string().min(1, {
|
registryUrl: z.string(),
|
||||||
message: "Registry URL is required",
|
|
||||||
}),
|
|
||||||
imagePrefix: z.string(),
|
imagePrefix: z.string(),
|
||||||
|
serverId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type AddRegistry = z.infer<typeof AddRegistrySchema>;
|
type AddRegistry = z.infer<typeof AddRegistrySchema>;
|
||||||
@@ -48,9 +55,9 @@ export const AddRegistry = () => {
|
|||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const { mutateAsync, error, isError } = api.registry.create.useMutation();
|
const { mutateAsync, error, isError } = api.registry.create.useMutation();
|
||||||
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
const { mutateAsync: testRegistry, isLoading } =
|
const { mutateAsync: testRegistry, isLoading } =
|
||||||
api.registry.testRegistry.useMutation();
|
api.registry.testRegistry.useMutation();
|
||||||
const router = useRouter();
|
|
||||||
const form = useForm<AddRegistry>({
|
const form = useForm<AddRegistry>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
username: "",
|
username: "",
|
||||||
@@ -58,6 +65,7 @@ export const AddRegistry = () => {
|
|||||||
registryUrl: "",
|
registryUrl: "",
|
||||||
imagePrefix: "",
|
imagePrefix: "",
|
||||||
registryName: "",
|
registryName: "",
|
||||||
|
serverId: "",
|
||||||
},
|
},
|
||||||
resolver: zodResolver(AddRegistrySchema),
|
resolver: zodResolver(AddRegistrySchema),
|
||||||
});
|
});
|
||||||
@@ -67,6 +75,7 @@ export const AddRegistry = () => {
|
|||||||
const registryUrl = form.watch("registryUrl");
|
const registryUrl = form.watch("registryUrl");
|
||||||
const registryName = form.watch("registryName");
|
const registryName = form.watch("registryName");
|
||||||
const imagePrefix = form.watch("imagePrefix");
|
const imagePrefix = form.watch("imagePrefix");
|
||||||
|
const serverId = form.watch("serverId");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset({
|
form.reset({
|
||||||
@@ -74,6 +83,7 @@ export const AddRegistry = () => {
|
|||||||
password: "",
|
password: "",
|
||||||
registryUrl: "",
|
registryUrl: "",
|
||||||
imagePrefix: "",
|
imagePrefix: "",
|
||||||
|
serverId: "",
|
||||||
});
|
});
|
||||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
||||||
|
|
||||||
@@ -85,6 +95,7 @@ export const AddRegistry = () => {
|
|||||||
registryUrl: data.registryUrl,
|
registryUrl: data.registryUrl,
|
||||||
registryType: "cloud",
|
registryType: "cloud",
|
||||||
imagePrefix: data.imagePrefix,
|
imagePrefix: data.imagePrefix,
|
||||||
|
serverId: data.serverId,
|
||||||
})
|
})
|
||||||
.then(async (data) => {
|
.then(async (data) => {
|
||||||
await utils.registry.all.invalidate();
|
await utils.registry.all.invalidate();
|
||||||
@@ -211,34 +222,77 @@ export const AddRegistry = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter className="flex flex-row w-full sm:justify-between gap-4 flex-wrap">
|
<DialogFooter className="flex flex-col w-full sm:justify-between gap-4 flex-wrap sm:flex-col">
|
||||||
<Button
|
<div className="flex flex-col gap-4 border p-2 rounded-lg">
|
||||||
type="button"
|
<span className="text-sm text-muted-foreground">
|
||||||
variant={"secondary"}
|
Select a server to test the registry. If you don't have a
|
||||||
isLoading={isLoading}
|
server choose the default one.
|
||||||
onClick={async () => {
|
</span>
|
||||||
await testRegistry({
|
<FormField
|
||||||
username: username,
|
control={form.control}
|
||||||
password: password,
|
name="serverId"
|
||||||
registryUrl: registryUrl,
|
render={({ field }) => (
|
||||||
registryName: registryName,
|
<FormItem>
|
||||||
registryType: "cloud",
|
<FormLabel>Server (Optional)</FormLabel>
|
||||||
imagePrefix: imagePrefix,
|
<FormControl>
|
||||||
})
|
<Select
|
||||||
.then((data) => {
|
onValueChange={field.onChange}
|
||||||
if (data) {
|
defaultValue={field.value}
|
||||||
toast.success("Registry Tested Successfully");
|
>
|
||||||
} else {
|
<SelectTrigger className="w-full">
|
||||||
toast.error("Registry Test Failed");
|
<SelectValue placeholder="Select a server" />
|
||||||
}
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Servers</SelectLabel>
|
||||||
|
{servers?.map((server) => (
|
||||||
|
<SelectItem
|
||||||
|
key={server.serverId}
|
||||||
|
value={server.serverId}
|
||||||
|
>
|
||||||
|
{server.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectItem value={"none"}>None</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={"secondary"}
|
||||||
|
isLoading={isLoading}
|
||||||
|
onClick={async () => {
|
||||||
|
await testRegistry({
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
registryUrl: registryUrl,
|
||||||
|
registryName: registryName,
|
||||||
|
registryType: "cloud",
|
||||||
|
imagePrefix: imagePrefix,
|
||||||
|
serverId: serverId,
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.then((data) => {
|
||||||
toast.error("Error to test the registry");
|
if (data) {
|
||||||
});
|
toast.success("Registry Tested Successfully");
|
||||||
}}
|
} else {
|
||||||
>
|
toast.error("Registry Test Failed");
|
||||||
Test Registry
|
}
|
||||||
</Button>
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error to test the registry");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Test Registry
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button isLoading={form.formState.isSubmitting} type="submit">
|
<Button isLoading={form.formState.isSubmitting} type="submit">
|
||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -34,10 +43,9 @@ const updateRegistry = z.object({
|
|||||||
message: "Username is required",
|
message: "Username is required",
|
||||||
}),
|
}),
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
registryUrl: z.string().min(1, {
|
registryUrl: z.string(),
|
||||||
message: "Registry URL is required",
|
|
||||||
}),
|
|
||||||
imagePrefix: z.string(),
|
imagePrefix: z.string(),
|
||||||
|
serverId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type UpdateRegistry = z.infer<typeof updateRegistry>;
|
type UpdateRegistry = z.infer<typeof updateRegistry>;
|
||||||
@@ -48,6 +56,8 @@ interface Props {
|
|||||||
|
|
||||||
export const UpdateDockerRegistry = ({ registryId }: Props) => {
|
export const UpdateDockerRegistry = ({ registryId }: Props) => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
|
|
||||||
const { mutateAsync: testRegistry, isLoading } =
|
const { mutateAsync: testRegistry, isLoading } =
|
||||||
api.registry.testRegistry.useMutation();
|
api.registry.testRegistry.useMutation();
|
||||||
const { data, refetch } = api.registry.one.useQuery(
|
const { data, refetch } = api.registry.one.useQuery(
|
||||||
@@ -69,15 +79,19 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
|
|||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
registryUrl: "",
|
registryUrl: "",
|
||||||
|
serverId: "",
|
||||||
},
|
},
|
||||||
resolver: zodResolver(updateRegistry),
|
resolver: zodResolver(updateRegistry),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(form.formState.errors);
|
||||||
|
|
||||||
const password = form.watch("password");
|
const password = form.watch("password");
|
||||||
const username = form.watch("username");
|
const username = form.watch("username");
|
||||||
const registryUrl = form.watch("registryUrl");
|
const registryUrl = form.watch("registryUrl");
|
||||||
const registryName = form.watch("registryName");
|
const registryName = form.watch("registryName");
|
||||||
const imagePrefix = form.watch("imagePrefix");
|
const imagePrefix = form.watch("imagePrefix");
|
||||||
|
const serverId = form.watch("serverId");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -87,6 +101,7 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
|
|||||||
username: data.username || "",
|
username: data.username || "",
|
||||||
password: "",
|
password: "",
|
||||||
registryUrl: data.registryUrl || "",
|
registryUrl: data.registryUrl || "",
|
||||||
|
serverId: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form, form.reset, data]);
|
}, [form, form.reset, data]);
|
||||||
@@ -99,6 +114,7 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
|
|||||||
username: data.username,
|
username: data.username,
|
||||||
registryUrl: data.registryUrl,
|
registryUrl: data.registryUrl,
|
||||||
imagePrefix: data.imagePrefix,
|
imagePrefix: data.imagePrefix,
|
||||||
|
serverId: data.serverId,
|
||||||
})
|
})
|
||||||
.then(async (data) => {
|
.then(async (data) => {
|
||||||
toast.success("Registry Updated");
|
toast.success("Registry Updated");
|
||||||
@@ -224,13 +240,47 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<DialogFooter
|
<DialogFooter className="flex flex-col w-full sm:justify-between gap-4 flex-wrap sm:flex-col">
|
||||||
className={cn(
|
<div className="flex flex-col gap-4 border p-2 rounded-lg">
|
||||||
isCloud ? "sm:justify-between " : "",
|
<span className="text-sm text-muted-foreground">
|
||||||
"flex flex-row w-full gap-4 flex-wrap",
|
Select a server to test the registry. If you don't have a server
|
||||||
)}
|
choose the default one.
|
||||||
>
|
</span>
|
||||||
{isCloud && (
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="serverId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Server (Optional)</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue placeholder="Select a server" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectLabel>Servers</SelectLabel>
|
||||||
|
{servers?.map((server) => (
|
||||||
|
<SelectItem
|
||||||
|
key={server.serverId}
|
||||||
|
value={server.serverId}
|
||||||
|
>
|
||||||
|
{server.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectItem value={"none"}>None</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
@@ -243,6 +293,7 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
|
|||||||
registryName: registryName,
|
registryName: registryName,
|
||||||
registryType: "cloud",
|
registryType: "cloud",
|
||||||
imagePrefix: imagePrefix,
|
imagePrefix: imagePrefix,
|
||||||
|
serverId: serverId,
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -258,12 +309,12 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
|
|||||||
>
|
>
|
||||||
Test Registry
|
Test Registry
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
isLoading={form.formState.isSubmitting}
|
isLoading={form.formState.isSubmitting}
|
||||||
form="hook-form"
|
|
||||||
type="submit"
|
type="submit"
|
||||||
|
form="hook-form"
|
||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -31,4 +31,39 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
);
|
);
|
||||||
Input.displayName = "Input";
|
Input.displayName = "Input";
|
||||||
|
|
||||||
export { Input };
|
const NumberInput = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
|
({ className, errorMessage, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
className={cn("text-left", className)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
value={props.value === undefined ? undefined : String(props.value)}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
if (value === "") {
|
||||||
|
props.onChange?.(e);
|
||||||
|
} else {
|
||||||
|
const number = Number.parseInt(value, 10);
|
||||||
|
if (!Number.isNaN(number)) {
|
||||||
|
const syntheticEvent = {
|
||||||
|
...e,
|
||||||
|
target: {
|
||||||
|
...e.target,
|
||||||
|
value: number,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
props.onChange?.(
|
||||||
|
syntheticEvent as unknown as React.ChangeEvent<HTMLInputElement>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
NumberInput.displayName = "NumberInput";
|
||||||
|
|
||||||
|
export { Input, NumberInput };
|
||||||
|
|||||||
1
apps/dokploy/drizzle/0038_rapid_landau.sql
Normal file
1
apps/dokploy/drizzle/0038_rapid_landau.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "registry" ALTER COLUMN "registryUrl" SET DEFAULT '';
|
||||||
3824
apps/dokploy/drizzle/meta/0038_snapshot.json
Normal file
3824
apps/dokploy/drizzle/meta/0038_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -267,6 +267,13 @@
|
|||||||
"when": 1726988289562,
|
"when": 1726988289562,
|
||||||
"tag": "0037_legal_namor",
|
"tag": "0037_legal_namor",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 38,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1727942090102,
|
||||||
|
"tag": "0038_rapid_landau",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.9.2",
|
"version": "v0.9.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const destinationRouter = createTRPCRouter({
|
|||||||
const destination = await findDestinationById(input.destinationId);
|
const destination = await findDestinationById(input.destinationId);
|
||||||
return destination;
|
return destination;
|
||||||
}),
|
}),
|
||||||
all: adminProcedure.query(async () => {
|
all: protectedProcedure.query(async () => {
|
||||||
return await db.query.destinations.findMany({});
|
return await db.query.destinations.findMany({});
|
||||||
}),
|
}),
|
||||||
remove: adminProcedure
|
remove: adminProcedure
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
apiUpdateRegistry,
|
apiUpdateRegistry,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import { initializeRegistry } from "@/server/setup/registry-setup";
|
import { initializeRegistry } from "@/server/setup/registry-setup";
|
||||||
import { execAsync } from "@/server/utils/process/execAsync";
|
import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync";
|
||||||
import { manageRegistry } from "@/server/utils/traefik/registry";
|
import { manageRegistry } from "@/server/utils/traefik/registry";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import {
|
import {
|
||||||
@@ -58,7 +58,13 @@ export const registryRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`;
|
const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`;
|
||||||
await execAsync(loginCommand);
|
|
||||||
|
if (input.serverId && input.serverId !== "none") {
|
||||||
|
await execAsyncRemote(input.serverId, loginCommand);
|
||||||
|
} else {
|
||||||
|
await execAsync(loginCommand);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error Registry:", error);
|
console.log("Error Registry:", error);
|
||||||
@@ -78,6 +84,7 @@ export const registryRouter = createTRPCRouter({
|
|||||||
? input.registryUrl
|
? input.registryUrl
|
||||||
: "dokploy-registry.docker.localhost",
|
: "dokploy-registry.docker.localhost",
|
||||||
imagePrefix: null,
|
imagePrefix: null,
|
||||||
|
serverId: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
await manageRegistry(selfHostedRegistry);
|
await manageRegistry(selfHostedRegistry);
|
||||||
@@ -86,3 +93,17 @@ export const registryRouter = createTRPCRouter({
|
|||||||
return selfHostedRegistry;
|
return selfHostedRegistry;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const shellEscape = (str: string) => {
|
||||||
|
const ret = [];
|
||||||
|
let s = str;
|
||||||
|
if (/[^A-Za-z0-9_\/:=-]/.test(s)) {
|
||||||
|
s = `'${s.replace(/'/g, "'\\''")}'`;
|
||||||
|
s = s
|
||||||
|
.replace(/^(?:'')+/g, "") // unduplicate single-quote at the beginning
|
||||||
|
.replace(/\\'''/g, "\\'"); // remove non-escaped single-quote if there are enclosed between 2 escaped
|
||||||
|
}
|
||||||
|
ret.push(s);
|
||||||
|
|
||||||
|
return ret.join(" ");
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { db } from "@/server/db";
|
|||||||
import { type apiCreateRegistry, registry } from "@/server/db/schema";
|
import { type apiCreateRegistry, registry } from "@/server/db/schema";
|
||||||
import { initializeRegistry } from "@/server/setup/registry-setup";
|
import { initializeRegistry } from "@/server/setup/registry-setup";
|
||||||
import { removeService } from "@/server/utils/docker/utils";
|
import { removeService } from "@/server/utils/docker/utils";
|
||||||
import { execAsync } from "@/server/utils/process/execAsync";
|
import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync";
|
||||||
import {
|
import {
|
||||||
manageRegistry,
|
manageRegistry,
|
||||||
removeSelfHostedRegistry,
|
removeSelfHostedRegistry,
|
||||||
@@ -32,9 +32,10 @@ export const createRegistry = async (input: typeof apiCreateRegistry._type) => {
|
|||||||
message: "Error input: Inserting registry",
|
message: "Error input: Inserting registry",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`;
|
||||||
if (newRegistry.registryType === "cloud") {
|
if (input.serverId && input.serverId !== "none") {
|
||||||
const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`;
|
await execAsyncRemote(input.serverId, loginCommand);
|
||||||
|
} else if (newRegistry.registryType === "cloud") {
|
||||||
await execAsync(loginCommand);
|
await execAsync(loginCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ export const removeRegistry = async (registryId: string) => {
|
|||||||
|
|
||||||
export const updateRegistry = async (
|
export const updateRegistry = async (
|
||||||
registryId: string,
|
registryId: string,
|
||||||
registryData: Partial<Registry>,
|
registryData: Partial<Registry> & { serverId?: string | null },
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await db
|
const response = await db
|
||||||
@@ -92,6 +93,13 @@ export const updateRegistry = async (
|
|||||||
await manageRegistry(response);
|
await manageRegistry(response);
|
||||||
await initializeRegistry(response.username, response.password);
|
await initializeRegistry(response.username, response.password);
|
||||||
}
|
}
|
||||||
|
const loginCommand = `echo ${response?.password} | docker login ${response?.registryUrl} --username ${response?.username} --password-stdin`;
|
||||||
|
|
||||||
|
if (registryData?.serverId && registryData?.serverId !== "none") {
|
||||||
|
await execAsyncRemote(registryData.serverId, loginCommand);
|
||||||
|
} else if (response?.registryType === "cloud") {
|
||||||
|
await execAsync(loginCommand);
|
||||||
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const registry = pgTable("registry", {
|
|||||||
imagePrefix: text("imagePrefix"),
|
imagePrefix: text("imagePrefix"),
|
||||||
username: text("username").notNull(),
|
username: text("username").notNull(),
|
||||||
password: text("password").notNull(),
|
password: text("password").notNull(),
|
||||||
registryUrl: text("registryUrl").notNull(),
|
registryUrl: text("registryUrl").notNull().default(""),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
@@ -45,7 +45,7 @@ const createSchema = createInsertSchema(registry, {
|
|||||||
registryName: z.string().min(1),
|
registryName: z.string().min(1),
|
||||||
username: z.string().min(1),
|
username: z.string().min(1),
|
||||||
password: z.string().min(1),
|
password: z.string().min(1),
|
||||||
registryUrl: z.string().min(1),
|
registryUrl: z.string(),
|
||||||
adminId: z.string().min(1),
|
adminId: z.string().min(1),
|
||||||
registryId: z.string().min(1),
|
registryId: z.string().min(1),
|
||||||
registryType: z.enum(["selfHosted", "cloud"]),
|
registryType: z.enum(["selfHosted", "cloud"]),
|
||||||
@@ -62,7 +62,10 @@ export const apiCreateRegistry = createSchema
|
|||||||
registryType: z.enum(["selfHosted", "cloud"]),
|
registryType: z.enum(["selfHosted", "cloud"]),
|
||||||
imagePrefix: z.string().nullable().optional(),
|
imagePrefix: z.string().nullable().optional(),
|
||||||
})
|
})
|
||||||
.required();
|
.required()
|
||||||
|
.extend({
|
||||||
|
serverId: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export const apiTestRegistry = createSchema.pick({}).extend({
|
export const apiTestRegistry = createSchema.pick({}).extend({
|
||||||
registryName: z.string().min(1),
|
registryName: z.string().min(1),
|
||||||
@@ -71,6 +74,7 @@ export const apiTestRegistry = createSchema.pick({}).extend({
|
|||||||
registryUrl: z.string(),
|
registryUrl: z.string(),
|
||||||
registryType: z.enum(["selfHosted", "cloud"]),
|
registryType: z.enum(["selfHosted", "cloud"]),
|
||||||
imagePrefix: z.string().nullable().optional(),
|
imagePrefix: z.string().nullable().optional(),
|
||||||
|
serverId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiRemoveRegistry = createSchema
|
export const apiRemoveRegistry = createSchema
|
||||||
@@ -87,6 +91,7 @@ export const apiFindOneRegistry = createSchema
|
|||||||
|
|
||||||
export const apiUpdateRegistry = createSchema.partial().extend({
|
export const apiUpdateRegistry = createSchema.partial().extend({
|
||||||
registryId: z.string().min(1),
|
registryId: z.string().min(1),
|
||||||
|
serverId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiEnableSelfHostedRegistry = createSchema
|
export const apiEnableSelfHostedRegistry = createSchema
|
||||||
|
|||||||
@@ -147,15 +147,15 @@ export const cloneRawBitbucketRepositoryRemote = async (compose: Compose) => {
|
|||||||
const bitbucketProvider = await findBitbucketById(bitbucketId);
|
const bitbucketProvider = await findBitbucketById(bitbucketId);
|
||||||
const basePath = COMPOSE_PATH;
|
const basePath = COMPOSE_PATH;
|
||||||
const outputPath = join(basePath, appName, "code");
|
const outputPath = join(basePath, appName, "code");
|
||||||
await recreateDirectory(outputPath);
|
|
||||||
const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
|
const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
|
||||||
const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
|
const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await execAsyncRemote(
|
const command = `
|
||||||
serverId,
|
rm -rf ${outputPath};
|
||||||
`git clone --branch ${bitbucketBranch} --depth 1 ${cloneUrl} ${outputPath}`,
|
git clone --branch ${bitbucketBranch} --depth 1 ${cloneUrl} ${outputPath}
|
||||||
);
|
`;
|
||||||
|
await execAsyncRemote(serverId, command);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,13 +271,13 @@ export const cloneRawGithubRepositoryRemote = async (compose: Compose) => {
|
|||||||
const octokit = authGithub(githubProvider);
|
const octokit = authGithub(githubProvider);
|
||||||
const token = await getGithubToken(octokit);
|
const token = await getGithubToken(octokit);
|
||||||
const repoclone = `github.com/${owner}/${repository}.git`;
|
const repoclone = `github.com/${owner}/${repository}.git`;
|
||||||
await recreateDirectory(outputPath);
|
|
||||||
const cloneUrl = `https://oauth2:${token}@${repoclone}`;
|
const cloneUrl = `https://oauth2:${token}@${repoclone}`;
|
||||||
try {
|
try {
|
||||||
await execAsyncRemote(
|
const command = `
|
||||||
serverId,
|
rm -rf ${outputPath};
|
||||||
`git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}`,
|
git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}
|
||||||
);
|
`;
|
||||||
|
await execAsyncRemote(serverId, command);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -390,14 +390,14 @@ export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
|
|||||||
await refreshGitlabToken(gitlabId);
|
await refreshGitlabToken(gitlabId);
|
||||||
const basePath = COMPOSE_PATH;
|
const basePath = COMPOSE_PATH;
|
||||||
const outputPath = join(basePath, appName, "code");
|
const outputPath = join(basePath, appName, "code");
|
||||||
await recreateDirectory(outputPath);
|
|
||||||
const repoclone = `gitlab.com/${gitlabPathNamespace}.git`;
|
const repoclone = `gitlab.com/${gitlabPathNamespace}.git`;
|
||||||
const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`;
|
const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`;
|
||||||
try {
|
try {
|
||||||
await execAsyncRemote(
|
const command = `
|
||||||
serverId,
|
rm -rf ${outputPath};
|
||||||
`git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}`,
|
git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}
|
||||||
);
|
`;
|
||||||
|
await execAsyncRemote(serverId, command);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export const createComposeFileRawRemote = async (compose: Compose) => {
|
|||||||
try {
|
try {
|
||||||
const encodedContent = encodeBase64(composeFile);
|
const encodedContent = encodeBase64(composeFile);
|
||||||
const command = `
|
const command = `
|
||||||
|
rm -rf ${outputPath};
|
||||||
mkdir -p ${outputPath};
|
mkdir -p ${outputPath};
|
||||||
echo "${encodedContent}" | base64 -d > "${filePath}";
|
echo "${encodedContent}" | base64 -d > "${filePath}";
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ export const setupTerminalWebSocketServer = (
|
|||||||
"StrictHostKeyChecking=no",
|
"StrictHostKeyChecking=no",
|
||||||
"-i",
|
"-i",
|
||||||
privateKey,
|
privateKey,
|
||||||
|
"-p",
|
||||||
|
`${server.port}`,
|
||||||
`${server.username}@${server.ipAddress}`,
|
`${server.username}@${server.ipAddress}`,
|
||||||
];
|
];
|
||||||
const ptyProcess = spawn("ssh", sshCommand.slice(1), {
|
const ptyProcess = spawn("ssh", sshCommand.slice(1), {
|
||||||
|
|||||||
@@ -1,114 +1,133 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
install_dokploy() {
|
||||||
|
if [ "$(id -u)" != "0" ]; then
|
||||||
|
echo "This script must be run as root" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$(id -u)" != "0" ]; then
|
# check if is Mac OS
|
||||||
echo "This script must be run as root" >&2
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
exit 1
|
echo "This script must be run on Linux" >&2
|
||||||
fi
|
exit 1
|
||||||
|
fi
|
||||||
# check if is Mac OS
|
|
||||||
if [ "$(uname)" = "Darwin" ]; then
|
|
||||||
echo "This script must be run on Linux" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# check if is running inside a container
|
# check if is running inside a container
|
||||||
if [ -f /.dockerenv ]; then
|
if [ -f /.dockerenv ]; then
|
||||||
echo "This script must be run on Linux" >&2
|
echo "This script must be run on Linux" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# check if something is running on port 80
|
# check if something is running on port 80
|
||||||
if ss -tulnp | grep ':80 ' >/dev/null; then
|
if ss -tulnp | grep ':80 ' >/dev/null; then
|
||||||
echo "Error: something is already running on port 80" >&2
|
echo "Error: something is already running on port 80" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# check if something is running on port 443
|
# check if something is running on port 443
|
||||||
if ss -tulnp | grep ':443 ' >/dev/null; then
|
if ss -tulnp | grep ':443 ' >/dev/null; then
|
||||||
echo "Error: something is already running on port 443" >&2
|
echo "Error: something is already running on port 443" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
command_exists() {
|
command_exists() {
|
||||||
command -v "$@" > /dev/null 2>&1
|
command -v "$@" > /dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
if command_exists docker; then
|
if command_exists docker; then
|
||||||
echo "Docker already installed"
|
echo "Docker already installed"
|
||||||
else
|
|
||||||
curl -sSL https://get.docker.com | sh
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker swarm leave --force 2>/dev/null
|
|
||||||
|
|
||||||
get_ip() {
|
|
||||||
# Try to get IPv4
|
|
||||||
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
|
||||||
|
|
||||||
if [ -n "$ipv4" ]; then
|
|
||||||
echo "$ipv4"
|
|
||||||
else
|
else
|
||||||
# Try to get IPv6
|
curl -sSL https://get.docker.com | sh
|
||||||
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
fi
|
||||||
if [ -n "$ipv6" ]; then
|
|
||||||
echo "$ipv6"
|
docker swarm leave --force 2>/dev/null
|
||||||
|
|
||||||
|
get_ip() {
|
||||||
|
# Try to get IPv4
|
||||||
|
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$ipv4" ]; then
|
||||||
|
echo "$ipv4"
|
||||||
|
else
|
||||||
|
# Try to get IPv6
|
||||||
|
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
||||||
|
if [ -n "$ipv6" ]; then
|
||||||
|
echo "$ipv6"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
}
|
||||||
|
|
||||||
|
advertise_addr=$(get_ip)
|
||||||
|
|
||||||
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
|
echo "Swarm initialized"
|
||||||
|
|
||||||
|
docker network rm -f dokploy-network 2>/dev/null
|
||||||
|
docker network create --driver overlay --attachable dokploy-network
|
||||||
|
|
||||||
|
echo "Network created"
|
||||||
|
|
||||||
|
mkdir -p /etc/dokploy
|
||||||
|
|
||||||
|
chmod 777 /etc/dokploy
|
||||||
|
|
||||||
|
docker pull dokploy/dokploy:canary
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
docker service create \
|
||||||
|
--name dokploy \
|
||||||
|
--replicas 1 \
|
||||||
|
--network dokploy-network \
|
||||||
|
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||||
|
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||||
|
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
||||||
|
--publish published=3000,target=3000,mode=host \
|
||||||
|
--update-parallelism 1 \
|
||||||
|
--update-order stop-first \
|
||||||
|
--constraint 'node.role == manager' \
|
||||||
|
-e RELEASE_TAG=canary \
|
||||||
|
dokploy/dokploy:canary
|
||||||
|
|
||||||
|
GREEN="\033[0;32m"
|
||||||
|
YELLOW="\033[1;33m"
|
||||||
|
BLUE="\033[0;34m"
|
||||||
|
NC="\033[0m" # No Color
|
||||||
|
|
||||||
|
format_ip_for_url() {
|
||||||
|
local ip="$1"
|
||||||
|
if echo "$ip" | grep -q ':'; then
|
||||||
|
# IPv6
|
||||||
|
echo "[${ip}]"
|
||||||
|
else
|
||||||
|
# IPv4
|
||||||
|
echo "${ip}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted_addr=$(format_ip_for_url "$advertise_addr")
|
||||||
|
echo ""
|
||||||
|
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
||||||
|
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
||||||
|
printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
|
||||||
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
advertise_addr=$(get_ip)
|
update_dokploy() {
|
||||||
|
echo "Updating Dokploy..."
|
||||||
|
|
||||||
|
# Pull the latest canary image
|
||||||
|
docker pull dokploy/dokploy:canary
|
||||||
|
|
||||||
docker swarm init --advertise-addr $advertise_addr
|
# Update the service
|
||||||
|
docker service update --image dokploy/dokploy:canary dokploy
|
||||||
|
|
||||||
echo "Swarm initialized"
|
echo "Dokploy has been updated to the latest canary version."
|
||||||
|
|
||||||
docker network rm -f dokploy-network 2>/dev/null
|
|
||||||
docker network create --driver overlay --attachable dokploy-network
|
|
||||||
|
|
||||||
echo "Network created"
|
|
||||||
|
|
||||||
mkdir -p /etc/dokploy
|
|
||||||
|
|
||||||
chmod 777 /etc/dokploy
|
|
||||||
|
|
||||||
docker pull dokploy/dokploy:canary
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
docker service create \
|
|
||||||
--name dokploy \
|
|
||||||
--replicas 1 \
|
|
||||||
--network dokploy-network \
|
|
||||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
|
||||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
|
||||||
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
|
||||||
--publish published=3000,target=3000,mode=host \
|
|
||||||
--update-parallelism 1 \
|
|
||||||
--update-order stop-first \
|
|
||||||
--constraint 'node.role == manager' \
|
|
||||||
-e RELEASE_TAG=canary \
|
|
||||||
dokploy/dokploy:canary
|
|
||||||
|
|
||||||
GREEN="\033[0;32m"
|
|
||||||
YELLOW="\033[1;33m"
|
|
||||||
BLUE="\033[0;34m"
|
|
||||||
NC="\033[0m" # No Color
|
|
||||||
|
|
||||||
format_ip_for_url() {
|
|
||||||
local ip="$1"
|
|
||||||
if echo "$ip" | grep -q ':'; then
|
|
||||||
# IPv6
|
|
||||||
echo "[${ip}]"
|
|
||||||
else
|
|
||||||
# IPv4
|
|
||||||
echo "${ip}"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
formatted_addr=$(format_ip_for_url "$advertise_addr")
|
# Main script execution
|
||||||
echo ""
|
if [ "$1" = "update" ]; then
|
||||||
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
update_dokploy
|
||||||
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
else
|
||||||
printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
|
install_dokploy
|
||||||
echo ""
|
fi
|
||||||
|
|
||||||
@@ -1,97 +1,117 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
install_dokploy() {
|
||||||
if [ "$(id -u)" != "0" ]; then
|
if [ "$(id -u)" != "0" ]; then
|
||||||
echo "This script must be run as root" >&2
|
echo "This script must be run as root" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
|
||||||
|
|
||||||
# check if is Mac OS
|
|
||||||
if [ "$(uname)" = "Darwin" ]; then
|
|
||||||
echo "This script must be run on Linux" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# check if is running inside a container
|
|
||||||
if [ -f /.dockerenv ]; then
|
|
||||||
echo "This script must be run on Linux" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# check if something is running on port 80
|
|
||||||
if ss -tulnp | grep ':80 ' >/dev/null; then
|
|
||||||
echo "Error: something is already running on port 80" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# check if something is running on port 443
|
|
||||||
if ss -tulnp | grep ':443 ' >/dev/null; then
|
|
||||||
echo "Error: something is already running on port 443" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
command_exists() {
|
|
||||||
command -v "$@" > /dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
if command_exists docker; then
|
|
||||||
echo "Docker already installed"
|
|
||||||
else
|
|
||||||
curl -sSL https://get.docker.com | sh
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker swarm leave --force 2>/dev/null
|
|
||||||
advertise_addr=$(curl -s ifconfig.me)
|
|
||||||
|
|
||||||
docker swarm init --advertise-addr $advertise_addr
|
|
||||||
|
|
||||||
echo "Swarm initialized"
|
|
||||||
|
|
||||||
docker network rm -f dokploy-network 2>/dev/null
|
|
||||||
docker network create --driver overlay --attachable dokploy-network
|
|
||||||
|
|
||||||
echo "Network created"
|
|
||||||
|
|
||||||
mkdir -p /etc/dokploy
|
|
||||||
|
|
||||||
chmod 777 /etc/dokploy
|
|
||||||
|
|
||||||
docker pull dokploy/dokploy:feature
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
docker service create \
|
|
||||||
--name dokploy \
|
|
||||||
--replicas 1 \
|
|
||||||
--network dokploy-network \
|
|
||||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
|
||||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
|
||||||
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
|
||||||
--publish published=3000,target=3000,mode=host \
|
|
||||||
--update-parallelism 1 \
|
|
||||||
--update-order stop-first \
|
|
||||||
--constraint 'node.role == manager' \
|
|
||||||
-e RELEASE_TAG=feature \
|
|
||||||
dokploy/dokploy:feature
|
|
||||||
|
|
||||||
GREEN="\033[0;32m"
|
|
||||||
YELLOW="\033[1;33m"
|
|
||||||
BLUE="\033[0;34m"
|
|
||||||
NC="\033[0m" # No Color
|
|
||||||
|
|
||||||
format_ip_for_url() {
|
|
||||||
local ip="$1"
|
|
||||||
if echo "$ip" | grep -q ':'; then
|
|
||||||
# IPv6
|
|
||||||
echo "[${ip}]"
|
|
||||||
else
|
|
||||||
# IPv4
|
|
||||||
echo "${ip}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# check if is Mac OS
|
||||||
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
|
echo "This script must be run on Linux" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# check if is running inside a container
|
||||||
|
if [ -f /.dockerenv ]; then
|
||||||
|
echo "This script must be run on Linux" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check if something is running on port 80
|
||||||
|
if ss -tulnp | grep ':80 ' >/dev/null; then
|
||||||
|
echo "Error: something is already running on port 80" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check if something is running on port 443
|
||||||
|
if ss -tulnp | grep ':443 ' >/dev/null; then
|
||||||
|
echo "Error: something is already running on port 443" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
command_exists() {
|
||||||
|
command -v "$@" > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
if command_exists docker; then
|
||||||
|
echo "Docker already installed"
|
||||||
|
else
|
||||||
|
curl -sSL https://get.docker.com | sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker swarm leave --force 2>/dev/null
|
||||||
|
advertise_addr=$(curl -s ifconfig.me)
|
||||||
|
|
||||||
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
|
echo "Swarm initialized"
|
||||||
|
|
||||||
|
docker network rm -f dokploy-network 2>/dev/null
|
||||||
|
docker network create --driver overlay --attachable dokploy-network
|
||||||
|
|
||||||
|
echo "Network created"
|
||||||
|
|
||||||
|
mkdir -p /etc/dokploy
|
||||||
|
|
||||||
|
chmod 777 /etc/dokploy
|
||||||
|
|
||||||
|
docker pull dokploy/dokploy:feature
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
docker service create \
|
||||||
|
--name dokploy \
|
||||||
|
--replicas 1 \
|
||||||
|
--network dokploy-network \
|
||||||
|
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||||
|
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||||
|
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
||||||
|
--publish published=3000,target=3000,mode=host \
|
||||||
|
--update-parallelism 1 \
|
||||||
|
--update-order stop-first \
|
||||||
|
--constraint 'node.role == manager' \
|
||||||
|
-e RELEASE_TAG=feature \
|
||||||
|
dokploy/dokploy:feature
|
||||||
|
|
||||||
|
GREEN="\033[0;32m"
|
||||||
|
YELLOW="\033[1;33m"
|
||||||
|
BLUE="\033[0;34m"
|
||||||
|
NC="\033[0m" # No Color
|
||||||
|
|
||||||
|
format_ip_for_url() {
|
||||||
|
local ip="$1"
|
||||||
|
if echo "$ip" | grep -q ':'; then
|
||||||
|
# IPv6
|
||||||
|
echo "[${ip}]"
|
||||||
|
else
|
||||||
|
# IPv4
|
||||||
|
echo "${ip}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted_addr=$(format_ip_for_url "$advertise_addr")
|
||||||
|
echo ""
|
||||||
|
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
||||||
|
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
||||||
|
printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
|
||||||
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
formatted_addr=$(format_ip_for_url "$advertise_addr")
|
update_dokploy() {
|
||||||
echo ""
|
echo "Updating Dokploy..."
|
||||||
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
|
||||||
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
# Pull the latest feature image
|
||||||
printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
|
docker pull dokploy/dokploy:feature
|
||||||
echo ""
|
|
||||||
|
# Update the service
|
||||||
|
docker service update --image dokploy/dokploy:feature dokploy
|
||||||
|
|
||||||
|
echo "Dokploy has been updated to the latest feature version."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main script execution
|
||||||
|
if [ "$1" = "update" ]; then
|
||||||
|
update_dokploy
|
||||||
|
else
|
||||||
|
install_dokploy
|
||||||
|
fi
|
||||||
@@ -1,112 +1,130 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
install_dokploy() {
|
||||||
|
if [ "$(id -u)" != "0" ]; then
|
||||||
|
echo "This script must be run as root" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$(id -u)" != "0" ]; then
|
# check if is Mac OS
|
||||||
echo "This script must be run as root" >&2
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
exit 1
|
echo "This script must be run on Linux" >&2
|
||||||
fi
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# check if is Mac OS
|
# check if is running inside a container
|
||||||
if [ "$(uname)" = "Darwin" ]; then
|
if [ -f /.dockerenv ]; then
|
||||||
echo "This script must be run on Linux" >&2
|
echo "This script must be run on Linux" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# check if something is running on port 80
|
||||||
|
if ss -tulnp | grep ':80 ' >/dev/null; then
|
||||||
|
echo "Error: something is already running on port 80" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# check if is running inside a container
|
# check if something is running on port 443
|
||||||
if [ -f /.dockerenv ]; then
|
if ss -tulnp | grep ':443 ' >/dev/null; then
|
||||||
echo "This script must be run on Linux" >&2
|
echo "Error: something is already running on port 443" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# check if something is running on port 80
|
command_exists() {
|
||||||
if ss -tulnp | grep ':80 ' >/dev/null; then
|
command -v "$@" > /dev/null 2>&1
|
||||||
echo "Error: something is already running on port 80" >&2
|
}
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# check if something is running on port 443
|
if command_exists docker; then
|
||||||
if ss -tulnp | grep ':443 ' >/dev/null; then
|
echo "Docker already installed"
|
||||||
echo "Error: something is already running on port 443" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
command_exists() {
|
|
||||||
command -v "$@" > /dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
if command_exists docker; then
|
|
||||||
echo "Docker already installed"
|
|
||||||
else
|
|
||||||
curl -sSL https://get.docker.com | sh
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker swarm leave --force 2>/dev/null
|
|
||||||
|
|
||||||
get_ip() {
|
|
||||||
# Try to get IPv4
|
|
||||||
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
|
||||||
|
|
||||||
if [ -n "$ipv4" ]; then
|
|
||||||
echo "$ipv4"
|
|
||||||
else
|
else
|
||||||
# Try to get IPv6
|
curl -sSL https://get.docker.com | sh
|
||||||
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
fi
|
||||||
if [ -n "$ipv6" ]; then
|
|
||||||
echo "$ipv6"
|
docker swarm leave --force 2>/dev/null
|
||||||
|
|
||||||
|
get_ip() {
|
||||||
|
# Try to get IPv4
|
||||||
|
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$ipv4" ]; then
|
||||||
|
echo "$ipv4"
|
||||||
|
else
|
||||||
|
# Try to get IPv6
|
||||||
|
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
||||||
|
if [ -n "$ipv6" ]; then
|
||||||
|
echo "$ipv6"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
}
|
||||||
|
|
||||||
|
advertise_addr=$(get_ip)
|
||||||
|
|
||||||
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
|
echo "Swarm initialized"
|
||||||
|
|
||||||
|
docker network rm -f dokploy-network 2>/dev/null
|
||||||
|
docker network create --driver overlay --attachable dokploy-network
|
||||||
|
|
||||||
|
echo "Network created"
|
||||||
|
|
||||||
|
mkdir -p /etc/dokploy
|
||||||
|
|
||||||
|
chmod 777 /etc/dokploy
|
||||||
|
|
||||||
|
docker pull dokploy/dokploy:latest
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
docker service create \
|
||||||
|
--name dokploy \
|
||||||
|
--replicas 1 \
|
||||||
|
--network dokploy-network \
|
||||||
|
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||||
|
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
||||||
|
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
||||||
|
--publish published=3000,target=3000,mode=host \
|
||||||
|
--update-parallelism 1 \
|
||||||
|
--update-order stop-first \
|
||||||
|
--constraint 'node.role == manager' \
|
||||||
|
dokploy/dokploy:latest
|
||||||
|
|
||||||
|
GREEN="\033[0;32m"
|
||||||
|
YELLOW="\033[1;33m"
|
||||||
|
BLUE="\033[0;34m"
|
||||||
|
NC="\033[0m" # No Color
|
||||||
|
|
||||||
|
format_ip_for_url() {
|
||||||
|
local ip="$1"
|
||||||
|
if echo "$ip" | grep -q ':'; then
|
||||||
|
# IPv6
|
||||||
|
echo "[${ip}]"
|
||||||
|
else
|
||||||
|
# IPv4
|
||||||
|
echo "${ip}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted_addr=$(format_ip_for_url "$advertise_addr")
|
||||||
|
echo ""
|
||||||
|
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
||||||
|
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
||||||
|
printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
advertise_addr=$(get_ip)
|
update_dokploy() {
|
||||||
|
echo "Updating Dokploy..."
|
||||||
|
|
||||||
|
# Pull the latest image
|
||||||
|
docker pull dokploy/dokploy:latest
|
||||||
|
|
||||||
docker swarm init --advertise-addr $advertise_addr
|
# Update the service
|
||||||
|
docker service update --image dokploy/dokploy:latest dokploy
|
||||||
|
|
||||||
echo "Swarm initialized"
|
echo "Dokploy has been updated to the latest version."
|
||||||
|
|
||||||
docker network rm -f dokploy-network 2>/dev/null
|
|
||||||
docker network create --driver overlay --attachable dokploy-network
|
|
||||||
|
|
||||||
echo "Network created"
|
|
||||||
|
|
||||||
mkdir -p /etc/dokploy
|
|
||||||
|
|
||||||
chmod 777 /etc/dokploy
|
|
||||||
|
|
||||||
docker pull dokploy/dokploy:latest
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
docker service create \
|
|
||||||
--name dokploy \
|
|
||||||
--replicas 1 \
|
|
||||||
--network dokploy-network \
|
|
||||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
|
||||||
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
|
|
||||||
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
|
|
||||||
--publish published=3000,target=3000,mode=host \
|
|
||||||
--update-parallelism 1 \
|
|
||||||
--update-order stop-first \
|
|
||||||
--constraint 'node.role == manager' \
|
|
||||||
dokploy/dokploy:latest
|
|
||||||
|
|
||||||
GREEN="\033[0;32m"
|
|
||||||
YELLOW="\033[1;33m"
|
|
||||||
BLUE="\033[0;34m"
|
|
||||||
NC="\033[0m" # No Color
|
|
||||||
|
|
||||||
format_ip_for_url() {
|
|
||||||
local ip="$1"
|
|
||||||
if echo "$ip" | grep -q ':'; then
|
|
||||||
# IPv6
|
|
||||||
echo "[${ip}]"
|
|
||||||
else
|
|
||||||
# IPv4
|
|
||||||
echo "${ip}"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
formatted_addr=$(format_ip_for_url "$advertise_addr")
|
# Main script execution
|
||||||
echo ""
|
if [ "$1" = "update" ]; then
|
||||||
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
update_dokploy
|
||||||
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
else
|
||||||
printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
|
install_dokploy
|
||||||
echo ""
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user