Skip to main content

Upload files/folders to Cloudflare R2 and get a shareable link

Project description

rink

Rink uploads any file or folder from your terminal directly to a Cloudflare R2 bucket and gives you a link!

R2 objects are private by default, so rink gives you two kinds of link:

  • Presigned (default) — a signed, self-expiring URL (up to 7 days). No bucket config needed.
  • Public — a permanent https://pub-xxxx.r2.dev/<key> (or custom-domain) URL, available after you enable public access on the bucket.

One-time Cloudflare setup

  1. Account ID — Cloudflare dashboard → R2 → copy the Account ID.
  2. Create a bucket — dashboard → R2 → Create bucket, or wrangler r2 bucket create <name>.
  3. API token — dashboard → R2 → Manage R2 API TokensCreate API Token with Object Read & Write. Copy the Access Key ID and Secret Access Key.
  4. (public links only) bucket → Settings → enable Public Development URL, and copy the pub-xxxx.r2.dev domain.

Install

From PyPI:

uv tool install rink     # install as a CLI on your PATH (recommended)
uv pip install rink      # or into the active environment
pip install rink         # or with plain pip

From source (for development):

uv sync                 # install deps into the project venv
uv run rink --help      # run from the project
uv tool install .       # install this checkout as a tool on your PATH

Configure

rink config             # interactive wizard

Writes ~/.config/rink/config.toml (mode 600). Every field can also be supplied via environment variables, which override the file: RINK_ACCOUNT_ID, RINK_ACCESS_KEY_ID, RINK_SECRET_ACCESS_KEY, RINK_BUCKET, RINK_PUBLIC_BASE_URL.

Buckets

rink buckets            # list all buckets in the account (default is marked ●)
rink use                # interactive picker to choose the default bucket
rink use my-bucket      # set the default bucket directly

rink up --bucket <name> still overrides the bucket for a single upload without changing the default.

Usage

rink up ./report.pdf                  # presigned link (default expiry)
rink up ./report.pdf --expiry 1d      # 1-day presigned link (30m, 2h, 7d, 1h30m…)
rink up ./report.pdf --public         # permanent public link
rink up ./report.pdf -c               # also copy the link to the clipboard
rink up ./report.pdf --qr             # also print a scannable QR code
rink up ./mydir                       # zip the folder, one link (default)
rink up ./mydir --recursive           # upload each file in parallel, link per file
rink up *.png a.txt                   # multiple paths at once
cat dump.sql | rink up - --name db.sql  # upload from stdin
rink up ./big.bin --prefix backups/   # store under a key prefix
rink up ./secret.pdf --random         # unguessable key prefix
rink up ./report.pdf --download       # browser downloads instead of rendering
rink up ./report.pdf --quiet          # print only the URL (for scripts / pipes)
rink up ./report.pdf --json           # machine-readable output

Options:

flag default meaning
--public / --presigned --presigned link type
--expiry <dur> config default_expiry presigned lifetime: 30m, 2h, 7d, 1h30m, or bare seconds (max 7d)
--zip / --recursive --zip folder handling (recursive uploads in parallel)
--prefix <str> none key prefix in the bucket
--bucket <name> configured bucket override target bucket
--name <str> source filename object name (single upload; required for stdin)
--random off prepend a random token to the key (unguessable links)
--download off set Content-Disposition: attachment
--copy / -c off copy link(s) to the clipboard
--qr off print a QR code (single upload)
--quiet / -q off print only the URL(s)
--json off print results as JSON
--workers <n> 4 parallel uploads for recursive folders

Large files (≥ 8 MiB) upload via multipart automatically with a progress bar. The URL(s) are printed on their own line(s) so they're easy to copy or pipe. Use - as a path to read from stdin (with --name).

Listing, expiry, and deleting

rink ls                 # list objects in the bucket with each link's time-left
rink ls backups/        # only keys under a prefix
rink ls --expired       # only entries whose presigned link has already expired
rink link myfile.zip    # regenerate a fresh link for an existing object (no re-upload)
rink link myfile.zip --expiry 7d -c   # …with a new expiry, copied to clipboard
rink open myfile.zip    # open the object's link in your browser
rink rm myfile.zip      # delete an object (this is how you revoke access)
rink rm a.txt b.txt -y  # delete several, skip the confirmation
rink prune              # delete objects whose presigned link has expired

rink ls reads the live bucket and joins it with a local log to show the link column:

  • 2h / 1d 3h — time left on the presigned link
  • permanent — shared via a public link (never expires)
  • untracked — object exists in the bucket but wasn't uploaded by rink, so we don't know its link expiry

Why a local log? R2 stores files, not links. A presigned URL's expiry is baked into the URL string at generation time — R2 keeps no record of it. So rink logs each upload to a small SQLite database at ~/.local/share/rink/rink.db (stdlib, no extra deps) to report time-left. Deleting with rink rm removes both the object and its log row.

Note: the file itself never expires — only the share link does. To make a file unreachable, delete it with rink rm. (R2 has no per-object private/public switch; publicity is bucket-wide, so deletion is the way to revoke.)

Development

uv sync --dev      # install with dev dependencies
uv run pytest -q   # run the test suite (uses moto to mock S3; no network)

Tests live in tests/. GitHub Actions runs them on every push/PR across Python 3.10–3.13.

Releasing

Releases publish to PyPI automatically when you push a version tag:

# bump the version in pyproject.toml and rink/__init__.py, then:
git tag v0.2.0
git push --tags

The publish workflow uses PyPI Trusted Publishing (OIDC) — no token stored in GitHub. One-time setup on PyPI: project rinkSettingsPublishing → add a GitHub publisher (owner HACKE-RC, repo rink, workflow publish.yml, environment pypi). Until that's configured, publish manually with uv build && uv publish.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rink-0.2.1.tar.gz (15.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rink-0.2.1-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

File details

Details for the file rink-0.2.1.tar.gz.

File metadata

  • Download URL: rink-0.2.1.tar.gz
  • Upload date:
  • Size: 15.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.14

File hashes

Hashes for rink-0.2.1.tar.gz
Algorithm Hash digest
SHA256 52e217f76b7e07e57b6025dfdeaef77e0500174df44b5edb263007cc60823fda
MD5 eda5b872e9a267b0d4d8e620febeff4d
BLAKE2b-256 82b96e1dfbb7f02c18fc81ba019413972500bfbeaa789c6dc017447614f9fb40

See more details on using hashes here.

File details

Details for the file rink-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: rink-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 18.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.14

File hashes

Hashes for rink-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d32b8c9dd90d54b8e6166120453381a6237e1bccea13c4cd170871c8ee532352
MD5 07565783eb7bd98d68351b72e1784daf
BLAKE2b-256 c5638b5afce8362707cd969b8ed9ede4f70f64c21975f4225bac8c0c38f5d102

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page