Evan JarrettandClaude Opus 5.5 c39606905e appview: a missing OAuth scope is a 403, not a dead session (#30)
A PDS that grants fewer scopes than requested produced a session that
worked until the first write it wasn't allowed, which the PDS answered
with a 403. We classified that 403 as a revoked session, deleted it, and
returned a 500 "unknown error"; Docker's retries then failed with "no
session found". Logging in again got the same partial grant, so the user
looped (#30, an older tranquil PDS that left a scope off its consent
screen).

- Login refuses a partial grant. The callback checks the granted scopes
  cover what was requested and, if not, deletes the new session and shows
  a page listing what's missing. It runs before the old-session cleanup,
  so a refused login leaves a working session alone. "Try again" goes
  back through the login page so return_to (e.g. the device page) holds.
- MissingScopes compares scopes by what they grant, not by spelling: an
  include: expanded or echoed back, collections split or reordered,
  wildcards, transition:generic. Extra grants are fine. It replaces the
  exact-match ScopesMatch at login, on resume, and in the boot sweep, which
  now evicts only sessions missing something.
- A 403 never deletes a session. InsufficientScope comes out of
  isAuthError and IsSessionInvalidError, and isOAuthError no longer treats
  every 403 as dead. PDSes spell this differently (tranquil:
  InsufficientScope, the reference PDS: ScopeMissingError), so nothing
  keys on the name.
- A PDS 403 on a manifest or tag write reaches Docker as DENIED with the
  PDS's own reason. The UI write handlers (star, tag and manifest delete,
  repo avatar and description) answer 403 with the reason too.
- The OAuth error, missing-permissions and success pages render in the
  site layout via an injected PageRenderer; pkg/auth/oauth keeps its
  inline templates as a fallback.

Verified live against a reference PDS with a forced partial grant: login
refused, an existing session kept, a push denied twice on the same
session with the PDS's message, the boot sweep evicting the partial
session, and a full login pushing normally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 20:26:23 -05:00
2026-05-03 14:51:05 -05:00
2026-01-18 17:44:15 -06:00
2026-01-06 23:56:17 -06:00
2026-09-12 11:05:04 -05:00
2026-09-12 11:05:04 -05:00
2026-06-14 04:02:43 +03:00
2025-11-02 22:11:19 -06:00

ATCR - ATProto Container Registry

https://atcr.io

An OCI-compliant container registry that uses the AT Protocol for manifest storage and S3 for blob storage.

What is ATCR?

ATCR integrates container registries with the AT Protocol ecosystem. Container image manifests are stored as ATProto records in your Personal Data Server (PDS), while layers are stored in S3-compatible storage.

Image names use your ATProto identity:

atcr.io/alice.bsky.social/myapp:latest
atcr.io/did:plc:xyz123/myapp:latest

Architecture

Three components:

  1. AppView - Registry API + web UI

    • Serves OCI Distribution API (Docker push/pull)
    • Resolves handles/DIDs to PDS endpoints
    • Routes manifests to user's PDS, blobs to hold services
    • Web interface for browsing/search
  2. Hold Service - Storage service with embedded PDS (optional BYOS)

    • Each hold has a full ATProto PDS for access control (captain + crew records)
    • Identified by did:web (e.g., did:web:hold01.atcr.io)
    • Generates presigned URLs for S3/Storj/Minio/etc.
    • Users can deploy their own storage and control access via crew membership
  3. Credential Helper - Client authentication

    • ATProto OAuth (DPoP handled transparently)
    • Automatic authentication on first push/pull

Storage model:

  • Manifests → ATProto records in user's PDS (small JSON, includes holdDid reference)
  • Blobs → Hold services via presigned S3 uploads, direct PUT for small blobs and XRPC multipart for large ones (stored in S3/etc.)
  • AppView uses service tokens to communicate with holds on behalf of users

