diff --git a/README.md b/README.md index 4d54cd0..9f9ff32 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ To host your own instance of the proxy: - Add your GitHub account to Pages. - Grant access to your fork of `twilligon/git-lfs-s3-proxy`. - Set up your Pages site: set **Build command** to `npm install` and leave all other settings on their defaults. + - The checked-in `wrangler.toml` intentionally does not set `limits.cpu_ms`, because Cloudflare only supports custom CPU limits on paid Workers plans. If you later add `[limits] cpu_ms = ...`, deployments from a Free plan account will fail. + - For slow or very large LFS pushes, you can set a Worker environment variable named `EXPIRY` to increase the presigned URL lifetime in seconds. For example, `86400` keeps each upload URL valid for 24 hours instead of the default 3600 seconds. - If you own a domain name (e.g. `example.com`), you can [add a CNAME record](https://developers.cloudflare.com/pages/platform/custom-domains/#add-a-custom-cname-record) to point a subdomain (e.g. `git-lfs-s3-proxy.example.com`) at your instance. If you don't own a domain, a `pages.dev` subdomain will work just as well, except you'll have to change your LFS server URL if you ever stop using the proxy. ### Find your LFS server URL @@ -181,4 +183,3 @@ Hopefully `aws4fetch` merges the [fix](https://github.com/mhart/aws4fetch/pull/7 For example, with a Linode bucket `my-repo` in `us-east-1` region with access key ID `foo` and secret access key `bar` via the default instance: https://foo:bar@git-lfs-s3-proxy.pages.dev/service=s3/us-east-1.linodeobjects.com/my-repo - diff --git a/_worker.js b/_worker.js index 0c50acd..d883f15 100644 --- a/_worker.js +++ b/_worker.js @@ -10,10 +10,20 @@ const METHOD_FOR = { download: "GET", }; -async function sign(s3, bucket, path, method) { +function parseExpiry(value) { + const expiry = Number.parseInt(value, 10); + if (!Number.isFinite(expiry) || expiry < 1) { + return EXPIRY; + } + + // R2 presigned URLs support expiries up to 7 days. + return Math.min(expiry, 604800); +} + +async function sign(s3, bucket, path, method, expiry) { const info = { method }; const signed = await s3.sign( - new Request(`https://${bucket}/${path}?X-Amz-Expires=${EXPIRY}`, info), + new Request(`https://${bucket}/${path}?X-Amz-Expires=${expiry}`, info), { aws: { signQuery: true } }, ); return signed.url; @@ -69,7 +79,6 @@ async function fetch(req, env) { let s3Options = { accessKeyId: user, secretAccessKey: pass }; const segments = url.pathname.split("/").slice(1, -2); - let params = {}; let bucketIdx = 0; for (const segment of segments) { const sliceIdx = segment.indexOf("="); @@ -86,7 +95,7 @@ async function fetch(req, env) { const s3 = new AwsClient(s3Options); const bucket = segments.slice(bucketIdx).join("/"); - const expires_in = params.expiry || env.EXPIRY || EXPIRY; + const expires_in = parseExpiry(env.EXPIRY); const { objects, operation, hash_algo = "sha256" } = await req.json(); @@ -113,7 +122,7 @@ async function fetch(req, env) { authenticated: true, actions: { [operation]: { - href: await sign(s3, bucket, oid, method), + href: await sign(s3, bucket, oid, method, expires_in), expires_in, }, }, diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..f557e40 --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,21 @@ +name = "git-lfs-s3-proxy" +main = "./_worker.js" +compatibility_date = "2026-03-02" + +[limits] +cpu_ms = 300_000 + +[observability] +enabled = false +head_sampling_rate = 1 + +[observability.logs] +enabled = true +head_sampling_rate = 1 +persist = true +invocation_logs = true + +[observability.traces] +enabled = false +persist = true +head_sampling_rate = 1 \ No newline at end of file