mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: ssh keys filesystel
This commit is contained in:
@@ -37,7 +37,7 @@ const GitProviderSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
branch: z.string().min(1, "Branch required"),
|
branch: z.string().min(1, "Branch required"),
|
||||||
buildPath: z.string().min(1, "Build Path required"),
|
buildPath: z.string().min(1, "Build Path required"),
|
||||||
sshKey: z.string(),
|
sshKey: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type GitProvider = z.infer<typeof GitProviderSchema>;
|
type GitProvider = z.infer<typeof GitProviderSchema>;
|
||||||
@@ -63,7 +63,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
branch: "",
|
branch: "",
|
||||||
buildPath: "/",
|
buildPath: "/",
|
||||||
repositoryURL: "",
|
repositoryURL: "",
|
||||||
sshKey: "",
|
sshKey: undefined,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(GitProviderSchema),
|
resolver: zodResolver(GitProviderSchema),
|
||||||
});
|
});
|
||||||
@@ -71,7 +71,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
form.reset({
|
form.reset({
|
||||||
sshKey: data.customGitSSHKeyId || "",
|
sshKey: data.customGitSSHKeyId || undefined,
|
||||||
branch: data.customGitBranch || "",
|
branch: data.customGitBranch || "",
|
||||||
buildPath: data.customGitBuildPath || "/",
|
buildPath: data.customGitBuildPath || "/",
|
||||||
repositoryURL: data.customGitUrl || "",
|
repositoryURL: data.customGitUrl || "",
|
||||||
@@ -84,7 +84,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
customGitBranch: values.branch,
|
customGitBranch: values.branch,
|
||||||
customGitBuildPath: values.buildPath,
|
customGitBuildPath: values.buildPath,
|
||||||
customGitUrl: values.repositoryURL,
|
customGitUrl: values.repositoryURL,
|
||||||
customGitSSHKeyId: values.sshKey,
|
customGitSSHKeyId: values.sshKey === "none" ? null : values.sshKey,
|
||||||
applicationId,
|
applicationId,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
@@ -149,6 +149,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
{sshKey.name}
|
{sshKey.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
|
<SelectItem value="none">None</SelectItem>
|
||||||
<SelectLabel>Keys ({sshKeys?.length})</SelectLabel>
|
<SelectLabel>Keys ({sshKeys?.length})</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
<SelectSeparator />
|
<SelectSeparator />
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ import {
|
|||||||
} from "@/server/utils/filesystem/directory";
|
} from "@/server/utils/filesystem/directory";
|
||||||
import {
|
import {
|
||||||
generateSSHKey,
|
generateSSHKey,
|
||||||
readRSAFile,
|
readSSHPublicKey,
|
||||||
removeRSAFiles,
|
removeSSHKey,
|
||||||
} from "@/server/utils/filesystem/ssh";
|
} from "@/server/utils/filesystem/ssh";
|
||||||
import {
|
import {
|
||||||
readConfig,
|
readConfig,
|
||||||
@@ -130,7 +130,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
async () => await removeMonitoringDirectory(application?.appName),
|
async () => await removeMonitoringDirectory(application?.appName),
|
||||||
async () => await removeTraefikConfig(application?.appName),
|
async () => await removeTraefikConfig(application?.appName),
|
||||||
async () => await removeService(application?.appName),
|
async () => await removeService(application?.appName),
|
||||||
async () => await removeRSAFiles(application?.appName),
|
async () => await removeSSHKey(application?.appName),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const operation of cleanupOperations) {
|
for (const operation of cleanupOperations) {
|
||||||
@@ -248,7 +248,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
try {
|
try {
|
||||||
await generateSSHKey(application.appName);
|
await generateSSHKey(application.appName);
|
||||||
const file = await readRSAFile(application.appName);
|
const file = await readSSHPublicKey(application.appName);
|
||||||
|
|
||||||
// await updateApplication(input.applicationId, {
|
// await updateApplication(input.applicationId, {
|
||||||
// customGitSSHKey: file,
|
// customGitSSHKey: file,
|
||||||
@@ -261,7 +261,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.input(apiFindOneApplication)
|
.input(apiFindOneApplication)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
await removeRSAFiles(application.appName);
|
await removeSSHKey(application.appName);
|
||||||
// await updateApplication(input.applicationId, {
|
// await updateApplication(input.applicationId, {
|
||||||
// customGitSSHKey: null,
|
// customGitSSHKey: null,
|
||||||
// });
|
// });
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import { randomizeComposeFile } from "@/server/utils/docker/compose";
|
|||||||
import { removeComposeDirectory } from "@/server/utils/filesystem/directory";
|
import { removeComposeDirectory } from "@/server/utils/filesystem/directory";
|
||||||
import {
|
import {
|
||||||
generateSSHKey,
|
generateSSHKey,
|
||||||
readRSAFile,
|
readSSHPublicKey,
|
||||||
removeRSAFiles,
|
removeSSHKey,
|
||||||
} from "@/server/utils/filesystem/ssh";
|
} from "@/server/utils/filesystem/ssh";
|
||||||
import { templates } from "@/templates/templates";
|
import { templates } from "@/templates/templates";
|
||||||
import type { TemplatesKeys } from "@/templates/types/templates-data.type";
|
import type { TemplatesKeys } from "@/templates/types/templates-data.type";
|
||||||
@@ -102,7 +102,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
async () => await removeCompose(composeResult),
|
async () => await removeCompose(composeResult),
|
||||||
async () => await removeDeploymentsByComposeId(composeResult),
|
async () => await removeDeploymentsByComposeId(composeResult),
|
||||||
async () => await removeComposeDirectory(composeResult.appName),
|
async () => await removeComposeDirectory(composeResult.appName),
|
||||||
async () => await removeRSAFiles(composeResult.appName),
|
async () => await removeSSHKey(composeResult.appName),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const operation of cleanupOperations) {
|
for (const operation of cleanupOperations) {
|
||||||
@@ -187,7 +187,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
try {
|
try {
|
||||||
await generateSSHKey(compose.appName);
|
await generateSSHKey(compose.appName);
|
||||||
const file = await readRSAFile(compose.appName);
|
const file = await readSSHPublicKey(compose.appName);
|
||||||
|
|
||||||
await updateCompose(input.composeId, {
|
await updateCompose(input.composeId, {
|
||||||
customGitSSHKey: file,
|
customGitSSHKey: file,
|
||||||
@@ -208,7 +208,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(apiFindCompose)
|
.input(apiFindCompose)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
await removeRSAFiles(compose.appName);
|
await removeSSHKey(compose.appName);
|
||||||
await updateCompose(input.composeId, {
|
await updateCompose(input.composeId, {
|
||||||
customGitSSHKey: null,
|
customGitSSHKey: null,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
type apiUpdateSshKey,
|
type apiUpdateSshKey,
|
||||||
sshKeys,
|
sshKeys,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
|
import { removeSSHKey, saveSSHKey } from "@/server/utils/filesystem/ssh";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
@@ -20,6 +21,11 @@ export const createSshKey = async ({
|
|||||||
.returning()
|
.returning()
|
||||||
.then((response) => response[0])
|
.then((response) => response[0])
|
||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
|
|
||||||
|
if (sshKey) {
|
||||||
|
saveSSHKey(sshKey.sshKeyId, sshKey.publicKey, privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
if (!sshKey) {
|
if (!sshKey) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
@@ -38,6 +44,8 @@ export const removeSSHKeyById = async (
|
|||||||
.where(eq(sshKeys.sshKeyId, sshKeyId))
|
.where(eq(sshKeys.sshKeyId, sshKeyId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
removeSSHKey(sshKeyId);
|
||||||
|
|
||||||
return result[0];
|
return result[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,35 @@ import * as path from "node:path";
|
|||||||
import { SSH_PATH } from "@/server/constants";
|
import { SSH_PATH } from "@/server/constants";
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
|
|
||||||
export const generateSSHKey = async (appName: string) => {
|
export const saveSSHKey = async (
|
||||||
|
id: string,
|
||||||
|
publicKey: string,
|
||||||
|
privateKey: string,
|
||||||
|
) => {
|
||||||
|
const applicationDirectory = SSH_PATH;
|
||||||
|
|
||||||
|
const privateKeyPath = path.join(applicationDirectory, `${id}_rsa`);
|
||||||
|
const publicKeyPath = path.join(applicationDirectory, `${id}_rsa.pub`);
|
||||||
|
|
||||||
|
const privateKeyStream = fs.createWriteStream(privateKeyPath, {
|
||||||
|
mode: 0o400,
|
||||||
|
});
|
||||||
|
privateKeyStream.write(privateKey);
|
||||||
|
privateKeyStream.end();
|
||||||
|
|
||||||
|
const publicKeyStream = fs.createWriteStream(publicKeyPath, { mode: 0o400 });
|
||||||
|
publicKeyStream.write(publicKey);
|
||||||
|
publicKeyStream.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateSSHKey = async (id: string) => {
|
||||||
const applicationDirectory = SSH_PATH;
|
const applicationDirectory = SSH_PATH;
|
||||||
|
|
||||||
if (!fs.existsSync(applicationDirectory)) {
|
if (!fs.existsSync(applicationDirectory)) {
|
||||||
fs.mkdirSync(applicationDirectory, { recursive: true });
|
fs.mkdirSync(applicationDirectory, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyPath = path.join(applicationDirectory, `${appName}_rsa`);
|
const keyPath = path.join(applicationDirectory, `${id}_rsa`);
|
||||||
|
|
||||||
if (fs.existsSync(`${keyPath}`)) {
|
if (fs.existsSync(`${keyPath}`)) {
|
||||||
fs.unlinkSync(`${keyPath}`);
|
fs.unlinkSync(`${keyPath}`);
|
||||||
@@ -37,12 +58,12 @@ export const generateSSHKey = async (appName: string) => {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const readRSAFile = async (appName: string) => {
|
export const readSSHPublicKey = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(SSH_PATH)) {
|
if (!fs.existsSync(SSH_PATH)) {
|
||||||
fs.mkdirSync(SSH_PATH, { recursive: true });
|
fs.mkdirSync(SSH_PATH, { recursive: true });
|
||||||
}
|
}
|
||||||
const keyPath = path.join(SSH_PATH, `${appName}_rsa.pub`);
|
const keyPath = path.join(SSH_PATH, `${id}_rsa.pub`);
|
||||||
const data = fs.readFileSync(keyPath, { encoding: "utf-8" });
|
const data = fs.readFileSync(keyPath, { encoding: "utf-8" });
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -50,10 +71,10 @@ export const readRSAFile = async (appName: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeRSAFiles = async (appName: string) => {
|
export const removeSSHKey = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
const publicKeyPath = path.join(SSH_PATH, `${appName}_rsa.pub`);
|
const publicKeyPath = path.join(SSH_PATH, `${id}_rsa.pub`);
|
||||||
const privateKeyPath = path.join(SSH_PATH, `${appName}_rsa`);
|
const privateKeyPath = path.join(SSH_PATH, `${id}_rsa`);
|
||||||
await fs.promises.unlink(publicKeyPath);
|
await fs.promises.unlink(publicKeyPath);
|
||||||
await fs.promises.unlink(privateKeyPath);
|
await fs.promises.unlink(privateKeyPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ export const cloneGitRepository = async (
|
|||||||
appName: string;
|
appName: string;
|
||||||
customGitUrl?: string | null;
|
customGitUrl?: string | null;
|
||||||
customGitBranch?: string | null;
|
customGitBranch?: string | null;
|
||||||
customGitSSHKey?: string | null;
|
customGitSSHKeyId?: string | null;
|
||||||
},
|
},
|
||||||
logPath: string,
|
logPath: string,
|
||||||
isCompose = false,
|
isCompose = false,
|
||||||
) => {
|
) => {
|
||||||
const { appName, customGitUrl, customGitBranch, customGitSSHKey } = entity;
|
const { appName, customGitUrl, customGitBranch, customGitSSHKeyId } = entity;
|
||||||
|
|
||||||
if (!customGitUrl || !customGitBranch) {
|
if (!customGitUrl || !customGitBranch) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -26,7 +26,7 @@ export const cloneGitRepository = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const writeStream = createWriteStream(logPath, { flags: "a" });
|
const writeStream = createWriteStream(logPath, { flags: "a" });
|
||||||
const keyPath = path.join(SSH_PATH, `${appName}_rsa`);
|
const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
|
||||||
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
||||||
const outputPath = join(basePath, appName, "code");
|
const outputPath = join(basePath, appName, "code");
|
||||||
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
||||||
@@ -39,7 +39,7 @@ export const cloneGitRepository = async (
|
|||||||
writeStream.write(
|
writeStream.write(
|
||||||
`\nCloning Repo Custom ${customGitUrl} to ${outputPath}: ✅\n`,
|
`\nCloning Repo Custom ${customGitUrl} to ${outputPath}: ✅\n`,
|
||||||
);
|
);
|
||||||
|
console.log(customGitSSHKeyId);
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
"git",
|
"git",
|
||||||
[
|
[
|
||||||
@@ -60,7 +60,7 @@ export const cloneGitRepository = async (
|
|||||||
{
|
{
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
...(customGitSSHKey && {
|
...(customGitSSHKeyId && {
|
||||||
GIT_SSH_COMMAND: `ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}`,
|
GIT_SSH_COMMAND: `ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user