mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-11 06:25:17 +00:00
Merge pull request #3138 from Dokploy/711-custom-build-server
711 custom build server
This commit is contained in:
@@ -30,6 +30,9 @@ const baseApp: ApplicationNested = {
|
|||||||
previewLabels: [],
|
previewLabels: [],
|
||||||
herokuVersion: "",
|
herokuVersion: "",
|
||||||
giteaBranch: "",
|
giteaBranch: "",
|
||||||
|
buildServerId: "",
|
||||||
|
buildRegistryId: "",
|
||||||
|
buildRegistry: null,
|
||||||
giteaBuildPath: "",
|
giteaBuildPath: "",
|
||||||
previewRequireCollaboratorPermissions: false,
|
previewRequireCollaboratorPermissions: false,
|
||||||
giteaId: "",
|
giteaId: "",
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ const baseApp: ApplicationNested = {
|
|||||||
giteaRepository: "",
|
giteaRepository: "",
|
||||||
giteaOwner: "",
|
giteaOwner: "",
|
||||||
giteaBranch: "",
|
giteaBranch: "",
|
||||||
|
buildServerId: "",
|
||||||
|
buildRegistryId: "",
|
||||||
|
buildRegistry: null,
|
||||||
giteaBuildPath: "",
|
giteaBuildPath: "",
|
||||||
giteaId: "",
|
giteaId: "",
|
||||||
cleanCache: false,
|
cleanCache: false,
|
||||||
|
|||||||
@@ -0,0 +1,248 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Server } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
applicationId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
buildServerId: z.string().min(1, "Build server is required"),
|
||||||
|
buildRegistryId: z.string().min(1, "Build registry is required"),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Schema = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
export const ShowBuildServer = ({ applicationId }: Props) => {
|
||||||
|
const { data, refetch } = api.application.one.useQuery(
|
||||||
|
{ applicationId },
|
||||||
|
{ enabled: !!applicationId },
|
||||||
|
);
|
||||||
|
const { data: buildServers } = api.server.buildServers.useQuery();
|
||||||
|
const { data: registries } = api.registry.all.useQuery();
|
||||||
|
|
||||||
|
const { mutateAsync, isLoading } = api.application.update.useMutation();
|
||||||
|
|
||||||
|
const form = useForm<Schema>({
|
||||||
|
defaultValues: {
|
||||||
|
buildServerId: data?.buildServerId || "",
|
||||||
|
buildRegistryId: data?.buildRegistryId || "",
|
||||||
|
},
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
form.reset({
|
||||||
|
buildServerId: data?.buildServerId || "",
|
||||||
|
buildRegistryId: data?.buildRegistryId || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [form, form.reset, data]);
|
||||||
|
|
||||||
|
const onSubmit = async (formData: Schema) => {
|
||||||
|
await mutateAsync({
|
||||||
|
applicationId,
|
||||||
|
buildServerId:
|
||||||
|
formData?.buildServerId === "none" || !formData?.buildServerId
|
||||||
|
? null
|
||||||
|
: formData?.buildServerId,
|
||||||
|
buildRegistryId:
|
||||||
|
formData?.buildRegistryId === "none" || !formData?.buildRegistryId
|
||||||
|
? null
|
||||||
|
: formData?.buildRegistryId,
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
toast.success("Build Server Settings Updated");
|
||||||
|
await refetch();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error updating build server settings");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="bg-background">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex flex-row items-center gap-2">
|
||||||
|
<Server className="size-6 text-muted-foreground" />
|
||||||
|
<div>
|
||||||
|
<CardTitle className="text-xl">Build Server</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Configure a dedicated server for building your application.
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
<AlertBlock type="info">
|
||||||
|
Build servers offload the build process from your deployment servers.
|
||||||
|
Select a build server and registry to use for building your
|
||||||
|
application.
|
||||||
|
</AlertBlock>
|
||||||
|
|
||||||
|
<AlertBlock type="info">
|
||||||
|
📊 <strong>Important:</strong> Once the build finishes, you'll need to
|
||||||
|
wait a few seconds for the deployment server to download the image.
|
||||||
|
These download logs will <strong>NOT</strong> appear in the build
|
||||||
|
deployment logs. Check the <strong>Logs</strong> tab to see when the
|
||||||
|
container starts running.
|
||||||
|
</AlertBlock>
|
||||||
|
|
||||||
|
{!registries || registries.length === 0 ? (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
You need to add at least one registry to use build servers. Please
|
||||||
|
go to{" "}
|
||||||
|
<Link
|
||||||
|
href="/dashboard/settings/registry"
|
||||||
|
className="text-primary underline"
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</Link>{" "}
|
||||||
|
to add a registry.
|
||||||
|
</AlertBlock>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="grid w-full gap-4"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="buildServerId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Build Server</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={field.value || "none"}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a build server" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="none">
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<span>None</span>
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
{buildServers?.map((server) => (
|
||||||
|
<SelectItem
|
||||||
|
key={server.serverId}
|
||||||
|
value={server.serverId}
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
|
<span>{server.name}</span>
|
||||||
|
<span className="text-muted-foreground text-xs">
|
||||||
|
{server.ipAddress}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectLabel>
|
||||||
|
Build Servers ({buildServers?.length || 0})
|
||||||
|
</SelectLabel>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>
|
||||||
|
Select a build server to handle the build process for this
|
||||||
|
application.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="buildRegistryId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Build Registry</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={field.value || "none"}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a registry" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="none">
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<span>None</span>
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
{registries?.map((registry) => (
|
||||||
|
<SelectItem
|
||||||
|
key={registry.registryId}
|
||||||
|
value={registry.registryId}
|
||||||
|
>
|
||||||
|
{registry.registryName}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectLabel>
|
||||||
|
Registries ({registries?.length || 0})
|
||||||
|
</SelectLabel>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>
|
||||||
|
Select a registry to store the built images from the build
|
||||||
|
server.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex w-full justify-end">
|
||||||
|
<Button isLoading={isLoading} type="submit">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -407,7 +407,7 @@ export const ShowDeployments = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ShowDeployment
|
<ShowDeployment
|
||||||
serverId={serverId}
|
serverId={activeLog?.buildServerId || serverId}
|
||||||
open={Boolean(activeLog && activeLog.logPath !== null)}
|
open={Boolean(activeLog && activeLog.logPath !== null)}
|
||||||
onClose={() => setActiveLog(null)}
|
onClose={() => setActiveLog(null)}
|
||||||
logPath={activeLog?.logPath || ""}
|
logPath={activeLog?.logPath || ""}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const Schema = z.object({
|
|||||||
sshKeyId: z.string().min(1, {
|
sshKeyId: z.string().min(1, {
|
||||||
message: "SSH Key is required",
|
message: "SSH Key is required",
|
||||||
}),
|
}),
|
||||||
|
serverType: z.enum(["deploy", "build"]).default("deploy"),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Schema = z.infer<typeof Schema>;
|
type Schema = z.infer<typeof Schema>;
|
||||||
@@ -89,6 +90,7 @@ export const HandleServers = ({ serverId }: Props) => {
|
|||||||
port: 22,
|
port: 22,
|
||||||
username: "root",
|
username: "root",
|
||||||
sshKeyId: "",
|
sshKeyId: "",
|
||||||
|
serverType: "deploy",
|
||||||
},
|
},
|
||||||
resolver: zodResolver(Schema),
|
resolver: zodResolver(Schema),
|
||||||
});
|
});
|
||||||
@@ -101,6 +103,7 @@ export const HandleServers = ({ serverId }: Props) => {
|
|||||||
port: data?.port || 22,
|
port: data?.port || 22,
|
||||||
username: data?.username || "root",
|
username: data?.username || "root",
|
||||||
sshKeyId: data?.sshKeyId || "",
|
sshKeyId: data?.sshKeyId || "",
|
||||||
|
serverType: data?.serverType || "deploy",
|
||||||
});
|
});
|
||||||
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
|
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
|
||||||
|
|
||||||
@@ -116,6 +119,7 @@ export const HandleServers = ({ serverId }: Props) => {
|
|||||||
port: data.port || 22,
|
port: data.port || 22,
|
||||||
username: data.username || "root",
|
username: data.username || "root",
|
||||||
sshKeyId: data.sshKeyId || "",
|
sshKeyId: data.sshKeyId || "",
|
||||||
|
serverType: data.serverType || "deploy",
|
||||||
serverId: serverId || "",
|
serverId: serverId || "",
|
||||||
})
|
})
|
||||||
.then(async (_data) => {
|
.then(async (_data) => {
|
||||||
@@ -266,6 +270,50 @@ export const HandleServers = ({ serverId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="serverType"
|
||||||
|
render={({ field }) => {
|
||||||
|
const serverTypeValue = form.watch("serverType");
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Server Type</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a server type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="deploy">Deploy Server</SelectItem>
|
||||||
|
<SelectItem value="build">Build Server</SelectItem>
|
||||||
|
<SelectLabel>Server Type</SelectLabel>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
{serverTypeValue === "deploy" && (
|
||||||
|
<AlertBlock type="info" className="mt-2">
|
||||||
|
Deploy servers are used to run your applications,
|
||||||
|
databases, and services. They handle the deployment and
|
||||||
|
execution of your projects.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
|
{serverTypeValue === "build" && (
|
||||||
|
<AlertBlock type="info" className="mt-2">
|
||||||
|
Build servers are dedicated to building your
|
||||||
|
applications. They handle the compilation and build
|
||||||
|
process, offloading this work from your deployment
|
||||||
|
servers. Build servers won't appear in deployment
|
||||||
|
options.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="sshKeyId"
|
name="sshKeyId"
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export const SetupServer = ({ serverId }: Props) => {
|
|||||||
|
|
||||||
const [activeLog, setActiveLog] = useState<string | null>(null);
|
const [activeLog, setActiveLog] = useState<string | null>(null);
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
const isBuildServer = server?.serverType === "build";
|
||||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
|
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
|
||||||
const [isDeploying, setIsDeploying] = useState(false);
|
const [isDeploying, setIsDeploying] = useState(false);
|
||||||
@@ -117,17 +118,26 @@ export const SetupServer = ({ serverId }: Props) => {
|
|||||||
<TabsList
|
<TabsList
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid w-[700px]",
|
"grid w-[700px]",
|
||||||
isCloud ? "grid-cols-6" : "grid-cols-5",
|
isBuildServer
|
||||||
|
? "grid-cols-3"
|
||||||
|
: isCloud
|
||||||
|
? "grid-cols-6"
|
||||||
|
: "grid-cols-5",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<TabsTrigger value="ssh-keys">SSH Keys</TabsTrigger>
|
<TabsTrigger value="ssh-keys">SSH Keys</TabsTrigger>
|
||||||
<TabsTrigger value="deployments">Deployments</TabsTrigger>
|
<TabsTrigger value="deployments">Deployments</TabsTrigger>
|
||||||
<TabsTrigger value="validate">Validate</TabsTrigger>
|
<TabsTrigger value="validate">Validate</TabsTrigger>
|
||||||
<TabsTrigger value="audit">Security</TabsTrigger>
|
|
||||||
{isCloud && (
|
{!isBuildServer && (
|
||||||
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
|
<>
|
||||||
|
<TabsTrigger value="audit">Security</TabsTrigger>
|
||||||
|
{isCloud && (
|
||||||
|
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
|
||||||
|
)}
|
||||||
|
<TabsTrigger value="gpu-setup">GPU Setup</TabsTrigger>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<TabsTrigger value="gpu-setup">GPU Setup</TabsTrigger>
|
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent
|
<TabsContent
|
||||||
value="ssh-keys"
|
value="ssh-keys"
|
||||||
@@ -311,32 +321,36 @@ export const SetupServer = ({ serverId }: Props) => {
|
|||||||
<ValidateServer serverId={serverId} />
|
<ValidateServer serverId={serverId} />
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent
|
{!isBuildServer && (
|
||||||
value="audit"
|
<>
|
||||||
className="outline-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
<TabsContent
|
||||||
>
|
value="audit"
|
||||||
<div className="flex flex-col gap-2 text-sm text-muted-foreground pt-3">
|
className="outline-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||||
<SecurityAudit serverId={serverId} />
|
>
|
||||||
</div>
|
<div className="flex flex-col gap-2 text-sm text-muted-foreground pt-3">
|
||||||
</TabsContent>
|
<SecurityAudit serverId={serverId} />
|
||||||
<TabsContent
|
</div>
|
||||||
value="monitoring"
|
</TabsContent>
|
||||||
className="outline-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
<TabsContent
|
||||||
>
|
value="monitoring"
|
||||||
<div className="flex flex-col gap-2 text-sm pt-3">
|
className="outline-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||||
<div className="rounded-xl bg-background shadow-md border">
|
>
|
||||||
<SetupMonitoring serverId={serverId} />
|
<div className="flex flex-col gap-2 text-sm pt-3">
|
||||||
</div>
|
<div className="rounded-xl bg-background shadow-md border">
|
||||||
</div>
|
<SetupMonitoring serverId={serverId} />
|
||||||
</TabsContent>
|
</div>
|
||||||
<TabsContent
|
</div>
|
||||||
value="gpu-setup"
|
</TabsContent>
|
||||||
className="outline-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
<TabsContent
|
||||||
>
|
value="gpu-setup"
|
||||||
<div className="flex flex-col gap-2 text-sm text-muted-foreground pt-3">
|
className="outline-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||||
<GPUSupport serverId={serverId} />
|
>
|
||||||
</div>
|
<div className="flex flex-col gap-2 text-sm text-muted-foreground pt-3">
|
||||||
</TabsContent>
|
<GPUSupport serverId={serverId} />
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -129,6 +129,9 @@ export const ShowServers = () => {
|
|||||||
Status
|
Status
|
||||||
</TableHead>
|
</TableHead>
|
||||||
)}
|
)}
|
||||||
|
<TableHead className="text-center">
|
||||||
|
Type
|
||||||
|
</TableHead>
|
||||||
<TableHead className="text-center">
|
<TableHead className="text-center">
|
||||||
IP Address
|
IP Address
|
||||||
</TableHead>
|
</TableHead>
|
||||||
@@ -153,6 +156,8 @@ export const ShowServers = () => {
|
|||||||
{data?.map((server) => {
|
{data?.map((server) => {
|
||||||
const canDelete = server.totalSum === 0;
|
const canDelete = server.totalSum === 0;
|
||||||
const isActive = server.serverStatus === "active";
|
const isActive = server.serverStatus === "active";
|
||||||
|
const isBuildServer =
|
||||||
|
server.serverType === "build";
|
||||||
return (
|
return (
|
||||||
<TableRow key={server.serverId}>
|
<TableRow key={server.serverId}>
|
||||||
<TableCell className="text-left">
|
<TableCell className="text-left">
|
||||||
@@ -171,6 +176,15 @@ export const ShowServers = () => {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
)}
|
)}
|
||||||
|
<TableCell className="text-center">
|
||||||
|
<Badge
|
||||||
|
variant={
|
||||||
|
isBuildServer ? "secondary" : "default"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{server.serverType}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
<Badge>{server.ipAddress}</Badge>
|
<Badge>{server.ipAddress}</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -233,11 +247,12 @@ export const ShowServers = () => {
|
|||||||
serverId={server.serverId}
|
serverId={server.serverId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{server.sshKeyId && (
|
{server.sshKeyId &&
|
||||||
<ShowServerActions
|
!isBuildServer && (
|
||||||
serverId={server.serverId}
|
<ShowServerActions
|
||||||
/>
|
serverId={server.serverId}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -286,41 +301,43 @@ export const ShowServers = () => {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogAction>
|
</DialogAction>
|
||||||
|
|
||||||
{isActive && server.sshKeyId && (
|
{isActive &&
|
||||||
<>
|
server.sshKeyId &&
|
||||||
<DropdownMenuSeparator />
|
!isBuildServer && (
|
||||||
<DropdownMenuLabel>
|
<>
|
||||||
Extra
|
<DropdownMenuSeparator />
|
||||||
</DropdownMenuLabel>
|
<DropdownMenuLabel>
|
||||||
|
Extra
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
|
||||||
<ShowTraefikFileSystemModal
|
<ShowTraefikFileSystemModal
|
||||||
serverId={server.serverId}
|
serverId={server.serverId}
|
||||||
/>
|
|
||||||
<ShowDockerContainersModal
|
|
||||||
serverId={server.serverId}
|
|
||||||
/>
|
|
||||||
{isCloud && (
|
|
||||||
<ShowMonitoringModal
|
|
||||||
url={`http://${server.ipAddress}:${server?.metricsConfig?.server?.port}/metrics`}
|
|
||||||
token={
|
|
||||||
server?.metricsConfig?.server
|
|
||||||
?.token
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<ShowDockerContainersModal
|
||||||
|
serverId={server.serverId}
|
||||||
|
/>
|
||||||
|
{isCloud && (
|
||||||
|
<ShowMonitoringModal
|
||||||
|
url={`http://${server.ipAddress}:${server?.metricsConfig?.server?.port}/metrics`}
|
||||||
|
token={
|
||||||
|
server?.metricsConfig
|
||||||
|
?.server?.token
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<ShowSwarmOverviewModal
|
<ShowSwarmOverviewModal
|
||||||
serverId={server.serverId}
|
serverId={server.serverId}
|
||||||
/>
|
/>
|
||||||
<ShowNodesModal
|
<ShowNodesModal
|
||||||
serverId={server.serverId}
|
serverId={server.serverId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ShowSchedulesModal
|
<ShowSchedulesModal
|
||||||
serverId={server.serverId}
|
serverId={server.serverId}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -25,6 +25,13 @@ export const ValidateServer = ({ serverId }: Props) => {
|
|||||||
enabled: !!serverId,
|
enabled: !!serverId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
const { data: server } = api.server.one.useQuery(
|
||||||
|
{ serverId },
|
||||||
|
{
|
||||||
|
enabled: !!serverId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const isBuildServer = server?.serverType === "build";
|
||||||
const _utils = api.useUtils();
|
const _utils = api.useUtils();
|
||||||
return (
|
return (
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
@@ -73,7 +80,9 @@ export const ValidateServer = ({ serverId }: Props) => {
|
|||||||
<div className="border rounded-lg p-4">
|
<div className="border rounded-lg p-4">
|
||||||
<h3 className="text-lg font-semibold mb-1">Status</h3>
|
<h3 className="text-lg font-semibold mb-1">Status</h3>
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
Shows the server configuration status
|
{isBuildServer
|
||||||
|
? "Shows the build server configuration status"
|
||||||
|
: "Shows the server configuration status"}
|
||||||
</p>
|
</p>
|
||||||
<div className="grid gap-2.5">
|
<div className="grid gap-2.5">
|
||||||
<StatusRow
|
<StatusRow
|
||||||
@@ -85,15 +94,17 @@ export const ValidateServer = ({ serverId }: Props) => {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatusRow
|
{!isBuildServer && (
|
||||||
label="RClone Installed"
|
<StatusRow
|
||||||
isEnabled={data?.rclone?.enabled}
|
label="RClone Installed"
|
||||||
description={
|
isEnabled={data?.rclone?.enabled}
|
||||||
data?.rclone?.enabled
|
description={
|
||||||
? `Installed: ${data?.rclone?.version}`
|
data?.rclone?.enabled
|
||||||
: undefined
|
? `Installed: ${data?.rclone?.version}`
|
||||||
}
|
: undefined
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<StatusRow
|
<StatusRow
|
||||||
label="Nixpacks Installed"
|
label="Nixpacks Installed"
|
||||||
isEnabled={data?.nixpacks?.enabled}
|
isEnabled={data?.nixpacks?.enabled}
|
||||||
@@ -113,23 +124,36 @@ export const ValidateServer = ({ serverId }: Props) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatusRow
|
<StatusRow
|
||||||
label="Docker Swarm Initialized"
|
label="Railpack Installed"
|
||||||
isEnabled={data?.isSwarmInstalled}
|
isEnabled={data?.railpack?.enabled}
|
||||||
description={
|
description={
|
||||||
data?.isSwarmInstalled
|
data?.railpack?.enabled
|
||||||
? "Initialized"
|
? `Installed: ${data?.railpack?.version}`
|
||||||
: "Not Initialized"
|
: undefined
|
||||||
}
|
|
||||||
/>
|
|
||||||
<StatusRow
|
|
||||||
label="Dokploy Network Created"
|
|
||||||
isEnabled={data?.isDokployNetworkInstalled}
|
|
||||||
description={
|
|
||||||
data?.isDokployNetworkInstalled
|
|
||||||
? "Created"
|
|
||||||
: "Not Created"
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{!isBuildServer && (
|
||||||
|
<>
|
||||||
|
<StatusRow
|
||||||
|
label="Docker Swarm Initialized"
|
||||||
|
isEnabled={data?.isSwarmInstalled}
|
||||||
|
description={
|
||||||
|
data?.isSwarmInstalled
|
||||||
|
? "Initialized"
|
||||||
|
: "Not Initialized"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StatusRow
|
||||||
|
label="Dokploy Network Created"
|
||||||
|
isEnabled={data?.isDokployNetworkInstalled}
|
||||||
|
description={
|
||||||
|
data?.isDokployNetworkInstalled
|
||||||
|
? "Created"
|
||||||
|
: "Not Created"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<StatusRow
|
<StatusRow
|
||||||
label="Main Directory Created"
|
label="Main Directory Created"
|
||||||
isEnabled={data?.isMainDirectoryInstalled}
|
isEnabled={data?.isMainDirectoryInstalled}
|
||||||
@@ -139,15 +163,6 @@ export const ValidateServer = ({ serverId }: Props) => {
|
|||||||
: "Not Created"
|
: "Not Created"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StatusRow
|
|
||||||
label="Railpack Installed"
|
|
||||||
isEnabled={data?.railpack?.enabled}
|
|
||||||
description={
|
|
||||||
data?.railpack?.enabled
|
|
||||||
? `Installed: ${data?.railpack?.version}`
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ export const CreateServer = ({ stepper }: Props) => {
|
|||||||
port: data.port || 22,
|
port: data.port || 22,
|
||||||
username: data.username || "root",
|
username: data.username || "root",
|
||||||
sshKeyId: data.sshKeyId || "",
|
sshKeyId: data.sshKeyId || "",
|
||||||
|
serverType: "deploy",
|
||||||
})
|
})
|
||||||
.then(async (_data) => {
|
.then(async (_data) => {
|
||||||
toast.success("Server Created");
|
toast.success("Server Created");
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TYPE "public"."serverType" AS ENUM('deploy', 'build');--> statement-breakpoint
|
||||||
|
ALTER TABLE "application" ADD COLUMN "buildServerId" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "application" ADD COLUMN "buildRegistryId" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "deployment" ADD COLUMN "buildServerId" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "server" ADD COLUMN "serverType" "serverType" DEFAULT 'deploy' NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "application" ADD CONSTRAINT "application_buildServerId_server_serverId_fk" FOREIGN KEY ("buildServerId") REFERENCES "public"."server"("serverId") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "application" ADD CONSTRAINT "application_buildRegistryId_registry_registryId_fk" FOREIGN KEY ("buildRegistryId") REFERENCES "public"."registry"("registryId") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "deployment" ADD CONSTRAINT "deployment_buildServerId_server_serverId_fk" FOREIGN KEY ("buildServerId") REFERENCES "public"."server"("serverId") ON DELETE cascade ON UPDATE no action;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -855,6 +855,13 @@
|
|||||||
"when": 1763755037033,
|
"when": 1763755037033,
|
||||||
"tag": "0121_rainy_cargill",
|
"tag": "0121_rainy_cargill",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 122,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1764479387555,
|
||||||
|
"tag": "0122_absent_frightful_four",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+2
-1
@@ -17,6 +17,7 @@ import { AddCommand } from "@/components/dashboard/application/advanced/general/
|
|||||||
import { ShowPorts } from "@/components/dashboard/application/advanced/ports/show-port";
|
import { ShowPorts } from "@/components/dashboard/application/advanced/ports/show-port";
|
||||||
import { ShowRedirects } from "@/components/dashboard/application/advanced/redirects/show-redirects";
|
import { ShowRedirects } from "@/components/dashboard/application/advanced/redirects/show-redirects";
|
||||||
import { ShowSecurity } from "@/components/dashboard/application/advanced/security/show-security";
|
import { ShowSecurity } from "@/components/dashboard/application/advanced/security/show-security";
|
||||||
|
import { ShowBuildServer } from "@/components/dashboard/application/advanced/show-build-server";
|
||||||
import { ShowResources } from "@/components/dashboard/application/advanced/show-resources";
|
import { ShowResources } from "@/components/dashboard/application/advanced/show-resources";
|
||||||
import { ShowTraefikConfig } from "@/components/dashboard/application/advanced/traefik/show-traefik-config";
|
import { ShowTraefikConfig } from "@/components/dashboard/application/advanced/traefik/show-traefik-config";
|
||||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||||
@@ -353,7 +354,7 @@ const Service = (
|
|||||||
id={applicationId}
|
id={applicationId}
|
||||||
type="application"
|
type="application"
|
||||||
/>
|
/>
|
||||||
|
<ShowBuildServer applicationId={applicationId} />
|
||||||
<ShowResources id={applicationId} type="application" />
|
<ShowResources id={applicationId} type="application" />
|
||||||
<ShowVolumes id={applicationId} type="application" />
|
<ShowVolumes id={applicationId} type="application" />
|
||||||
<ShowRedirects applicationId={applicationId} />
|
<ShowRedirects applicationId={applicationId} />
|
||||||
|
|||||||
@@ -81,8 +81,10 @@ export const serverRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
getDefaultCommand: protectedProcedure
|
getDefaultCommand: protectedProcedure
|
||||||
.input(apiFindOneServer)
|
.input(apiFindOneServer)
|
||||||
.query(async () => {
|
.query(async ({ input }) => {
|
||||||
return defaultCommand();
|
const server = await findServerById(input.serverId);
|
||||||
|
const isBuildServer = server.serverType === "build";
|
||||||
|
return defaultCommand(isBuildServer);
|
||||||
}),
|
}),
|
||||||
all: protectedProcedure.query(async ({ ctx }) => {
|
all: protectedProcedure.query(async ({ ctx }) => {
|
||||||
const result = await db
|
const result = await db
|
||||||
@@ -124,10 +126,30 @@ export const serverRouter = createTRPCRouter({
|
|||||||
isNotNull(server.sshKeyId),
|
isNotNull(server.sshKeyId),
|
||||||
eq(server.organizationId, ctx.session.activeOrganizationId),
|
eq(server.organizationId, ctx.session.activeOrganizationId),
|
||||||
eq(server.serverStatus, "active"),
|
eq(server.serverStatus, "active"),
|
||||||
|
eq(server.serverType, "deploy"),
|
||||||
)
|
)
|
||||||
: and(
|
: and(
|
||||||
isNotNull(server.sshKeyId),
|
isNotNull(server.sshKeyId),
|
||||||
eq(server.organizationId, ctx.session.activeOrganizationId),
|
eq(server.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
eq(server.serverType, "deploy"),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}),
|
||||||
|
buildServers: protectedProcedure.query(async ({ ctx }) => {
|
||||||
|
const result = await db.query.server.findMany({
|
||||||
|
orderBy: desc(server.createdAt),
|
||||||
|
where: IS_CLOUD
|
||||||
|
? and(
|
||||||
|
isNotNull(server.sshKeyId),
|
||||||
|
eq(server.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
eq(server.serverStatus, "active"),
|
||||||
|
eq(server.serverType, "build"),
|
||||||
|
)
|
||||||
|
: and(
|
||||||
|
isNotNull(server.sshKeyId),
|
||||||
|
eq(server.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
eq(server.serverType, "build"),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -204,6 +204,15 @@ export const applications = pgTable("application", {
|
|||||||
serverId: text("serverId").references(() => server.serverId, {
|
serverId: text("serverId").references(() => server.serverId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
|
buildServerId: text("buildServerId").references(() => server.serverId, {
|
||||||
|
onDelete: "set null",
|
||||||
|
}),
|
||||||
|
buildRegistryId: text("buildRegistryId").references(
|
||||||
|
() => registry.registryId,
|
||||||
|
{
|
||||||
|
onDelete: "set null",
|
||||||
|
},
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const applicationsRelations = relations(
|
export const applicationsRelations = relations(
|
||||||
@@ -226,6 +235,7 @@ export const applicationsRelations = relations(
|
|||||||
registry: one(registry, {
|
registry: one(registry, {
|
||||||
fields: [applications.registryId],
|
fields: [applications.registryId],
|
||||||
references: [registry.registryId],
|
references: [registry.registryId],
|
||||||
|
relationName: "applicationRegistry",
|
||||||
}),
|
}),
|
||||||
github: one(github, {
|
github: one(github, {
|
||||||
fields: [applications.githubId],
|
fields: [applications.githubId],
|
||||||
@@ -246,6 +256,17 @@ export const applicationsRelations = relations(
|
|||||||
server: one(server, {
|
server: one(server, {
|
||||||
fields: [applications.serverId],
|
fields: [applications.serverId],
|
||||||
references: [server.serverId],
|
references: [server.serverId],
|
||||||
|
relationName: "applicationServer",
|
||||||
|
}),
|
||||||
|
buildServer: one(server, {
|
||||||
|
fields: [applications.buildServerId],
|
||||||
|
references: [server.serverId],
|
||||||
|
relationName: "applicationBuildServer",
|
||||||
|
}),
|
||||||
|
buildRegistry: one(registry, {
|
||||||
|
fields: [applications.buildRegistryId],
|
||||||
|
references: [registry.registryId],
|
||||||
|
relationName: "applicationBuildRegistry",
|
||||||
}),
|
}),
|
||||||
previewDeployments: many(previewDeployments),
|
previewDeployments: many(previewDeployments),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ export const deployments = pgTable("deployment", {
|
|||||||
(): AnyPgColumn => volumeBackups.volumeBackupId,
|
(): AnyPgColumn => volumeBackups.volumeBackupId,
|
||||||
{ onDelete: "cascade" },
|
{ onDelete: "cascade" },
|
||||||
),
|
),
|
||||||
|
buildServerId: text("buildServerId").references(() => server.serverId, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const deploymentsRelations = relations(deployments, ({ one }) => ({
|
export const deploymentsRelations = relations(deployments, ({ one }) => ({
|
||||||
@@ -84,6 +87,12 @@ export const deploymentsRelations = relations(deployments, ({ one }) => ({
|
|||||||
server: one(server, {
|
server: one(server, {
|
||||||
fields: [deployments.serverId],
|
fields: [deployments.serverId],
|
||||||
references: [server.serverId],
|
references: [server.serverId],
|
||||||
|
relationName: "deploymentServer",
|
||||||
|
}),
|
||||||
|
buildServer: one(server, {
|
||||||
|
fields: [deployments.buildServerId],
|
||||||
|
references: [server.serverId],
|
||||||
|
relationName: "deploymentBuildServer",
|
||||||
}),
|
}),
|
||||||
previewDeployment: one(previewDeployments, {
|
previewDeployment: one(previewDeployments, {
|
||||||
fields: [deployments.previewDeploymentId],
|
fields: [deployments.previewDeploymentId],
|
||||||
@@ -115,6 +124,7 @@ const schema = createInsertSchema(deployments, {
|
|||||||
composeId: z.string(),
|
composeId: z.string(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
previewDeploymentId: z.string(),
|
previewDeploymentId: z.string(),
|
||||||
|
buildServerId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateDeployment = schema
|
export const apiCreateDeployment = schema
|
||||||
|
|||||||
@@ -33,7 +33,12 @@ export const registry = pgTable("registry", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const registryRelations = relations(registry, ({ many }) => ({
|
export const registryRelations = relations(registry, ({ many }) => ({
|
||||||
applications: many(applications),
|
applications: many(applications, {
|
||||||
|
relationName: "applicationRegistry",
|
||||||
|
}),
|
||||||
|
buildApplications: many(applications, {
|
||||||
|
relationName: "applicationBuildRegistry",
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const createSchema = createInsertSchema(registry, {
|
const createSchema = createInsertSchema(registry, {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { schedules } from "./schedule";
|
|||||||
import { sshKeys } from "./ssh-key";
|
import { sshKeys } from "./ssh-key";
|
||||||
import { generateAppName } from "./utils";
|
import { generateAppName } from "./utils";
|
||||||
export const serverStatus = pgEnum("serverStatus", ["active", "inactive"]);
|
export const serverStatus = pgEnum("serverStatus", ["active", "inactive"]);
|
||||||
|
export const serverType = pgEnum("serverType", ["deploy", "build"]);
|
||||||
|
|
||||||
export const server = pgTable("server", {
|
export const server = pgTable("server", {
|
||||||
serverId: text("serverId")
|
serverId: text("serverId")
|
||||||
@@ -44,6 +45,7 @@ export const server = pgTable("server", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.references(() => organization.id, { onDelete: "cascade" }),
|
.references(() => organization.id, { onDelete: "cascade" }),
|
||||||
serverStatus: serverStatus("serverStatus").notNull().default("active"),
|
serverStatus: serverStatus("serverStatus").notNull().default("active"),
|
||||||
|
serverType: serverType("serverType").notNull().default("deploy"),
|
||||||
command: text("command").notNull().default(""),
|
command: text("command").notNull().default(""),
|
||||||
sshKeyId: text("sshKeyId").references(() => sshKeys.sshKeyId, {
|
sshKeyId: text("sshKeyId").references(() => sshKeys.sshKeyId, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
@@ -97,12 +99,22 @@ export const server = pgTable("server", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const serverRelations = relations(server, ({ one, many }) => ({
|
export const serverRelations = relations(server, ({ one, many }) => ({
|
||||||
deployments: many(deployments),
|
deployments: many(deployments, {
|
||||||
|
relationName: "deploymentServer",
|
||||||
|
}),
|
||||||
|
buildDeployments: many(deployments, {
|
||||||
|
relationName: "deploymentBuildServer",
|
||||||
|
}),
|
||||||
sshKey: one(sshKeys, {
|
sshKey: one(sshKeys, {
|
||||||
fields: [server.sshKeyId],
|
fields: [server.sshKeyId],
|
||||||
references: [sshKeys.sshKeyId],
|
references: [sshKeys.sshKeyId],
|
||||||
}),
|
}),
|
||||||
applications: many(applications),
|
applications: many(applications, {
|
||||||
|
relationName: "applicationServer",
|
||||||
|
}),
|
||||||
|
buildApplications: many(applications, {
|
||||||
|
relationName: "applicationBuildServer",
|
||||||
|
}),
|
||||||
compose: many(compose),
|
compose: many(compose),
|
||||||
redis: many(redis),
|
redis: many(redis),
|
||||||
mariadb: many(mariadb),
|
mariadb: many(mariadb),
|
||||||
@@ -131,6 +143,7 @@ export const apiCreateServer = createSchema
|
|||||||
port: true,
|
port: true,
|
||||||
username: true,
|
username: true,
|
||||||
sshKeyId: true,
|
sshKeyId: true,
|
||||||
|
serverType: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
@@ -155,6 +168,7 @@ export const apiUpdateServer = createSchema
|
|||||||
port: true,
|
port: true,
|
||||||
username: true,
|
username: true,
|
||||||
sshKeyId: true,
|
sshKeyId: true,
|
||||||
|
serverType: true,
|
||||||
})
|
})
|
||||||
.required()
|
.required()
|
||||||
.extend({
|
.extend({
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ export const findApplicationById = async (applicationId: string) => {
|
|||||||
gitea: true,
|
gitea: true,
|
||||||
server: true,
|
server: true,
|
||||||
previewDeployments: true,
|
previewDeployments: true,
|
||||||
|
buildRegistry: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!application) {
|
if (!application) {
|
||||||
@@ -172,6 +173,7 @@ export const deployApplication = async ({
|
|||||||
descriptionLog: string;
|
descriptionLog: string;
|
||||||
}) => {
|
}) => {
|
||||||
const application = await findApplicationById(applicationId);
|
const application = await findApplicationById(applicationId);
|
||||||
|
const serverId = application.buildServerId || application.serverId;
|
||||||
|
|
||||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.environment.projectId}/environment/${application.environmentId}/services/application/${application.applicationId}?tab=deployments`;
|
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.environment.projectId}/environment/${application.environmentId}/services/application/${application.applicationId}?tab=deployments`;
|
||||||
const deployment = await createDeployment({
|
const deployment = await createDeployment({
|
||||||
@@ -199,8 +201,8 @@ export const deployApplication = async ({
|
|||||||
command += getBuildCommand(application);
|
command += getBuildCommand(application);
|
||||||
|
|
||||||
const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
|
const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
|
||||||
if (application.serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(application.serverId, commandWithLog);
|
await execAsyncRemote(serverId, commandWithLog);
|
||||||
} else {
|
} else {
|
||||||
await execAsync(commandWithLog);
|
await execAsync(commandWithLog);
|
||||||
}
|
}
|
||||||
@@ -240,8 +242,8 @@ export const deployApplication = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
command += `echo "\nError occurred ❌, check the logs for details." >> ${deployment.logPath};`;
|
command += `echo "\nError occurred ❌, check the logs for details." >> ${deployment.logPath};`;
|
||||||
if (application.serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(application.serverId, command);
|
await execAsyncRemote(serverId, command);
|
||||||
} else {
|
} else {
|
||||||
await execAsync(command);
|
await execAsync(command);
|
||||||
}
|
}
|
||||||
@@ -285,6 +287,7 @@ export const rebuildApplication = async ({
|
|||||||
descriptionLog: string;
|
descriptionLog: string;
|
||||||
}) => {
|
}) => {
|
||||||
const application = await findApplicationById(applicationId);
|
const application = await findApplicationById(applicationId);
|
||||||
|
const serverId = application.buildServerId || application.serverId;
|
||||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.environment.projectId}/environment/${application.environmentId}/services/application/${application.applicationId}?tab=deployments`;
|
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.environment.projectId}/environment/${application.environmentId}/services/application/${application.applicationId}?tab=deployments`;
|
||||||
|
|
||||||
const deployment = await createDeployment({
|
const deployment = await createDeployment({
|
||||||
@@ -298,8 +301,8 @@ export const rebuildApplication = async ({
|
|||||||
// Check case for docker only
|
// Check case for docker only
|
||||||
command += getBuildCommand(application);
|
command += getBuildCommand(application);
|
||||||
const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
|
const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
|
||||||
if (application.serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(application.serverId, commandWithLog);
|
await execAsyncRemote(serverId, commandWithLog);
|
||||||
} else {
|
} else {
|
||||||
await execAsync(commandWithLog);
|
await execAsync(commandWithLog);
|
||||||
}
|
}
|
||||||
@@ -338,8 +341,8 @@ export const rebuildApplication = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
command += `echo "\nError occurred ❌, check the logs for details." >> ${deployment.logPath};`;
|
command += `echo "\nError occurred ❌, check the logs for details." >> ${deployment.logPath};`;
|
||||||
if (application.serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(application.serverId, command);
|
await execAsyncRemote(serverId, command);
|
||||||
} else {
|
} else {
|
||||||
await execAsync(command);
|
await execAsync(command);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export const createDeployment = async (
|
|||||||
"application",
|
"application",
|
||||||
application.serverId,
|
application.serverId,
|
||||||
);
|
);
|
||||||
const serverId = application.serverId;
|
const serverId = application.buildServerId || application.serverId;
|
||||||
|
|
||||||
const { LOGS_PATH } = paths(!!serverId);
|
const { LOGS_PATH } = paths(!!serverId);
|
||||||
const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss");
|
const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss");
|
||||||
@@ -93,6 +93,7 @@ export const createDeployment = async (
|
|||||||
const command = `
|
const command = `
|
||||||
mkdir -p ${LOGS_PATH}/${application.appName};
|
mkdir -p ${LOGS_PATH}/${application.appName};
|
||||||
echo "Initializing deployment" >> ${logFilePath};
|
echo "Initializing deployment" >> ${logFilePath};
|
||||||
|
echo "Building on ${serverId ? "Build Server" : "Dokploy Server"}" >> ${logFilePath};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
await execAsyncRemote(server.serverId, command);
|
await execAsyncRemote(server.serverId, command);
|
||||||
@@ -112,6 +113,9 @@ export const createDeployment = async (
|
|||||||
logPath: logFilePath,
|
logPath: logFilePath,
|
||||||
description: deployment.description || "",
|
description: deployment.description || "",
|
||||||
startedAt: new Date().toISOString(),
|
startedAt: new Date().toISOString(),
|
||||||
|
...(application.buildServerId && {
|
||||||
|
buildServerId: application.buildServerId,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
|
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
|
||||||
|
|||||||
@@ -51,7 +51,12 @@ export const serverSetup = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
onData?.("\nInstalling Server Dependencies: ✅\n");
|
const isBuildServer = server.serverType === "build";
|
||||||
|
onData?.(
|
||||||
|
isBuildServer
|
||||||
|
? "\nInstalling Build Server Dependencies: ✅\n"
|
||||||
|
: "\nInstalling Server Dependencies: ✅\n",
|
||||||
|
);
|
||||||
await installRequirements(serverId, onData);
|
await installRequirements(serverId, onData);
|
||||||
|
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
@@ -65,7 +70,7 @@ export const serverSetup = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultCommand = () => {
|
export const defaultCommand = (isBuildServer = false) => {
|
||||||
const bashCommand = `
|
const bashCommand = `
|
||||||
set -e;
|
set -e;
|
||||||
DOCKER_VERSION=27.0.3
|
DOCKER_VERSION=27.0.3
|
||||||
@@ -126,6 +131,7 @@ echo -e "---------------------------------------------"
|
|||||||
echo "| CPU Architecture | $SYS_ARCH"
|
echo "| CPU Architecture | $SYS_ARCH"
|
||||||
echo "| Operating System | $OS_TYPE $OS_VERSION"
|
echo "| Operating System | $OS_TYPE $OS_VERSION"
|
||||||
echo "| Docker | $DOCKER_VERSION"
|
echo "| Docker | $DOCKER_VERSION"
|
||||||
|
${isBuildServer ? 'echo "| Server Type | Build Server"' : ""}
|
||||||
echo -e "---------------------------------------------\n"
|
echo -e "---------------------------------------------\n"
|
||||||
echo -e "1. Installing required packages (curl, wget, git, jq, openssl). "
|
echo -e "1. Installing required packages (curl, wget, git, jq, openssl). "
|
||||||
|
|
||||||
@@ -135,6 +141,9 @@ command_exists() {
|
|||||||
|
|
||||||
${installUtilities()}
|
${installUtilities()}
|
||||||
|
|
||||||
|
${
|
||||||
|
!isBuildServer
|
||||||
|
? `
|
||||||
echo -e "2. Validating ports. "
|
echo -e "2. Validating ports. "
|
||||||
${validatePorts()}
|
${validatePorts()}
|
||||||
|
|
||||||
@@ -173,6 +182,25 @@ ${installBuildpacks()}
|
|||||||
|
|
||||||
echo -e "13. Installing Railpack"
|
echo -e "13. Installing Railpack"
|
||||||
${installRailpack()}
|
${installRailpack()}
|
||||||
|
`
|
||||||
|
: `
|
||||||
|
echo -e "2. Installing Docker. "
|
||||||
|
${installDocker()}
|
||||||
|
|
||||||
|
echo -e "3. Setting up Directories"
|
||||||
|
${setupMainDirectory()}
|
||||||
|
${setupDirectories()}
|
||||||
|
|
||||||
|
echo -e "4. Installing Nixpacks"
|
||||||
|
${installNixpacks()}
|
||||||
|
|
||||||
|
echo -e "5. Installing Buildpacks"
|
||||||
|
${installBuildpacks()}
|
||||||
|
|
||||||
|
echo -e "6. Installing Railpack"
|
||||||
|
${installRailpack()}
|
||||||
|
`
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return bashCommand;
|
return bashCommand;
|
||||||
@@ -189,10 +217,12 @@ const installRequirements = async (
|
|||||||
throw new Error("No SSH Key found");
|
throw new Error("No SSH Key found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isBuildServer = server.serverType === "build";
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
client
|
client
|
||||||
.once("ready", () => {
|
.once("ready", () => {
|
||||||
const command = server.command || defaultCommand();
|
const command = server.command || defaultCommand(isBuildServer);
|
||||||
client.exec(command, (err, stream) => {
|
client.exec(command, (err, stream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
onData?.(err.message);
|
onData?.(err.message);
|
||||||
|
|||||||
@@ -29,13 +29,14 @@ export type ApplicationNested = InferResultType<
|
|||||||
redirects: true;
|
redirects: true;
|
||||||
ports: true;
|
ports: true;
|
||||||
registry: true;
|
registry: true;
|
||||||
|
buildRegistry: true;
|
||||||
environment: { with: { project: true } };
|
environment: { with: { project: true } };
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const getBuildCommand = (application: ApplicationNested) => {
|
export const getBuildCommand = (application: ApplicationNested) => {
|
||||||
let command = "";
|
let command = "";
|
||||||
const { buildType, registry } = application;
|
const { buildType } = application;
|
||||||
|
|
||||||
if (application.sourceType === "docker") {
|
if (application.sourceType === "docker") {
|
||||||
return "";
|
return "";
|
||||||
@@ -60,7 +61,7 @@ export const getBuildCommand = (application: ApplicationNested) => {
|
|||||||
command = getRailpackCommand(application);
|
command = getRailpackCommand(application);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (registry) {
|
if (application.registry || application.buildRegistry) {
|
||||||
command += uploadImageRemoteCommand(application);
|
command += uploadImageRemoteCommand(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,13 +170,15 @@ export const mechanizeDockerContainer = async (
|
|||||||
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
await docker.createService(settings);
|
await docker.createService(settings);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getImageName = (application: ApplicationNested) => {
|
const getImageName = (application: ApplicationNested) => {
|
||||||
const { appName, sourceType, dockerImage, registry } = application;
|
const { appName, sourceType, dockerImage, registry, buildRegistry } =
|
||||||
|
application;
|
||||||
const imageName = `${appName}:latest`;
|
const imageName = `${appName}:latest`;
|
||||||
if (sourceType === "docker") {
|
if (sourceType === "docker") {
|
||||||
return dockerImage || "ERROR-NO-IMAGE-PROVIDED";
|
return dockerImage || "ERROR-NO-IMAGE-PROVIDED";
|
||||||
@@ -188,12 +191,26 @@ const getImageName = (application: ApplicationNested) => {
|
|||||||
: `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`;
|
: `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`;
|
||||||
return registryTag;
|
return registryTag;
|
||||||
}
|
}
|
||||||
|
if (buildRegistry) {
|
||||||
|
const { registryUrl, imagePrefix, username } = buildRegistry;
|
||||||
|
const registryTag = imagePrefix
|
||||||
|
? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}`
|
||||||
|
: `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`;
|
||||||
|
return registryTag;
|
||||||
|
}
|
||||||
|
|
||||||
return imageName;
|
return imageName;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAuthConfig = (application: ApplicationNested) => {
|
export const getAuthConfig = (application: ApplicationNested) => {
|
||||||
const { registry, username, password, sourceType, registryUrl } = application;
|
const {
|
||||||
|
registry,
|
||||||
|
buildRegistry,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
sourceType,
|
||||||
|
registryUrl,
|
||||||
|
} = application;
|
||||||
|
|
||||||
if (sourceType === "docker") {
|
if (sourceType === "docker") {
|
||||||
if (username && password) {
|
if (username && password) {
|
||||||
@@ -209,6 +226,12 @@ export const getAuthConfig = (application: ApplicationNested) => {
|
|||||||
username: registry.username,
|
username: registry.username,
|
||||||
serveraddress: registry.registryUrl,
|
serveraddress: registry.registryUrl,
|
||||||
};
|
};
|
||||||
|
} else if (buildRegistry) {
|
||||||
|
return {
|
||||||
|
password: buildRegistry.password,
|
||||||
|
username: buildRegistry.username,
|
||||||
|
serveraddress: buildRegistry.registryUrl,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -1,44 +1,77 @@
|
|||||||
|
import type { Registry } from "@dokploy/server/services/registry";
|
||||||
import type { ApplicationNested } from "../builders";
|
import type { ApplicationNested } from "../builders";
|
||||||
|
|
||||||
export const uploadImageRemoteCommand = (application: ApplicationNested) => {
|
export const uploadImageRemoteCommand = (application: ApplicationNested) => {
|
||||||
const registry = application.registry;
|
const registry = application.registry;
|
||||||
|
const buildRegistry = application.buildRegistry;
|
||||||
|
|
||||||
if (!registry) {
|
if (!registry && !buildRegistry) {
|
||||||
throw new Error("Registry not found");
|
throw new Error("No registry found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { registryUrl, imagePrefix, username } = registry;
|
|
||||||
const { appName } = application;
|
const { appName } = application;
|
||||||
const imageName = `${appName}:latest`;
|
const imageName = `${appName}:latest`;
|
||||||
|
|
||||||
const finalURL = registryUrl;
|
const commands: string[] = [];
|
||||||
|
if (registry) {
|
||||||
// Build registry tag in correct format: registry.com/owner/image:tag
|
const registryTag = getRegistryTag(registry, imageName);
|
||||||
const registryTag = imagePrefix
|
if (registryTag) {
|
||||||
? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}`
|
commands.push(`echo "📦 [Enabled Registry Swarm]"`);
|
||||||
: `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`;
|
commands.push(getRegistryCommands(registry, imageName, registryTag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (buildRegistry) {
|
||||||
|
const buildRegistryTag = getRegistryTag(buildRegistry, imageName);
|
||||||
|
if (buildRegistryTag) {
|
||||||
|
commands.push(`echo "🔑 [Enabled Build Registry]"`);
|
||||||
|
commands.push(
|
||||||
|
getRegistryCommands(buildRegistry, imageName, buildRegistryTag),
|
||||||
|
);
|
||||||
|
commands.push(
|
||||||
|
`echo "⚠️ INFO: After the build is finished, you need to wait a few seconds for the server to download the image and run the container."`,
|
||||||
|
);
|
||||||
|
commands.push(
|
||||||
|
`echo "📊 Check the Logs tab to see when the container starts running."`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const command = `
|
return commands.join("\n");
|
||||||
echo "📦 [Enabled Registry] Uploading image to '${registry.registryType}' | '${registryTag}'" ;
|
|
||||||
echo "${registry.password}" | docker login ${finalURL} -u ${registry.username} --password-stdin || {
|
|
||||||
echo "❌ DockerHub Failed" ;
|
|
||||||
exit 1;
|
|
||||||
}
|
|
||||||
echo "✅ Registry Login Success" ;
|
|
||||||
docker tag ${imageName} ${registryTag} || {
|
|
||||||
echo "❌ Error tagging image" ;
|
|
||||||
exit 1;
|
|
||||||
}
|
|
||||||
echo "✅ Image Tagged" ;
|
|
||||||
docker push ${registryTag} || {
|
|
||||||
echo "❌ Error pushing image" ;
|
|
||||||
exit 1;
|
|
||||||
}
|
|
||||||
echo "✅ Image Pushed" ;
|
|
||||||
`;
|
|
||||||
return command;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const getRegistryTag = (registry: Registry | null, imageName: string) => {
|
||||||
|
if (!registry) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { registryUrl, imagePrefix, username } = registry;
|
||||||
|
return imagePrefix
|
||||||
|
? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}`
|
||||||
|
: `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRegistryCommands = (
|
||||||
|
registry: Registry,
|
||||||
|
imageName: string,
|
||||||
|
registryTag: string,
|
||||||
|
): string => {
|
||||||
|
return `
|
||||||
|
echo "📦 [Enabled Registry] Uploading image to '${registry.registryType}' | '${registryTag}'" ;
|
||||||
|
echo "${registry.password}" | docker login ${registry.registryUrl} -u ${registry.username} --password-stdin || {
|
||||||
|
echo "❌ DockerHub Failed" ;
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
echo "✅ Registry Login Success" ;
|
||||||
|
docker tag ${imageName} ${registryTag} || {
|
||||||
|
echo "❌ Error tagging image" ;
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
echo "✅ Image Tagged" ;
|
||||||
|
docker push ${registryTag} || {
|
||||||
|
echo "❌ Error pushing image" ;
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
echo "✅ Image Pushed" ;
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user