import type { Hono } from "hono"; import type { Bucket } from "../bucket/reader.ts"; import { headId, saveByPrefix } from "../mirror/queries.ts"; import { notFound, openPublicRepo, type PageContext } from "../pages/layout.ts"; import { countInstall } from "../releases/releases.ts"; import { cachedTarball } from "./cache.ts"; // Long-lived but never `immutable` (§18): a strip can change what a save's tarball holds. `private`, // so no shared cache keeps it after the repo turns private (§20). const TARBALL_CACHE = "private, max-age=31536000"; export function registerTarballRoutes(app: Hono, ctx: PageContext, bucket: Bucket): void { app.get("/:repo/archive/HEAD.tar.gz", (c) => { const repo = openPublicRepo(ctx, c.req.param("repo")); const head = repo && headId(repo.mirror); if (!repo || !head) return notFound(c, ctx); const path = `/${repo.settings.name}/archive/${head}.tar.gz`; // Nix pins the immutable link in its lock file, so it must be absolute. c.header("Link", `; rel="immutable"`); c.header("Cache-Control", "no-cache"); return c.redirect(path, 302); }); app.get("/:repo/archive/:id{[0-9a-f]+\\.tar\\.gz}", async (c) => { const repo = openPublicRepo(ctx, c.req.param("repo")); const id = c.req.param("id").replace(/\.tar\.gz$/, ""); const save = repo && saveByPrefix(repo.mirror, id); if (!repo || !save) return notFound(c, ctx); const name = repo.settings.name; if (save.id !== id) { c.header("Cache-Control", "public, max-age=60"); return c.redirect(`/${name}/archive/${save.id}.tar.gz`, 302); } const bytes = await cachedTarball(bucket, repo.mirror, name, save.id); if (!bytes) return notFound(c, ctx); // §22.2: a Nix fetch is an install. Only the tarball itself counts, not the redirects to it. if (c.req.header("User-Agent")?.includes("nix/")) countInstall(ctx.kepr, name); c.header("Content-Type", "application/gzip"); c.header("Content-Disposition", `attachment; filename="${name}-${save.id.slice(0, 8)}.tar.gz"`); c.header("Cache-Control", TARBALL_CACHE); return c.body(bytes.slice()); }); }