A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go

add atcr-hold readme

evan.jarrett.net ec90f43d d7e9580a

verified
+405 -1
+2 -1
Dockerfile.hold
··· 40 40 org.opencontainers.image.documentation="https://tangled.org/@evan.jarrett.net/at-container-registry" \ 41 41 org.opencontainers.image.licenses="MIT" \ 42 42 org.opencontainers.image.version="0.1.0" \ 43 - io.atcr.icon="https://imgs.blue/evan.jarrett.net/1TpTOdtS60GdJWBYEqtK22y688jajbQ9a5kbYRFtwuqrkBAE" 43 + io.atcr.icon="https://imgs.blue/evan.jarrett.net/1TpTOdtS60GdJWBYEqtK22y688jajbQ9a5kbYRFtwuqrkBAE" \ 44 + io.atcr.readme="https://tangled.org/@evan.jarrett.net/at-container-registry/raw/main/docs/hold.md" 44 45 45 46 ENTRYPOINT ["/atcr-hold"]
+403
docs/hold.md
··· 1 + # ATCR Hold Service 2 + 3 + > The storage backend component of ATCR (ATProto Container Registry) 4 + 5 + ## Overview 6 + 7 + **Hold Service** is the storage backend component of ATCR. It enables BYOS (Bring Your Own Storage) - users can store their own container image layers in their own S3, Storj, Minio, or filesystem storage. Each hold runs as a full ATProto user with an embedded PDS, exposing both standard ATProto sync endpoints and custom XRPC endpoints for OCI multipart blob uploads. 8 + 9 + ### What Hold Service Does 10 + 11 + Hold Service is the storage layer that: 12 + 13 + - **Bring Your Own Storage (BYOS)** - Store your own container image layers in your own S3, Storj, Minio, or filesystem 14 + - **Embedded ATProto PDS** - Each hold is a full ATProto user with its own DID, repository, and identity 15 + - **Custom XRPC Endpoints** - OCI-compatible multipart upload endpoints (`io.atcr.hold.*`) for blob operations 16 + - **Presigned URL Generation** - Creates time-limited S3 URLs for direct client-to-storage transfers (~99% bandwidth reduction) 17 + - **Crew Management** - Controls access via captain and crew records stored in the hold's embedded PDS 18 + - **Standard ATProto Sync** - Exposes com.atproto.sync.* endpoints for repository synchronization and firehose 19 + - **Multi-Backend Support** - Works with S3, Storj, Minio, filesystem, Azure, GCS via distribution's driver system 20 + - **Bluesky Integration** - Optional: Posts container image push notifications from the hold's identity to Bluesky 21 + 22 + ### The ATCR Ecosystem 23 + 24 + Hold Service is the **storage backend** of a multi-component architecture: 25 + 26 + 1. **[AppView](https://atcr.io/r/evan.jarrett.net/atcr-appview)** - Registry API + web interface 27 + 2. **Hold Service** (this component) - Storage backend with embedded PDS 28 + 3. **Credential Helper** - Client-side tool for ATProto OAuth authentication 29 + 30 + **Data flow:** 31 + ``` 32 + Docker Client → AppView (resolves identity) → User's PDS (stores manifest) 33 + 34 + Hold Service (generates presigned URL) 35 + 36 + S3/Storj/etc. (client uploads/downloads blobs directly) 37 + ``` 38 + 39 + Manifests (small JSON metadata) live in users' ATProto PDS, while blobs (large binary layers) live in hold services. AppView orchestrates the routing, and hold services provide presigned URLs to eliminate bandwidth bottlenecks. 40 + 41 + ## When to Run Your Own Hold 42 + 43 + Most users can push to the default hold at **https://hold01.atcr.io** - you don't need to run your own hold. 44 + 45 + **Run your own hold if you want to:** 46 + - Control where your container layer data is stored (own S3 bucket, Storj, etc.) 47 + - Manage access for a team or organization via crew membership 48 + - Reduce bandwidth costs by using presigned URLs for direct S3 transfers 49 + - Run a shared hold for a community or project 50 + - Maintain data sovereignty (keep blobs in specific geographic regions) 51 + 52 + **Prerequisites:** 53 + - S3-compatible storage (AWS S3, Storj, Minio, UpCloud, etc.) OR filesystem storage 54 + - (Optional) Domain name with SSL/TLS certificates for production 55 + - ATProto DID for hold owner (get from: `https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=yourhandle.bsky.social`) 56 + 57 + ## Quick Start 58 + 59 + ### Using Docker Compose 60 + 61 + The fastest way to run Hold service with S3 storage: 62 + 63 + ```bash 64 + # Clone repository 65 + git clone https://tangled.org/@evan.jarrett.net/at-container-registry 66 + cd atcr 67 + 68 + # Copy and configure environment 69 + cp .env.hold.example .env.hold 70 + # Edit .env.hold - set HOLD_PUBLIC_URL, HOLD_OWNER, S3 credentials (see Configuration below) 71 + 72 + # Start hold service 73 + docker-compose -f docker-compose.hold.yml up -d 74 + 75 + # Verify 76 + curl http://localhost:8080/.well-known/did.json 77 + ``` 78 + 79 + ### Minimal Configuration 80 + 81 + At minimum, you must set: 82 + 83 + ```bash 84 + # Required: Public URL (generates did:web identity) 85 + HOLD_PUBLIC_URL=https://hold.example.com 86 + 87 + # Required: Your ATProto DID (for captain record) 88 + HOLD_OWNER=did:plc:your-did-here 89 + 90 + # Required: Storage driver type 91 + STORAGE_DRIVER=s3 92 + 93 + # Required for S3: Credentials and bucket 94 + AWS_ACCESS_KEY_ID=your-access-key 95 + AWS_SECRET_ACCESS_KEY=your-secret-key 96 + S3_BUCKET=your-bucket-name 97 + 98 + # Recommended: Database directory for embedded PDS 99 + HOLD_DATABASE_DIR=/var/lib/atcr-hold 100 + ``` 101 + 102 + See **Configuration Reference** below for all options. 103 + 104 + ## Configuration Reference 105 + 106 + Hold Service is configured entirely via environment variables. Load them with: 107 + ```bash 108 + source .env.hold 109 + ./bin/atcr-hold 110 + ``` 111 + 112 + Or via Docker Compose (recommended). 113 + 114 + ### Server Configuration 115 + 116 + #### `HOLD_PUBLIC_URL` ⚠️ REQUIRED 117 + - **Default:** None (required) 118 + - **Description:** Public URL of this hold service. Used to generate the hold's did:web identity. The hostname becomes the hold's DID. 119 + - **Format:** `https://hold.example.com` or `http://127.0.0.1:8080` (development) 120 + - **Example:** `https://hold01.atcr.io` → DID is `did:web:hold01.atcr.io` 121 + - **Note:** This URL must be reachable by AppView and Docker clients 122 + 123 + #### `HOLD_SERVER_ADDR` 124 + - **Default:** `:8080` 125 + - **Description:** HTTP listen address for XRPC endpoints 126 + - **Example:** `:8080`, `:9000`, `0.0.0.0:8080` 127 + 128 + #### `HOLD_PUBLIC` 129 + - **Default:** `false` 130 + - **Description:** Allow public blob reads (pulls) without authentication. Writes always require crew membership. 131 + - **Use cases:** 132 + - `true`: Public registry (anyone can pull, authenticated users can push if crew) 133 + - `false`: Private registry (authentication required for both push and pull) 134 + 135 + ### Storage Configuration 136 + 137 + #### `STORAGE_DRIVER` 138 + - **Default:** `s3` 139 + - **Options:** `s3`, `filesystem` 140 + - **Description:** Storage backend type. S3 enables presigned URLs for direct client-to-storage transfers (~99% bandwidth reduction). Filesystem stores blobs locally (development/testing). 141 + 142 + #### S3 Storage (when `STORAGE_DRIVER=s3`) 143 + 144 + ##### `AWS_ACCESS_KEY_ID` ⚠️ REQUIRED for S3 145 + - **Description:** S3 access key ID for authentication 146 + - **Example:** `AKIAIOSFODNN7EXAMPLE` 147 + 148 + ##### `AWS_SECRET_ACCESS_KEY` ⚠️ REQUIRED for S3 149 + - **Description:** S3 secret access key for authentication 150 + - **Example:** `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` 151 + 152 + ##### `AWS_REGION` 153 + - **Default:** `us-east-1` 154 + - **Description:** S3 region 155 + - **AWS regions:** `us-east-1`, `us-west-2`, `eu-west-1`, etc. 156 + - **UpCloud regions:** `us-chi1`, `us-nyc1`, `de-fra1`, `uk-lon1`, `sg-sin1` 157 + 158 + ##### `S3_BUCKET` ⚠️ REQUIRED for S3 159 + - **Description:** S3 bucket name where blobs will be stored 160 + - **Example:** `atcr-blobs`, `my-company-registry-blobs` 161 + - **Note:** Bucket must already exist 162 + 163 + ##### `S3_ENDPOINT` 164 + - **Default:** None (uses AWS S3) 165 + - **Description:** S3-compatible endpoint URL for non-AWS providers 166 + - **Storj:** `https://gateway.storjshare.io` 167 + - **UpCloud:** `https://[bucket-id].upcloudobjects.com` 168 + - **Minio:** `http://minio:9000` 169 + - **Note:** Leave empty for AWS S3 170 + 171 + #### Filesystem Storage (when `STORAGE_DRIVER=filesystem`) 172 + 173 + ##### `STORAGE_ROOT_DIR` 174 + - **Default:** `/var/lib/atcr/hold` 175 + - **Description:** Directory path where blobs will be stored on local filesystem 176 + - **Use case:** Development, testing, or single-server deployments 177 + - **Note:** Presigned URLs are not available with filesystem driver (hold proxies all blob transfers) 178 + 179 + ### Embedded PDS Configuration 180 + 181 + #### `HOLD_DATABASE_DIR` 182 + - **Default:** `/var/lib/atcr-hold` 183 + - **Description:** Directory path for embedded PDS carstore (SQLite database). Carstore creates `db.sqlite3` inside this directory. 184 + - **Note:** This must be a directory path, NOT a file path. If empty, embedded PDS is disabled (not recommended - hold authorization requires PDS). 185 + 186 + #### `HOLD_KEY_PATH` 187 + - **Default:** `{HOLD_DATABASE_DIR}/signing.key` 188 + - **Description:** Path to hold's signing key (secp256k1). Auto-generated on first run if missing. 189 + - **Note:** Keep this secure - it's used to sign ATProto commits in the hold's repository 190 + 191 + ### Access Control 192 + 193 + #### `HOLD_OWNER` 194 + - **Default:** None 195 + - **Description:** Your ATProto DID. Used to create the captain record and add you as the first crew member with admin role. 196 + - **Get your DID:** `https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=yourhandle.bsky.social` 197 + - **Example:** `did:plc:abc123xyz789` 198 + - **Note:** If set, the hold will initialize with your DID as owner on first run 199 + 200 + #### `HOLD_ALLOW_ALL_CREW` 201 + - **Default:** `false` 202 + - **Description:** Allow any authenticated ATCR user to write to this hold (treat all as crew) 203 + - **Security model:** 204 + - `true`: Any authenticated user can push images (useful for shared/community holds) 205 + - `false`: Only hold owner and explicit crew members can push (verified via crew records in hold's PDS) 206 + - **Use cases:** 207 + - Public registry: `HOLD_PUBLIC=true, HOLD_ALLOW_ALL_CREW=true` 208 + - ATProto users only: `HOLD_PUBLIC=false, HOLD_ALLOW_ALL_CREW=true` 209 + - Private hold: `HOLD_PUBLIC=false, HOLD_ALLOW_ALL_CREW=false` (default) 210 + 211 + ### Bluesky Integration 212 + 213 + #### `HOLD_BLUESKY_POSTS_ENABLED` 214 + - **Default:** `false` 215 + - **Description:** Create Bluesky posts when users push container images. Posts include image name, tag, size, and layer count. 216 + - **Note:** Posts are created from the hold's embedded PDS identity (did:web). Requires hold to be crawled by Bluesky relay. 217 + - **Enable relay crawl:** `./deploy/request-crawl.sh hold.example.com` 218 + 219 + #### `HOLD_PROFILE_AVATAR` 220 + - **Default:** `https://imgs.blue/evan.jarrett.net/1TpTOdtS60GdJWBYEqtK22y688jajbQ9a5kbYRFtwuqrkBAE` 221 + - **Description:** URL to download avatar image for hold's Bluesky profile. Downloaded and uploaded as blob during bootstrap. 222 + - **Note:** Avatar is stored in hold's PDS and displayed on Bluesky profile 223 + 224 + ### Advanced Configuration 225 + 226 + #### `TEST_MODE` 227 + - **Default:** `false` 228 + - **Description:** Enable test mode (skips some validations). Do not use in production. 229 + 230 + #### `DISABLE_PRESIGNED_URLS` 231 + - **Default:** `false` 232 + - **Description:** Force proxy mode even with S3 configured (for testing). Disables presigned URL generation and routes all blob transfers through the hold service. 233 + - **Use case:** Testing, debugging, or environments where presigned URLs don't work 234 + 235 + ## XRPC Endpoints 236 + 237 + Hold Service exposes two types of XRPC endpoints: 238 + 239 + ### ATProto Sync Endpoints (Standard) 240 + - `GET /.well-known/did.json` - DID document (did:web resolution) 241 + - `GET /xrpc/com.atproto.sync.getRepo` - Download full repository as CAR file 242 + - `GET /xrpc/com.atproto.sync.getBlob` - Get blob or presigned download URL 243 + - `GET /xrpc/com.atproto.sync.subscribeRepos` - WebSocket firehose for real-time events 244 + - `GET /xrpc/com.atproto.sync.listRepos` - List all repositories (single-user PDS) 245 + - `GET /xrpc/com.atproto.repo.describeRepo` - Repository metadata 246 + - `GET /xrpc/com.atproto.repo.getRecord` - Get record by collection and rkey 247 + - `GET /xrpc/com.atproto.repo.listRecords` - List records in collection 248 + - `POST /xrpc/com.atproto.repo.deleteRecord` - Delete record (owner/crew admin only) 249 + 250 + ### OCI Multipart Upload Endpoints (Custom) 251 + - `POST /xrpc/io.atcr.hold.initiateUpload` - Start multipart upload session 252 + - `POST /xrpc/io.atcr.hold.getPartUploadUrl` - Get presigned URL for uploading a part 253 + - `PUT /xrpc/io.atcr.hold.uploadPart` - Direct buffered part upload (alternative to presigned URLs) 254 + - `POST /xrpc/io.atcr.hold.completeUpload` - Finalize multipart upload 255 + - `POST /xrpc/io.atcr.hold.abortUpload` - Cancel multipart upload 256 + - `POST /xrpc/io.atcr.hold.notifyManifest` - Notify hold of manifest upload (creates layer records, Bluesky posts) 257 + 258 + ## Authorization Model 259 + 260 + Hold Service uses crew membership records in its embedded PDS for access control: 261 + 262 + ### Read Access (Blob Downloads) 263 + 264 + **Public Hold** (`HOLD_PUBLIC=true`): 265 + - Anonymous users: ✅ Allowed 266 + - Authenticated users: ✅ Allowed 267 + 268 + **Private Hold** (`HOLD_PUBLIC=false`): 269 + - Anonymous users: ❌ Forbidden 270 + - Authenticated users with crew membership: ✅ Allowed 271 + - Crew must have `blob:read` permission 272 + 273 + ### Write Access (Blob Uploads) 274 + 275 + Regardless of `HOLD_PUBLIC` setting: 276 + - Hold owner (from captain record): ✅ Allowed 277 + - Crew members with `blob:write` permission: ✅ Allowed 278 + - Non-crew authenticated users: Depends on `HOLD_ALLOW_ALL_CREW` 279 + - `HOLD_ALLOW_ALL_CREW=true`: ✅ Allowed 280 + - `HOLD_ALLOW_ALL_CREW=false`: ❌ Forbidden 281 + 282 + ### Authentication Method 283 + 284 + AppView uses **service tokens** from user's PDS to authenticate with hold service: 285 + 1. AppView calls user's PDS: `com.atproto.server.getServiceAuth` with hold DID 286 + 2. User's PDS returns a service token scoped to the hold DID 287 + 3. AppView includes service token in XRPC requests to hold 288 + 4. Hold validates token and checks crew membership in its embedded PDS 289 + 290 + ## Deployment Scenarios 291 + 292 + ### Personal Hold (Single User) 293 + 294 + Your own storage for your images: 295 + 296 + ```bash 297 + # Hold config 298 + HOLD_PUBLIC_URL=https://hold.alice.com 299 + HOLD_OWNER=did:plc:alice-did 300 + HOLD_PUBLIC=false # Private (only you can pull) 301 + HOLD_ALLOW_ALL_CREW=false # Only you can push 302 + HOLD_DATABASE_DIR=/var/lib/atcr-hold 303 + 304 + # S3 storage 305 + STORAGE_DRIVER=s3 306 + AWS_ACCESS_KEY_ID=your-key 307 + AWS_SECRET_ACCESS_KEY=your-secret 308 + S3_BUCKET=alice-container-registry 309 + S3_ENDPOINT=https://gateway.storjshare.io # Using Storj 310 + ``` 311 + 312 + ### Shared Hold (Team/Organization) 313 + 314 + Shared storage for a team with crew members: 315 + 316 + ```bash 317 + # Hold config 318 + HOLD_PUBLIC_URL=https://hold.acme.corp 319 + HOLD_OWNER=did:plc:acme-org-did 320 + HOLD_PUBLIC=false # Private reads (crew only) 321 + HOLD_ALLOW_ALL_CREW=false # Explicit crew membership required 322 + HOLD_DATABASE_DIR=/var/lib/atcr-hold 323 + 324 + # S3 storage 325 + STORAGE_DRIVER=s3 326 + AWS_ACCESS_KEY_ID=your-key 327 + AWS_SECRET_ACCESS_KEY=your-secret 328 + S3_BUCKET=acme-registry-blobs 329 + ``` 330 + 331 + Then add crew members via XRPC or hold PDS records. 332 + 333 + ### Public Hold (Community Registry) 334 + 335 + Open storage allowing anyone to push and pull: 336 + 337 + ```bash 338 + # Hold config 339 + HOLD_PUBLIC_URL=https://hold.community.io 340 + HOLD_OWNER=did:plc:community-did 341 + HOLD_PUBLIC=true # Public reads (anyone can pull) 342 + HOLD_ALLOW_ALL_CREW=true # Any authenticated user can push 343 + HOLD_DATABASE_DIR=/var/lib/atcr-hold 344 + 345 + # S3 storage 346 + STORAGE_DRIVER=s3 347 + AWS_ACCESS_KEY_ID=your-key 348 + AWS_SECRET_ACCESS_KEY=your-secret 349 + S3_BUCKET=community-registry-blobs 350 + ``` 351 + 352 + ### Development/Testing 353 + 354 + Local filesystem storage for testing: 355 + 356 + ```bash 357 + # Hold config 358 + HOLD_PUBLIC_URL=http://127.0.0.1:8080 359 + HOLD_OWNER=did:plc:your-test-did 360 + HOLD_PUBLIC=true 361 + HOLD_ALLOW_ALL_CREW=true 362 + HOLD_DATABASE_DIR=/tmp/atcr-hold 363 + 364 + # Filesystem storage 365 + STORAGE_DRIVER=filesystem 366 + STORAGE_ROOT_DIR=/tmp/atcr-hold-blobs 367 + ``` 368 + 369 + ## Production Deployment 370 + 371 + For production deployments with: 372 + - SSL/TLS certificates 373 + - S3 storage with presigned URLs 374 + - Proper access control 375 + - Systemd service files 376 + - Monitoring 377 + 378 + See **[deploy/README.md](https://tangled.org/@evan.jarrett.net/at-container-registry/blob/main/deploy/README.md)** for comprehensive production deployment guide. 379 + 380 + ### Quick Production Checklist 381 + 382 + Before going to production: 383 + 384 + - [ ] Set `HOLD_PUBLIC_URL` to your public HTTPS URL 385 + - [ ] Set `HOLD_OWNER` to your ATProto DID 386 + - [ ] Configure S3 storage (`STORAGE_DRIVER=s3`) 387 + - [ ] Set `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `S3_BUCKET`, `S3_ENDPOINT` 388 + - [ ] Set `HOLD_DATABASE_DIR` to persistent directory 389 + - [ ] Configure `HOLD_PUBLIC` and `HOLD_ALLOW_ALL_CREW` for desired access model 390 + - [ ] Configure SSL/TLS termination (Caddy/nginx/Cloudflare) 391 + - [ ] Verify DID document: `curl https://hold.example.com/.well-known/did.json` 392 + - [ ] Test presigned URLs: Check logs for "presigned URL" messages during push 393 + - [ ] Monitor crew membership: `curl https://hold.example.com/xrpc/com.atproto.repo.listRecords?repo={holdDID}&collection=io.atcr.hold.crew` 394 + - [ ] (Optional) Enable Bluesky posts: `HOLD_BLUESKY_POSTS_ENABLED=true` 395 + - [ ] (Optional) Request relay crawl: `./deploy/request-crawl.sh hold.example.com` 396 + 397 + ## Configuration Files Reference 398 + 399 + - **[.env.hold.example](https://tangled.org/@evan.jarrett.net/at-container-registry/blob/main/.env.hold.example)** - All available environment variables with documentation 400 + - **[deploy/.env.prod.template](https://tangled.org/@evan.jarrett.net/at-container-registry/blob/main/deploy/.env.prod.template)** - Production configuration template (includes both AppView and Hold) 401 + - **[deploy/README.md](https://tangled.org/@evan.jarrett.net/at-container-registry/blob/main/deploy/README.md)** - Production deployment guide 402 + - **[AppView Documentation](https://atcr.io/r/evan.jarrett.net/atcr-appview)** - Registry API server setup 403 + - **[BYOS Architecture](https://tangled.org/@evan.jarrett.net/at-container-registry/blob/main/docs/BYOS.md)** - Bring Your Own Storage technical design