···33333434- Generic single-node deployment instructions live in `docs/DEPLOYMENT.md`.
3535- The deployment guide includes a reverse-proxy layout, a sample `systemd` unit, validation commands, and a `createAccount` example for bootstrapping the first user.
3636+- `perlsky` now includes a built-in ATProto OAuth provider surface, so modern third-party clients that use the Bluesky OAuth flow can authenticate directly against your PDS without extra auth-server infrastructure.
3737+- The built-in provider publishes `/.well-known/oauth-protected-resource`, `/.well-known/oauth-authorization-server`, `/oauth/jwks`, `/oauth/par`, `/oauth/authorize`, `/oauth/token`, and `/oauth/revoke` from the same host as the PDS.
3638- If `service_handle_domain` is `example.com`, submitting `handle: "alice"` to `com.atproto.server.createAccount` creates `alice.example.com`.
3739- If `invite_code_required` is enabled, public signup is disabled until a valid invite code is supplied.
3840- `com.atproto.server.createInviteCode` and `com.atproto.server.createInviteCodes` are admin-only by default. Set `self_service_invite_codes` to enable self-service invite minting for authenticated full-access sessions, limited to the caller's own account.
3941- `script/perlsky-admin create-invite` can mint invite codes locally on the server without needing an existing user session.
4042- The invite-only bootstrap flow is documented with copy-pasteable commands in `docs/DEPLOYMENT.md`.
4143- Browser clients such as `bsky.app` can talk to `perlsky` directly because XRPC and DID-document responses include CORS headers and answer OPTIONS preflight requests.
4444+- OAuth clients such as Tangled can also discover and use `perlsky` directly as both the protected resource and authorization server, using PAR, PKCE, `private_key_jwt`, and DPoP as required by the ATProto OAuth profile.
4245- Unknown `app.bsky.*` requests are proxied to `https://api.bsky.app` by default, and unknown `chat.bsky.*` requests are proxied to `https://api.bsky.chat` by default using per-account service-auth JWTs.
4346- Set `bsky_appview_url` / `bsky_appview_did` or `chat_service_url` / `chat_service_did` in your config if you want different upstream services.
4447
+9
docs/DEPLOYMENT.md
···8282- `service_handle_domain`: the suffix used for local handles
8383- `jwt_secret`: required; the server now refuses to start if it is missing or still set to the old `perlsky-dev-secret` fallback
8484- `sentry_dsn`: optional; when set, perlsky reports unhandled XRPC exceptions to Sentry with request context and Perl stack frames
8585+- `base_url` also drives the built-in ATProto OAuth provider metadata and endpoints, so it must be the same public origin that third-party clients will use for login
8586- If you want users like `alice.pds.example.com`, set `service_handle_domain` to `pds.example.com`, not `example.com`.
8687- Public handle resolution for `alice.pds.example.com` also requires wildcard DNS for `*.pds.example.com` and a reverse proxy/TLS setup that will answer those subdomains.
8788- `invite_code_required`: if true, `createAccount` requires a valid invite code
···234235```sh
235236curl https://pds.example.com/_health
236237curl https://pds.example.com/.well-known/did.json
238238+curl https://pds.example.com/.well-known/oauth-protected-resource
239239+curl https://pds.example.com/.well-known/oauth-authorization-server
240240+curl https://pds.example.com/oauth/jwks
237241curl https://pds.example.com/xrpc/com.atproto.server.describeServer
238242curl --resolve alice.pds.example.com:443:SERVER_IP https://alice.pds.example.com/.well-known/atproto-did
239243```
···250254251255- a healthy `_health` response
252256- a `did:web:pds.example.com` DID document
257257+- OAuth protected-resource metadata advertising the same host as the authorization server
258258+- OAuth authorization-server metadata advertising `private_key_jwt`, PAR, PKCE `S256`, DPoP-bound access tokens, and the local `/oauth/*` endpoints
259259+- a JWK set with at least one signing key from `/oauth/jwks`
253260- `describeServer.availableUserDomains` matching `service_handle_domain`
254261- a per-handle `/.well-known/atproto-did` response returning the account DID when queried on the handle host
262262+263263+Modern third-party ATProto OAuth clients should now be able to discover and authenticate directly against your PDS. For example, a client like Tangled will start by fetching `/.well-known/oauth-protected-resource`, follow the advertised authorization-server metadata, submit a pushed authorization request, and then send the browser through `/oauth/authorize`.
255264256265## First Account
257266