Merge pull request #521 from Dokploy/canary

v0.9.4
This commit is contained in:
Mauricio Siu
2024-10-03 02:12:03 -06:00
committed by GitHub
27 changed files with 4451 additions and 424 deletions

View File

@@ -17,7 +17,7 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Input, NumberInput } from "@/components/ui/input";
import {
Select,
SelectContent,
@@ -125,28 +125,14 @@ export const UpdatePort = ({ portId }: Props) => {
<FormItem>
<FormLabel>Published Port</FormLabel>
<FormControl>
<Input
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);
}
}
}}
/>
<NumberInput placeholder="1-65535" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="targetPort"
@@ -154,22 +140,7 @@ export const UpdatePort = ({ portId }: Props) => {
<FormItem>
<FormLabel>Target Port</FormLabel>
<FormControl>
<Input
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);
}
}
}}
/>
<Input placeholder="1-65535" {...field} />
</FormControl>
<FormMessage />

View File

@@ -18,7 +18,7 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Input, NumberInput } from "@/components/ui/input";
import {
Select,
SelectContent,
@@ -228,13 +228,7 @@ export const AddDomain = ({
<FormItem>
<FormLabel>Container Port</FormLabel>
<FormControl>
<Input
placeholder={"3000"}
{...field}
onChange={(e) => {
field.onChange(Number.parseInt(e.target.value));
}}
/>
<NumberInput placeholder={"3000"} {...field} />
</FormControl>
<FormMessage />
</FormItem>

View File

@@ -18,7 +18,7 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Input, NumberInput } from "@/components/ui/input";
import {
Select,
SelectContent,
@@ -364,13 +364,7 @@ export const AddDomainCompose = ({
<FormItem>
<FormLabel>Container Port</FormLabel>
<FormControl>
<Input
placeholder={"3000"}
{...field}
onChange={(e) => {
field.onChange(Number.parseInt(e.target.value));
}}
/>
<NumberInput placeholder={"3000"} {...field} />
</FormControl>
<FormMessage />
</FormItem>

View File

@@ -48,6 +48,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
const { data, refetch } = api.mariadb.one.useQuery({ mariadbId });
const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
@@ -79,7 +80,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
const buildConnectionUrl = () => {
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());
@@ -90,7 +91,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
form,
data?.databaseName,
data?.databaseUser,
ip,
getIp,
]);
return (
<>

View File

@@ -48,7 +48,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
const { data, refetch } = api.mongo.one.useQuery({ mongoId });
const { mutateAsync, isLoading } = api.mongo.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
@@ -80,7 +80,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
const buildConnectionUrl = () => {
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());
@@ -90,7 +90,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
data?.databasePassword,
form,
data?.databaseUser,
ip,
getIp,
]);
return (

View File

@@ -48,7 +48,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
const { data, refetch } = api.mysql.one.useQuery({ mysqlId });
const { mutateAsync, isLoading } = api.mysql.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const form = useForm<DockerProvider>({
defaultValues: {},
resolver: zodResolver(DockerProviderSchema),
@@ -80,7 +80,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
const buildConnectionUrl = () => {
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());
@@ -91,7 +91,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
data?.databaseName,
data?.databaseUser,
form,
ip,
getIp,
]);
return (
<>

View File

@@ -48,6 +48,7 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
const { data, refetch } = api.postgres.one.useQuery({ postgresId });
const { mutateAsync, isLoading } =
api.postgres.saveExternalPort.useMutation();
const getIp = data?.server?.ipAddress || ip;
const [connectionUrl, setConnectionUrl] = useState("");
const form = useForm<DockerProvider>({
@@ -79,10 +80,9 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
useEffect(() => {
const buildConnectionUrl = () => {
const hostname = window.location.hostname;
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());
@@ -92,7 +92,7 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
data?.databasePassword,
form,
data?.databaseName,
ip,
getIp,
]);
return (

View File

@@ -39,7 +39,7 @@ import { slugify } from "@/lib/slug";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CircuitBoard, HelpCircle } from "lucide-react";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
@@ -71,6 +71,7 @@ interface Props {
export const AddCompose = ({ projectId, projectName }: Props) => {
const utils = api.useUtils();
const [visible, setVisible] = useState(false);
const slug = slugify(projectName);
const { data: servers } = api.server.withSSHKey.useQuery();
const { mutateAsync, isLoading, error, isError } =
@@ -101,6 +102,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
})
.then(async () => {
toast.success("Compose Created");
setVisible(false);
await utils.project.one.invalidate({
projectId,
});
@@ -111,7 +113,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
};
return (
<Dialog>
<Dialog open={visible} onOpenChange={setVisible}>
<DialogTrigger className="w-full">
<DropdownMenuItem
className="w-full cursor-pointer space-x-3"

View File

@@ -48,6 +48,7 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
const { data, refetch } = api.redis.one.useQuery({ redisId });
const { mutateAsync, isLoading } = api.redis.saveExternalPort.useMutation();
const [connectionUrl, setConnectionUrl] = useState("");
const getIp = data?.server?.ipAddress || ip;
const form = useForm<DockerProvider>({
defaultValues: {},
@@ -81,11 +82,11 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
const hostname = window.location.hostname;
const port = form.watch("externalPort") || data?.externalPort;
return `redis://default:${data?.databasePassword}@${ip}:${port}`;
return `redis://default:${data?.databasePassword}@${getIp}:${port}`;
};
setConnectionUrl(buildConnectionUrl());
}, [data?.appName, data?.externalPort, data?.databasePassword, form, ip]);
}, [data?.appName, data?.externalPort, data?.databasePassword, form, getIp]);
return (
<>
<div className="flex w-full flex-col gap-5 ">

View File

@@ -17,10 +17,18 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Container } from "lucide-react";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -36,10 +44,9 @@ const AddRegistrySchema = z.object({
password: z.string().min(1, {
message: "Password is required",
}),
registryUrl: z.string().min(1, {
message: "Registry URL is required",
}),
registryUrl: z.string(),
imagePrefix: z.string(),
serverId: z.string().optional(),
});
type AddRegistry = z.infer<typeof AddRegistrySchema>;
@@ -48,9 +55,9 @@ export const AddRegistry = () => {
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, error, isError } = api.registry.create.useMutation();
const { data: servers } = api.server.withSSHKey.useQuery();
const { mutateAsync: testRegistry, isLoading } =
api.registry.testRegistry.useMutation();
const router = useRouter();
const form = useForm<AddRegistry>({
defaultValues: {
username: "",
@@ -58,6 +65,7 @@ export const AddRegistry = () => {
registryUrl: "",
imagePrefix: "",
registryName: "",
serverId: "",
},
resolver: zodResolver(AddRegistrySchema),
});
@@ -67,6 +75,7 @@ export const AddRegistry = () => {
const registryUrl = form.watch("registryUrl");
const registryName = form.watch("registryName");
const imagePrefix = form.watch("imagePrefix");
const serverId = form.watch("serverId");
useEffect(() => {
form.reset({
@@ -74,6 +83,7 @@ export const AddRegistry = () => {
password: "",
registryUrl: "",
imagePrefix: "",
serverId: "",
});
}, [form, form.reset, form.formState.isSubmitSuccessful]);
@@ -85,6 +95,7 @@ export const AddRegistry = () => {
registryUrl: data.registryUrl,
registryType: "cloud",
imagePrefix: data.imagePrefix,
serverId: data.serverId,
})
.then(async (data) => {
await utils.registry.all.invalidate();
@@ -211,34 +222,77 @@ export const AddRegistry = () => {
)}
/>
</div>
<DialogFooter className="flex flex-row w-full sm:justify-between gap-4 flex-wrap">
<Button
type="button"
variant={"secondary"}
isLoading={isLoading}
onClick={async () => {
await testRegistry({
username: username,
password: password,
registryUrl: registryUrl,
registryName: registryName,
registryType: "cloud",
imagePrefix: imagePrefix,
})
.then((data) => {
if (data) {
toast.success("Registry Tested Successfully");
} else {
toast.error("Registry Test Failed");
}
<DialogFooter className="flex flex-col w-full sm:justify-between gap-4 flex-wrap sm:flex-col">
<div className="flex flex-col gap-4 border p-2 rounded-lg">
<span className="text-sm text-muted-foreground">
Select a server to test the registry. If you don't have a
server choose the default one.
</span>
<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
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(() => {
toast.error("Error to test the registry");
});
}}
>
Test Registry
</Button>
.then((data) => {
if (data) {
toast.success("Registry Tested Successfully");
} else {
toast.error("Registry Test Failed");
}
})
.catch(() => {
toast.error("Error to test the registry");
});
}}
>
Test Registry
</Button>
</div>
<Button isLoading={form.formState.isSubmitting} type="submit">
Create
</Button>

View File

@@ -17,6 +17,15 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -34,10 +43,9 @@ const updateRegistry = z.object({
message: "Username is required",
}),
password: z.string(),
registryUrl: z.string().min(1, {
message: "Registry URL is required",
}),
registryUrl: z.string(),
imagePrefix: z.string(),
serverId: z.string().optional(),
});
type UpdateRegistry = z.infer<typeof updateRegistry>;
@@ -48,6 +56,8 @@ interface Props {
export const UpdateDockerRegistry = ({ registryId }: Props) => {
const utils = api.useUtils();
const { data: servers } = api.server.withSSHKey.useQuery();
const { mutateAsync: testRegistry, isLoading } =
api.registry.testRegistry.useMutation();
const { data, refetch } = api.registry.one.useQuery(
@@ -69,15 +79,19 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
username: "",
password: "",
registryUrl: "",
serverId: "",
},
resolver: zodResolver(updateRegistry),
});
console.log(form.formState.errors);
const password = form.watch("password");
const username = form.watch("username");
const registryUrl = form.watch("registryUrl");
const registryName = form.watch("registryName");
const imagePrefix = form.watch("imagePrefix");
const serverId = form.watch("serverId");
useEffect(() => {
if (data) {
@@ -87,6 +101,7 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
username: data.username || "",
password: "",
registryUrl: data.registryUrl || "",
serverId: "",
});
}
}, [form, form.reset, data]);
@@ -99,6 +114,7 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
username: data.username,
registryUrl: data.registryUrl,
imagePrefix: data.imagePrefix,
serverId: data.serverId,
})
.then(async (data) => {
toast.success("Registry Updated");
@@ -224,13 +240,47 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
</div>
</form>
<DialogFooter
className={cn(
isCloud ? "sm:justify-between " : "",
"flex flex-row w-full gap-4 flex-wrap",
)}
>
{isCloud && (
<DialogFooter className="flex flex-col w-full sm:justify-between gap-4 flex-wrap sm:flex-col">
<div className="flex flex-col gap-4 border p-2 rounded-lg">
<span className="text-sm text-muted-foreground">
Select a server to test the registry. If you don't have a server
choose the default one.
</span>
<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
type="button"
variant={"secondary"}
@@ -243,6 +293,7 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
registryName: registryName,
registryType: "cloud",
imagePrefix: imagePrefix,
serverId: serverId,
})
.then((data) => {
if (data) {
@@ -258,12 +309,12 @@ export const UpdateDockerRegistry = ({ registryId }: Props) => {
>
Test Registry
</Button>
)}
</div>
<Button
isLoading={form.formState.isSubmitting}
form="hook-form"
type="submit"
form="hook-form"
>
Update
</Button>

View File

@@ -31,4 +31,39 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
);
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 };

View File

@@ -0,0 +1 @@
ALTER TABLE "registry" ALTER COLUMN "registryUrl" SET DEFAULT '';

File diff suppressed because it is too large Load Diff

View File

@@ -267,6 +267,13 @@
"when": 1726988289562,
"tag": "0037_legal_namor",
"breakpoints": true
},
{
"idx": 38,
"version": "6",
"when": 1727942090102,
"tag": "0038_rapid_landau",
"breakpoints": true
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.9.3",
"version": "v0.9.4",
"private": true,
"license": "Apache-2.0",
"type": "module",

View File

@@ -68,7 +68,7 @@ export const destinationRouter = createTRPCRouter({
const destination = await findDestinationById(input.destinationId);
return destination;
}),
all: adminProcedure.query(async () => {
all: protectedProcedure.query(async () => {
return await db.query.destinations.findMany({});
}),
remove: adminProcedure

View File

@@ -7,7 +7,7 @@ import {
apiUpdateRegistry,
} from "@/server/db/schema";
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 { TRPCError } from "@trpc/server";
import {
@@ -58,7 +58,13 @@ export const registryRouter = createTRPCRouter({
.mutation(async ({ input }) => {
try {
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;
} catch (error) {
console.log("Error Registry:", error);
@@ -78,6 +84,7 @@ export const registryRouter = createTRPCRouter({
? input.registryUrl
: "dokploy-registry.docker.localhost",
imagePrefix: null,
serverId: undefined,
});
await manageRegistry(selfHostedRegistry);
@@ -86,3 +93,17 @@ export const registryRouter = createTRPCRouter({
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(" ");
};

View File

@@ -2,7 +2,7 @@ import { db } from "@/server/db";
import { type apiCreateRegistry, registry } from "@/server/db/schema";
import { initializeRegistry } from "@/server/setup/registry-setup";
import { removeService } from "@/server/utils/docker/utils";
import { execAsync } from "@/server/utils/process/execAsync";
import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync";
import {
manageRegistry,
removeSelfHostedRegistry,
@@ -32,9 +32,10 @@ export const createRegistry = async (input: typeof apiCreateRegistry._type) => {
message: "Error input: Inserting registry",
});
}
if (newRegistry.registryType === "cloud") {
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`;
if (input.serverId && input.serverId !== "none") {
await execAsyncRemote(input.serverId, loginCommand);
} else if (newRegistry.registryType === "cloud") {
await execAsync(loginCommand);
}
@@ -76,7 +77,7 @@ export const removeRegistry = async (registryId: string) => {
export const updateRegistry = async (
registryId: string,
registryData: Partial<Registry>,
registryData: Partial<Registry> & { serverId?: string | null },
) => {
try {
const response = await db
@@ -92,6 +93,13 @@ export const updateRegistry = async (
await manageRegistry(response);
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;
} catch (error) {

View File

@@ -23,7 +23,7 @@ export const registry = pgTable("registry", {
imagePrefix: text("imagePrefix"),
username: text("username").notNull(),
password: text("password").notNull(),
registryUrl: text("registryUrl").notNull(),
registryUrl: text("registryUrl").notNull().default(""),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
@@ -45,7 +45,7 @@ const createSchema = createInsertSchema(registry, {
registryName: z.string().min(1),
username: z.string().min(1),
password: z.string().min(1),
registryUrl: z.string().min(1),
registryUrl: z.string(),
adminId: z.string().min(1),
registryId: z.string().min(1),
registryType: z.enum(["selfHosted", "cloud"]),
@@ -62,7 +62,10 @@ export const apiCreateRegistry = createSchema
registryType: z.enum(["selfHosted", "cloud"]),
imagePrefix: z.string().nullable().optional(),
})
.required();
.required()
.extend({
serverId: z.string().optional(),
});
export const apiTestRegistry = createSchema.pick({}).extend({
registryName: z.string().min(1),
@@ -71,6 +74,7 @@ export const apiTestRegistry = createSchema.pick({}).extend({
registryUrl: z.string(),
registryType: z.enum(["selfHosted", "cloud"]),
imagePrefix: z.string().nullable().optional(),
serverId: z.string().optional(),
});
export const apiRemoveRegistry = createSchema
@@ -87,6 +91,7 @@ export const apiFindOneRegistry = createSchema
export const apiUpdateRegistry = createSchema.partial().extend({
registryId: z.string().min(1),
serverId: z.string().optional(),
});
export const apiEnableSelfHostedRegistry = createSchema

View File

@@ -147,15 +147,15 @@ export const cloneRawBitbucketRepositoryRemote = async (compose: Compose) => {
const bitbucketProvider = await findBitbucketById(bitbucketId);
const basePath = COMPOSE_PATH;
const outputPath = join(basePath, appName, "code");
await recreateDirectory(outputPath);
const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
try {
await execAsyncRemote(
serverId,
`git clone --branch ${bitbucketBranch} --depth 1 ${cloneUrl} ${outputPath}`,
);
const command = `
rm -rf ${outputPath};
git clone --branch ${bitbucketBranch} --depth 1 ${cloneUrl} ${outputPath}
`;
await execAsyncRemote(serverId, command);
} catch (error) {
throw error;
}

View File

@@ -271,13 +271,13 @@ export const cloneRawGithubRepositoryRemote = async (compose: Compose) => {
const octokit = authGithub(githubProvider);
const token = await getGithubToken(octokit);
const repoclone = `github.com/${owner}/${repository}.git`;
await recreateDirectory(outputPath);
const cloneUrl = `https://oauth2:${token}@${repoclone}`;
try {
await execAsyncRemote(
serverId,
`git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}`,
);
const command = `
rm -rf ${outputPath};
git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}
`;
await execAsyncRemote(serverId, command);
} catch (error) {
throw error;
}

View File

@@ -390,14 +390,14 @@ export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
await refreshGitlabToken(gitlabId);
const basePath = COMPOSE_PATH;
const outputPath = join(basePath, appName, "code");
await recreateDirectory(outputPath);
const repoclone = `gitlab.com/${gitlabPathNamespace}.git`;
const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`;
try {
await execAsyncRemote(
serverId,
`git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}`,
);
const command = `
rm -rf ${outputPath};
git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}
`;
await execAsyncRemote(serverId, command);
} catch (error) {
throw error;
}

View File

@@ -70,6 +70,7 @@ export const createComposeFileRawRemote = async (compose: Compose) => {
try {
const encodedContent = encodeBase64(composeFile);
const command = `
rm -rf ${outputPath};
mkdir -p ${outputPath};
echo "${encodedContent}" | base64 -d > "${filePath}";
`;

View File

@@ -1,114 +1,133 @@
#!/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
echo "This script must be run as root" >&2
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 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 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 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
# 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
}
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"
if command_exists docker; then
echo "Docker already installed"
else
# Try to get IPv6
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
if [ -n "$ipv6" ]; then
echo "$ipv6"
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
# Try to get IPv6
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
if [ -n "$ipv6" ]; then
echo "$ipv6"
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"
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
echo "Dokploy has been updated to the latest canary version."
}
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 ""
# Main script execution
if [ "$1" = "update" ]; then
update_dokploy
else
install_dokploy
fi

View File

@@ -1,97 +1,117 @@
#!/bin/bash
if [ "$(id -u)" != "0" ]; then
echo "This script must be run as root" >&2
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}"
install_dokploy() {
if [ "$(id -u)" != "0" ]; then
echo "This script must be run as root" >&2
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
}
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")
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 ""
update_dokploy() {
echo "Updating Dokploy..."
# Pull the latest feature image
docker pull dokploy/dokploy:feature
# 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

View File

@@ -1,112 +1,130 @@
#!/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
echo "This script must be run as root" >&2
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 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 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 443
if ss -tulnp | grep ':443 ' >/dev/null; then
echo "Error: something is already running on port 443" >&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
command_exists() {
command -v "$@" > /dev/null 2>&1
}
# 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
get_ip() {
# Try to get IPv4
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
if [ -n "$ipv4" ]; then
echo "$ipv4"
if command_exists docker; then
echo "Docker already installed"
else
# Try to get IPv6
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
if [ -n "$ipv6" ]; then
echo "$ipv6"
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
# Try to get IPv6
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
if [ -n "$ipv6" ]; then
echo "$ipv6"
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"
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
echo "Dokploy has been updated to the latest version."
}
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 ""
# Main script execution
if [ "$1" = "update" ]; then
update_dokploy
else
install_dokploy
fi