Features

  • ✅ OCI-compliant - Works with Docker, containerd, podman
  • ✅ Decentralized - You own your manifest data via your PDS
  • ✅ ATProto OAuth - Secure authentication (DPoP-compliant)
  • ✅ BYOS - Deploy your own storage service
  • ✅ Web UI - Browse, search, star repositories
  • ✅ Multi-backend - S3, Storj, Minio, Azure, GCS, filesystem

Quick Start

Using the Registry

1. Install credential helper:

curl -fsSL https://atcr.io/static/install.sh | bash

2. Configure Docker (add to ~/.docker/config.json):

{
  "credHelpers": {
    "atcr.io": "atcr"
  }
}

3. Push/pull images:

docker tag myapp:latest atcr.io/yourhandle/myapp:latest
docker push atcr.io/yourhandle/myapp:latest  # Authenticates automatically
docker pull atcr.io/yourhandle/myapp:latest

See INSTALLATION.md for detailed installation instructions.

Running Your Own AppView

# Build
go build -o bin/atcr-appview ./cmd/appview

# Generate a config file with all defaults
./bin/atcr-appview config init config-appview.yaml
# Edit config-appview.yaml — set server.default_hold_did at minimum

# Run
./bin/atcr-appview serve --config config-appview.yaml

Using Docker:

docker build -f Dockerfile.appview -t atcr-appview:latest .
docker run -d -p 5000:5000 \
  -v ./config-appview.yaml:/config.yaml:ro \
  -v atcr-data:/var/lib/atcr \
  atcr-appview:latest serve --config /config.yaml

See deploy/README.md for production deployment.

Running Your Own Hold (BYOS Storage)

See docs/hold.md for deploying your own storage backend.

Development

Building from Source

# Build all binaries
go build -o bin/atcr-appview ./cmd/appview
go build -o bin/atcr-hold ./cmd/hold
go build -o bin/docker-credential-atcr ./cmd/credential-helper

# Run tests
go test ./...
go test -race ./...

Project Structure

cmd/
├── appview/           # Registry server + web UI
├── hold/              # Storage service (BYOS)
├── credential-helper/ # Docker credential helper
├── oauth-helper/      # OAuth debug tool
├── healthcheck/       # HTTP health check (for Docker)
├── db-migrate/        # SQLite → libsql migration
├── usage-report/      # Hold storage usage report
├── record-query/      # Query ATProto relay by collection
└── s3-test/           # S3 connectivity test

pkg/
├── appview/
│   ├── db/            # SQLite database (migrations, queries, stores)
│   ├── handlers/      # HTTP handlers (home, repo, search, auth, settings)
│   ├── holdhealth/    # Hold service health checker
│   ├── jetstream/     # ATProto Jetstream consumer
│   ├── middleware/    # Auth & registry middleware
│   ├── ogcard/        # OpenGraph image generation
│   ├── readme/        # Repository README fetcher
│   ├── routes/        # HTTP route registration
│   ├── storage/       # Storage routing (blob proxy, manifest store)
│   ├── public/        # Static assets (JS, CSS, install scripts)
│   └── templates/     # HTML templates
├── atproto/           # ATProto client, records, manifest/tag stores
├── auth/
│   ├── oauth/         # OAuth client, refresher, storage
│   ├── token/         # JWT issuer, validator, claims
│   └── holdlocal/     # Local hold authorization
├── config/            # Config marshaling (commented YAML)
├── hold/
│   ├── admin/         # Admin web UI
│   ├── billing/       # Stripe billing integration
│   ├── db/            # Vendored carstore (go-libsql)
│   ├── gc/            # Garbage collection
│   ├── oci/           # OCI upload endpoints
│   ├── pds/           # Embedded PDS (DID, captain, crew, stats, scans)
│   └── quota/         # Storage quotas
├── logging/           # Structured logging + remote shipping
└── s3/                # S3 client utilities

License

MIT

Contributing

Contributions welcome! Please open an issue or PR.

S
Description
No description provided
Readme
136 MiB
Languages
Go 88.6%
HTML 6.1%
JavaScript 3.1%
CSS 0.9%
Shell 0.7%
Other 0.6%