Skip to content

feat: optionally serve installers with a filename tag - #3

Merged
jlcarvalho merged 2 commits into
mainfrom
feat/tokenized-download
Aug 6, 2026
Merged

feat: optionally serve installers with a filename tag#3
jlcarvalho merged 2 commits into
mainfrom
feat/tokenized-download

Conversation

@jlcarvalho

@jlcarvalho jlcarvalho commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an optional release mirror so the Windows installer can be served with a caller-supplied tag in its filename.

GitHub's signed asset URLs carry response-content-disposition inside the signature, so a redirect cannot control the filename the browser saves. With the asset copied to a bucket we control, a presigned URL can — and it does so without any bytes flowing through this server, which is the design principle the service is built on.

This resolves the // TODO: armazenar no S3 e gerar link de download de lá that was already sitting in proxyPrivateDownload.

What it does

  • src/lib/mirror.ts — copies each release asset to an S3-compatible bucket. One copy per release, not per download. Kicked off in the background from traffic the service already receives, including the update poll, so a new release is usually mirrored before anyone clicks download.
  • ?t=<tag> on /download and /download/:platform — responds 302 to a presigned URL with ResponseContentDisposition, forcing <asset> [<prefix><tag>].exe.

Design notes

  • Fails open. Mirror not configured, malformed tag, or asset not copied yet → plain redirect. The download never fails; the caller just doesn't get the tag.
  • With no bucket configured every route behaves exactly as before, so this can be merged and deployed ahead of the infrastructure.
  • ?t= is ignored when ?update=true. A Squirrel update must keep the canonical filename or the protocol stops recognising the package.
  • The object key includes the repository (<prefix>/<REPOSITORY>/<version>/<asset>), so several deployments reading different release repos can share one bucket.
  • The tag prefix is configurable (INSTALLER_TAG_PREFIX). Whatever client reads the filename must be configured with the same value; keeping it out of the code lets the pair be rotated without a release.
  • The tag is opaque to this service and may carry sensitive caller data. It is never logged — the per-request console.log(latest.platforms) in downloadPlatform was removed for that reason. Callers should prefer a short-lived, single-use value.
  • Renaming is safe for signed binaries: Authenticode covers the PE contents, not the filename, and SmartScreen reputation is keyed on the hash — identical bytes keep both.

Configuration

All optional; the mirror stays off unless the first three are present. Documented in the README.

BUCKET_NAME_CLOUDFLARE_R2
AWS_ACCESS_KEY_ID_CLOUDFLARE_R2
AWS_SECRET_ACCESS_KEY_CLOUDFLARE_R2
AWS_ENDPOINT_CLOUDFLARE_R2
AWS_DEFAULT_REGION_CLOUDFLARE_R2   # default: auto
RELEASES_MIRROR_PREFIX             # default: releases
INSTALLER_TAG_PREFIX               # default: build-

New dependencies: @aws-sdk/client-s3, @aws-sdk/lib-storage, @aws-sdk/s3-request-presigner.

Tests

40 passing. The mirror is covered against a stubbed S3 client: the tagged filename, the object key, the short signature lifetime, the fallback when an asset has not been copied, 20 concurrent calls resulting in a single download, and GitHub/upload failures not taking the process down.

A first version of these tests only exercised pure helpers and the disabled path — five mutations survived it (dropping ResponseContentDisposition, dropping tag validation, widening the presigned URL lifetime, &&|| in isConfigured, inverting the concurrency guard). All five now fail.

Not exercised against a real bucket. On the first deploy, watch the log for [mirror] espelhado … and confirm the downloaded file arrives with the expected name.

🤖 Generated with Claude Code

jlcarvalho and others added 2 commits July 28, 2026 18:16
GitHub's signed asset URLs carry `response-content-disposition` inside the
signature, so a redirect cannot control the filename the browser saves. This
adds an optional mirror: each release asset is copied once to an S3-compatible
bucket, and `/download` and `/download/:platform` accept `?t=<tag>` to serve the
Windows installer through a presigned URL that forces
`<asset> [<prefix><tag>].exe`. Lets an installed client correlate the download
it came from.

Still no bytes through this server — both paths are redirects.

Degrades quietly: mirror not configured, malformed tag, or release not copied
yet all fall back to the plain redirect, so the download never fails. With no
bucket configured every route behaves exactly as before, which makes it safe to
deploy ahead of the infrastructure.

`?t=` is ignored for `?update=true`, since a Squirrel update must keep the
canonical filename. The object key includes the repository so several
deployments can share one bucket. The tag prefix is configurable, and the tag
itself is opaque here — it may carry sensitive caller data, so it is never
logged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first version of these tests only exercised pure helpers and the
mirror-disabled path, so several mutations survived: dropping
`ResponseContentDisposition`, dropping tag validation, widening the presigned
URL lifetime, turning the `&&` in `isConfigured` into `||`, and inverting the
concurrency guard.

Now covers the tagged filename, the object key, the short signature lifetime,
the fallback when an asset has not been copied yet, 20 concurrent calls
resulting in a single download, and GitHub/upload failures not taking the
process down. All five mutations above now fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jlcarvalho
jlcarvalho force-pushed the feat/tokenized-download branch from cc8d380 to 14dee23 Compare July 28, 2026 21:16
@jlcarvalho jlcarvalho changed the title feat: serve o instalador com nome por usuário para o auto-login [TIP1-000] feat: optionally serve installers with a filename tag Jul 28, 2026
@jlcarvalho
jlcarvalho merged commit 01b679f into main Aug 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants