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
- Account ID — Cloudflare dashboard → R2 → copy the Account ID.
- Create a bucket — dashboard → R2 → Create bucket, or
wrangler r2 bucket create <name>. - API token — dashboard → R2 → Manage R2 API Tokens → Create API Token with Object Read & Write. Copy the Access Key ID and Secret Access Key.
- (public links only) bucket → Settings → enable Public Development URL, and
copy the
pub-xxxx.r2.devdomain.
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 linkpermanent— shared via a public link (never expires)untracked— object exists in the bucket but wasn't uploaded byrink, 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 rink → Settings → Publishing → 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rink-0.2.0.tar.gz.
File metadata
- Download URL: rink-0.2.0.tar.gz
- Upload date:
- Size: 15.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
86dc3a25641542ad2b10cb046b6ef9bd50433de616bd18deef9c23156e60c1b3
|
|
| MD5 |
d938ad29dc425149d251accb4b587a98
|
|
| BLAKE2b-256 |
0d1bf7bcd6272b87b22aa0bc3f3216680cb41acc878ec3ecf5f5efb572392cbb
|
File details
Details for the file rink-0.2.0-py3-none-any.whl.
File metadata
- Download URL: rink-0.2.0-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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d29648263bf57c951db75d1355957b1e6f235e5b88b0ab0802b0911a2af554f3
|
|
| MD5 |
7fc119168d077b0ed4b16f7324781014
|
|
| BLAKE2b-256 |
ad3771d31d1059438b4ebbe6f575cf5ee166f395e65f41db994dcf9651c1afd7
|