dokploy/server/utils/providers/git.ts
Lorenzo Migliorero c681aa2e9f
feat: compose app
2024-07-25 22:10:35 +02:00

125 lines
3.3 KiB
TypeScript

import { createWriteStream } from "node:fs";
import path, { join } from "node:path";
import { APPLICATIONS_PATH, COMPOSE_PATH, SSH_PATH } from "@/server/constants";
import { TRPCError } from "@trpc/server";
import { recreateDirectory } from "../filesystem/directory";
import { execAsync } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
export const cloneGitRepository = async (
entity: {
appName: string;
customGitUrl?: string | null;
customGitBranch?: string | null;
customGitSSHKeyId?: string | null;
},
logPath: string,
isCompose = false,
) => {
const { appName, customGitUrl, customGitBranch, customGitSSHKeyId } = entity;
if (!customGitUrl || !customGitBranch) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error: Repository not found",
});
}
const writeStream = createWriteStream(logPath, { flags: "a" });
const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
const outputPath = join(basePath, appName, "code");
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
try {
await addHostToKnownHosts(customGitUrl);
await recreateDirectory(outputPath);
// const command = `GIT_SSH_COMMAND="ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}" git clone --branch ${customGitBranch} --depth 1 ${customGitUrl} ${gitCopyPath} --progress`;
// const { stdout, stderr } = await execAsync(command);
writeStream.write(
`\nCloning Repo Custom ${customGitUrl} to ${outputPath}: ✅\n`,
);
await spawnAsync(
"git",
[
"clone",
"--branch",
customGitBranch,
"--depth",
"1",
customGitUrl,
outputPath,
"--progress",
],
(data) => {
if (writeStream.writable) {
writeStream.write(data);
}
},
{
env: {
...process.env,
...(customGitSSHKeyId && {
GIT_SSH_COMMAND: `ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}`,
}),
},
},
);
writeStream.write(`\nCloned Custom Git ${customGitUrl}: ✅\n`);
} catch (error) {
writeStream.write(`\nERROR Cloning Custom Git: ${error}: ❌\n`);
throw error;
} finally {
writeStream.end();
}
};
const addHostToKnownHosts = async (repositoryURL: string) => {
const { domain, port } = sanitizeRepoPathSSH(repositoryURL);
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
const command = `ssh-keyscan -p ${port} ${domain} >> ${knownHostsPath}`;
try {
await execAsync(command);
} catch (error) {
console.error(`Error adding host to known_hosts: ${error}`);
throw error;
}
};
const sanitizeRepoPathSSH = (input: string) => {
const SSH_PATH_RE = new RegExp(
[
/^\s*/,
/(?:(?<proto>[a-z]+):\/\/)?/,
/(?:(?<user>[a-z_][a-z0-9_-]+)@)?/,
/(?<domain>[^\s\/\?#:]+)/,
/(?::(?<port>[0-9]{1,5}))?/,
/(?:[\/:](?<owner>[^\s\/\?#:]+))?/,
/(?:[\/:](?<repo>(?:[^\s\?#:.]|\.(?!git\/?\s*$))+))/,
/(?:.git)?\/?\s*$/,
]
.map((r) => r.source)
.join(""),
"i",
);
const found = input.match(SSH_PATH_RE);
if (!found) {
throw new Error(`Malformatted SSH path: ${input}`);
}
return {
user: found.groups?.user ?? "git",
domain: found.groups?.domain,
port: 22,
owner: found.groups?.owner ?? "",
repo: found.groups?.repo,
get repoPath() {
return `ssh://${this.user}@${this.domain}:${this.port}/${this.owner}${
this.owner && "/"
}${this.repo}.git`;
},
};
};