Evan JarrettandClaude Opus 5 5f74299bd7 test/e2e: exercise the stale-preview refusal and the real GC sweep
Three scripts, split by what they cost to run.

batch07-stale-preview.mjs stages a preview and waits out the 30-minute
maxPreviewAgeForDelete constant. batch07-stale-click.mjs is the resumable
half: it re-renders whatever preview the hold already holds and clicks delete
on it. GET /admin/api/gc/status re-renders lastPreview WITHOUT touching
lastPreviewAt, so showing an old preview does not reset its age — which is
what makes a failed run cost seconds instead of another 31 minutes.

Result against the dev hold: "preview is 34m0s old (limit 30m0s) — run Scan
again before deleting" rendered through the progress-to-error fragment chain,
with all 387 records still there afterwards. That chain is the point; the
refusal logic itself already has a Go test, but a refusal that renders as
nothing is indistinguishable from "there was nothing to delete".

batch07-sweep.mjs then runs the destructive path for real: 387 records
deleted of 387 staged, orphaned count to zero, referenced blobs unchanged at
15. Safe only against the dev hold on Storj; production is Bunny + UpCloud
and is not reachable from here.

page.on('dialog') did not reliably intercept hx-confirm on this page, and an
unaccepted native dialog blocks every later evaluate() and innerText(), so
the script hangs rather than fails — the worst failure mode for an unattended
check. Both scripts now strip the hx-confirm attribute before clicking. The
confirm is not what is under test.

Two findings worth carrying, neither introduced by this range:

  * deleteOrphanedBlobs is still unexercised. The bucket holds 19 objects,
    of which 8 are past the 7-day blob grace, and none are unreferenced — so
    there is nothing for it to collect. More pushes cannot help: fresh blobs
    are inside the grace window by definition.

  * Storage accounting is derived from layer records, so this sweep moved the
    dashboard from 1.3 GB to 1.1 KB while the bucket held 147 MB throughout.
    It was overstating by ~9x before (records for blobs held by another hold)
    and understates now (referenced blobs with no layer records). Quotas and
    billing read the same number. Belongs to batch 12.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 16:34:25 -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-05-09 21:21:20 -05:00
2026-05-16 17:58:01 -05:00
2026-05-11 19:53:13 -05:00
2026-06-14 04:02:43 +03:00
2025-11-02 22:11:19 -06:00
2026-04-23 10:38:55 -05: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 XRPC multipart upload (large binaries, 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
134 MiB
Languages
Go 86.5%
HTML 7.3%
JavaScript 3.6%
CSS 1.1%
Shell 1%
Other 0.5%