import { existsSync } from "node:fs"; import path from "node:path"; import * as argon2 from "argon2"; import { Elysia, t } from "elysia"; import { ADMIN_USERNAME, REPOS_DIR, VALID_REPO_NAME_RE } from "../constants.ts"; import { db } from "../db"; function pktLine(str: string): Buffer { const len = Buffer.byteLength(str, "utf-8") + 4; return Buffer.from(len.toString(16).padStart(4, "0") + str, "utf-8"); } const PKT_FLUSH = Buffer.from("0000"); async function verifyBasicAuth( authHeader: string | null, adminOnly: boolean, ): Promise { if (!authHeader?.startsWith("Basic ")) return false; const decoded = Buffer.from(authHeader.slice(6), "base64").toString( "utf-8", ); const sep = decoded.indexOf(":"); if (sep === -1) return false; const username = decoded.slice(0, sep); const password = decoded.slice(sep + 1); if (adminOnly && username !== ADMIN_USERNAME) return false; const user = await db .selectFrom("users") .select("password_hash") .where("username", "=", username) .executeTakeFirst(); if (!user?.password_hash) return false; try { return await argon2.verify(user.password_hash, password); } catch { return false; } } function unauthorized(): Response { return new Response("Unauthorized", { status: 401, headers: { "WWW-Authenticate": 'Basic realm="Hearthforge"', "Content-Type": "text/plain", }, }); } async function getRepo( slug: string, ): Promise<{ repoPath: string; isPrivate: boolean } | null> { const repoName = slug.endsWith(".git") ? slug.slice(0, -4) : slug; if (!VALID_REPO_NAME_RE.test(repoName)) return null; const repo = await db .selectFrom("repositories") .select(["name", "is_private"]) .where("name", "=", repoName) .executeTakeFirst(); if (!repo) return null; const repoPath = path.join(REPOS_DIR, `${repo.name}.git`); if (!existsSync(repoPath)) return null; return { repoPath, isPrivate: repo.is_private === 1 }; } async function spawnGit( args: string[], stdinBytes?: Uint8Array, ): Promise { const proc = Bun.spawn(args, { stdin: stdinBytes ?? "ignore", stdout: "pipe", stderr: "pipe", }); await proc.exited; return new Uint8Array(await Bun.readableStreamToArrayBuffer(proc.stdout)); } export const gitRoutes = new Elysia() // info/refs — serves both upload-pack (clone/fetch) and receive-pack (push) .get( "/:repo/info/refs", async ({ params, query, request }) => { const service = query.service; if ( service !== "git-upload-pack" && service !== "git-receive-pack" ) { return new Response("Bad Request", { status: 400 }); } const repo = await getRepo(params.repo); if (!repo) return new Response("Not Found", { status: 404 }); const authHeader = request.headers.get("Authorization"); if (service === "git-receive-pack") { if (!(await verifyBasicAuth(authHeader, true))) return unauthorized(); } else if (repo.isPrivate) { if (!(await verifyBasicAuth(authHeader, false))) return unauthorized(); } const gitCmd = service === "git-receive-pack" ? "receive-pack" : "upload-pack"; const refs = await spawnGit([ "git", gitCmd, "--stateless-rpc", "--advertise-refs", repo.repoPath, ]); const body = Buffer.concat([ pktLine(`# service=git-${gitCmd}\n`), PKT_FLUSH, refs, ]); return new Response(body, { headers: { "Content-Type": `application/x-git-${gitCmd}-advertisement`, "Cache-Control": "no-cache", }, }); }, { query: t.Object({ service: t.Optional(t.String()) }), }, ) // upload-pack POST — clone/fetch pack transfer (public for public repos) .post("/:repo/git-upload-pack", async ({ params, request }) => { const repo = await getRepo(params.repo); if (!repo) return new Response("Not Found", { status: 404 }); if (repo.isPrivate) { if ( !(await verifyBasicAuth( request.headers.get("Authorization"), false, )) ) return unauthorized(); } const body = new Uint8Array(await request.arrayBuffer()); const result = await spawnGit( ["git", "upload-pack", "--stateless-rpc", repo.repoPath], body, ); return new Response(result, { headers: { "Content-Type": "application/x-git-upload-pack-result", "Cache-Control": "no-cache", }, }); }) // receive-pack POST — push pack transfer (admin only) .post("/:repo/git-receive-pack", async ({ params, request }) => { if ( !(await verifyBasicAuth(request.headers.get("Authorization"), true)) ) return unauthorized(); const repo = await getRepo(params.repo); if (!repo) return new Response("Not Found", { status: 404 }); const body = new Uint8Array(await request.arrayBuffer()); const result = await spawnGit( ["git", "receive-pack", "--stateless-rpc", repo.repoPath], body, ); return new Response(result, { headers: { "Content-Type": "application/x-git-receive-pack-result", "Cache-Control": "no-cache", }, }); });