~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
137 lines · 4.5 KB Raw History
# Deploy: Cloudflare Workers

The Worker keeps the repo and the shared account data in a Durable Object with
SQLite storage, and blobs in R2.

## Prerequisites

- A Cloudflare account, signed in with `npx wrangler login`

The Workers Free plan carries Durable Objects with the SQLite storage backend,
which is the backend `examples/cloudflare/wrangler.toml` declares through
`new_sqlite_classes`. R2 has a free tier of its own. Check the current limits for
each against your traffic.

Wrangler is a dev dependency of this repository, so every command below uses
`npx`. Drop the prefix if you installed it globally.

## Steps

1. **Create the R2 bucket**

   ```bash
   npx wrangler r2 bucket create pds-blobs
   ```

2. **Set the secrets**

   ```bash
   npx wrangler secret put PDS_PASSWORD
   npx wrangler secret put JWT_SECRET
   ```

3. **Deploy**

   ```bash
   cd examples/cloudflare
   npx wrangler deploy
   ```

4. **Register the DID**

   ```bash
   npm run setup -- --pds https://your-worker.workers.dev
   ```

## Permissioned data

Uncomment the `[vars]` block in `examples/cloudflare/wrangler.toml` before you
deploy. See [permissioned data](permissioned-data.md).

## Your own Worker

The package exports the fetch handler and the Durable Object class. Point
`wrangler.toml` at `@pdsjs/cloudflare`, or re-export from your own entrypoint:

```javascript
export { default, PDSDurableObject } from '@pdsjs/cloudflare'
```

Your `wrangler.toml` needs the same bindings as
`examples/cloudflare/wrangler.toml`:

| Binding | Kind | Holds |
|---------|------|-------|
| `PDS` | Durable Object, `PDSDurableObject` | The repo and the account data, in Durable Object SQLite |
| `BLOBS` | R2 bucket | Blobs |
| `BACKUPS` | R2 bucket, optional | Scheduled backups |
| `EMAIL` | Send Email, optional | Account verification mail |

The Durable Object needs the `new_sqlite_classes` migration. Keep `BACKUPS` in a
separate bucket from `BLOBS`. The retention policy deletes freely inside it.

## Email

Account verification mail (confirming an address, authorizing a PLC operation)
goes out through the Email Service `EMAIL` binding:

```toml
[[send_email]]
name = "EMAIL"
```

The From address must use a domain onboarded for Email Sending:

```bash
npx wrangler email sending enable example.com
```

Set `PDS_EMAIL_FROM` to the sender, or leave it out to default to
`no-reply@HOSTNAME`. A workers.dev hostname cannot be onboarded, so a custom
domain is required. With the binding and a sender in place the account page
offers "Email for recovery"; without them it stores an address that stays
unconfirmed. See [configuration](configuration.md#mail).

## Static sites

Set `PDS_EXPERIMENTAL_SITE_DOMAIN` and route the domain's traffic to the Worker. Two zone
routes cover every site, present and future:

```toml
[[routes]]
pattern = "example.com/*"
zone_name = "example.com"

[[routes]]
pattern = "*.example.com/*"
zone_name = "example.com"
```

DNS needs two proxied records in the zone: one for the apex and a wildcard
(`*`, CNAME to the apex works). With both in place, deploying a site record
is the whole release: `blog` is live at `blog.example.com` with no further
configuration. `www` with no `www` site deployed redirects to the apex.

- **Routes only intercept proxied traffic.** A DNS-only (grey cloud) record
  bypasses the Worker silently: the route deploys cleanly, and requests keep
  going to whatever the record points at. Check the proxy status first when a
  routed hostname serves the wrong thing.
- A hostname that must not reach the Worker (a legacy host on the same zone)
  stays out by keeping its record DNS-only.
- Per-site `custom_domain` entries also work, but wrangler only ever attaches
  them: removing one from `wrangler.toml` and redeploying does not detach it.
  Delete them in the dashboard or over the API.
- Two zone features rewrite site HTML in flight: HTML post-processing strips
  the ETag, and Browser Cache TTL raises `max-age`, so a redeploy can look
  stuck for hours. Set Browser Cache TTL to "Respect Existing Headers", or
  scope a Configuration Rule to the site hostnames.
- With the zone's image transformations enabled ("Transform via URL"), the
  `/.blobs/<cid>` route composes with `/cdn-cgi/image/` for resized variants
  of repo images. The blobs are immutable, so transformed variants cache
  indefinitely.

## Operating notes

The Durable Object holds the `subscribeRepos` WebSockets. A hard restart of the
object stops federation, and it stays stopped until those sockets close. Close
them gracefully instead.