Metadata-Version: 2.4
Name: rink
Version: 0.2.1
Summary: Upload files/folders to Cloudflare R2 and get a shareable link
Author-email: Rc <crretsim@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: cli,cloudflare,presigned-url,r2,s3,upload
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Requires-Dist: boto3>=1.34
Requires-Dist: rich>=13.7
Requires-Dist: segno>=1.6
Requires-Dist: tomli>=2.0; python_version < '3.11'
Requires-Dist: typer>=0.12
Description-Content-Type: text/markdown

# 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 Tokens* → *Create 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:

```sh
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):

```sh
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

```sh
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

```sh
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

```sh
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

```sh
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

```sh
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:

```sh
# 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`.
