mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-04-20 08:30:29 +00:00
did:plc Identity Support (pkg/hold/pds/did.go, pkg/hold/config.go, pkg/hold/server.go) The big feature — holds can now use did:plc identities instead of only did:web. This adds: - LoadOrCreateDID() — resolves hold DID by priority: config DID > did.txt on disk > create new - CreatePLCIdentity() — builds a genesis operation, signs with rotation key, submits to PLC directory - EnsurePLCCurrent() — on boot, compares local signing key + URL against PLC directory and auto-updates if they've drifted (requires rotation key) - New config fields: did_method (web/plc), did, plc_directory_url, rotation_key_path - GenerateDIDDocument() now uses the stored DID instead of always deriving did:web from URL - NewHoldServer wired up to call LoadOrCreateDID instead of GenerateDIDFromURL CAR Export/Import (pkg/hold/pds/export.go, pkg/hold/pds/import.go, cmd/hold/repo.go) New CLI subcommands for repo backup/restore: - atcr-hold repo export — streams the hold's repo as a CAR file to stdout - atcr-hold repo import <file>... — reads CAR files, upserts all records in a single atomic commit. Uses a bulkImportRecords method that opens a delta session, checks each record for create vs update, commits once, and fires repo events. - openHoldPDS() helper to spin up a HoldPDS from config for offline CLI operations Admin UI Fixes (pkg/hold/admin/) - Logout changed from GET to POST — nav template now uses a <form method=POST> instead of an <a> link (prevents CSRF on logout) - Removed return_to parameter from login flow — simplified redirect logic, auth middleware now redirects to /admin/auth/login without query params Config/Deploy - config-hold.example.yaml and deploy/upcloud/configs/hold.yaml.tmpl updated with the four new did:plc config fields - go.mod / go.sum — added github.com/did-method-plc/go-didplc dependency
191 lines
7.8 KiB
Markdown
191 lines
7.8 KiB
Markdown
# ATCR Hold Service
|
|
|
|
Hold Service is the BYOS (Bring Your Own Storage) blob storage backend for ATCR. It stores container image layers in your own S3-compatible storage (AWS S3, Storj, Minio, UpCloud, etc.) and generates presigned URLs so clients transfer data directly to/from S3. Each hold runs an embedded ATProto PDS with its own DID, repository, and crew-based access control.
|
|
|
|
Hold Service is one component of the ATCR ecosystem:
|
|
|
|
1. **[AppView](https://atcr.io/r/evan.jarrett.net/atcr-appview)** — Registry API + web interface
|
|
2. **Hold Service** (this component) — Storage backend with embedded PDS
|
|
3. **Credential Helper** — Client-side tool for ATProto OAuth authentication
|
|
|
|
```
|
|
Docker Client --> AppView (resolves identity) --> User's PDS (stores manifest)
|
|
|
|
|
Hold Service (generates presigned URL)
|
|
|
|
|
S3/Storj/etc. (client uploads/downloads directly)
|
|
```
|
|
|
|
Manifests (small JSON metadata) live in users' ATProto PDS. Blobs (large binary layers) live in hold services. AppView orchestrates the routing.
|
|
|
|
## When to Run Your Own Hold
|
|
|
|
Most users can push to the default hold at **https://hold01.atcr.io** — you don't need to run your own.
|
|
|
|
Run your own hold if you want to:
|
|
- Control where your container layer data is stored (own S3 bucket, geographic region)
|
|
- Manage access for a team or organization via crew membership
|
|
- Run a shared hold for a community or project
|
|
- Use a CDN pull zone for faster downloads
|
|
|
|
**Prerequisites:** S3-compatible storage with a bucket already created, and a domain with TLS for production.
|
|
|
|
## Quick Start
|
|
|
|
### 1. Generate Configuration
|
|
|
|
```bash
|
|
# Build the hold binary
|
|
go build -o bin/atcr-hold ./cmd/hold
|
|
|
|
# Generate a fully-commented config file with all defaults
|
|
./bin/atcr-hold config init config-hold.yaml
|
|
```
|
|
|
|
Or generate config from Docker without building locally:
|
|
|
|
```bash
|
|
docker run --rm -i $(docker build -q -f Dockerfile.hold .) config init > config-hold.yaml
|
|
```
|
|
|
|
The generated file documents every option with inline comments. Edit only what you need.
|
|
|
|
### 2. Minimal Configuration
|
|
|
|
Only three things need to be set — everything else has sensible defaults:
|
|
|
|
```yaml
|
|
storage:
|
|
access_key: "YOUR_S3_ACCESS_KEY"
|
|
secret_key: "YOUR_S3_SECRET_KEY"
|
|
bucket: "your-bucket-name"
|
|
endpoint: "https://gateway.storjshare.io" # omit for AWS S3
|
|
|
|
server:
|
|
public_url: "https://hold.example.com"
|
|
|
|
registration:
|
|
owner_did: "did:plc:your-did-here"
|
|
```
|
|
|
|
- **`server.public_url`** — Your hold's public HTTPS URL. This becomes the hold's `did:web` identity.
|
|
- **`storage.bucket`** — S3 bucket name (must already exist).
|
|
- **`registration.owner_did`** — Your ATProto DID. Creates you as captain (admin) on first boot. Get yours from: `https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=yourhandle.bsky.social`
|
|
|
|
### 3. Build and Run with Docker
|
|
|
|
```bash
|
|
# Build the image
|
|
docker build -f Dockerfile.hold -t atcr-hold:latest .
|
|
|
|
# Run it
|
|
docker run -d \
|
|
--name atcr-hold \
|
|
-p 8080:8080 \
|
|
-v $(pwd)/config-hold.yaml:/config.yaml:ro \
|
|
-v atcr-hold-data:/var/lib/atcr-hold \
|
|
atcr-hold:latest serve --config /config.yaml
|
|
```
|
|
|
|
- **`/var/lib/atcr-hold`** — Persistent volume for the embedded PDS (carstore database + signing keys). Back this up.
|
|
- **Port 8080** — Default listen address. Put a reverse proxy (Caddy, nginx) in front for TLS.
|
|
- The image is built `FROM scratch` — the binary includes SQLite statically linked.
|
|
- Optional: `docker build --build-arg BILLING_ENABLED=true` to include Stripe billing support.
|
|
|
|
## Configuration
|
|
|
|
Config loads in layers: **defaults → YAML file → environment variables**. Later layers override earlier ones.
|
|
|
|
All YAML fields can be overridden with environment variables using the `HOLD_` prefix and `_` path separators. For example, `server.public_url` becomes `HOLD_SERVER_PUBLIC_URL`.
|
|
|
|
S3 credentials also accept standard AWS environment variable names: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `S3_BUCKET`, `S3_ENDPOINT`.
|
|
|
|
For the complete configuration reference with all options and defaults, see [`config-hold.example.yaml`](../config-hold.example.yaml) or run `atcr-hold config init`.
|
|
|
|
## Access Control
|
|
|
|
| Setting | Who can pull | Who can push |
|
|
|---|---|---|
|
|
| `server.public: true` | Anyone | Captain + crew with `blob:write` |
|
|
| `server.public: false` (default) | Crew with `blob:read` | Captain + crew with `blob:write` |
|
|
| + `registration.allow_all_crew: true` | (per above) | Any authenticated user |
|
|
|
|
The captain (set via `registration.owner_did`) has all permissions implicitly. `blob:write` implies `blob:read`.
|
|
|
|
Authentication uses ATProto service tokens: AppView requests a token from the user's PDS scoped to the hold's DID, then includes it in XRPC requests. The hold validates the token and checks crew membership.
|
|
|
|
See [BYOS.md](BYOS.md) for the full authorization model.
|
|
|
|
## Optional Subsystems
|
|
|
|
| Subsystem | Default | Config key | Notes |
|
|
|---|---|---|---|
|
|
| Admin panel | Enabled | `admin.enabled` | Web UI for crew, settings, and storage management |
|
|
| Quotas | Disabled | `quota.tiers` | Tier-based storage limits (e.g., deckhand=5GB, bosun=50GB) |
|
|
| Garbage collection | Disabled | `gc.enabled` | Nightly cleanup of orphaned blobs and records |
|
|
| Vulnerability scanner | Disabled | `scanner.secret` | Requires separate scanner service; see [SBOM_SCANNING.md](SBOM_SCANNING.md) |
|
|
| Billing (Stripe) | Disabled | Build flag + env | Build with `--build-arg BILLING_ENABLED=true`; see [BILLING.md](BILLING.md) |
|
|
| Bluesky posts | Disabled | `registration.enable_bluesky_posts` | Posts push notifications from hold's identity |
|
|
|
|
## Hold Identity
|
|
|
|
**did:web (default)** — Derived from `server.public_url` with zero setup. `https://hold.example.com` becomes `did:web:hold.example.com`. The DID document is served at `/.well-known/did.json`. Tied to domain ownership — if you lose the domain, you lose the identity.
|
|
|
|
**did:plc (portable)** — Set `database.did_method: plc` in config. Registered with plc.directory. Survives domain changes. Requires a rotation key (auto-generated at `{database.path}/rotation.key`). Use `database.did` to adopt an existing DID for recovery or migration.
|
|
|
|
## Verification
|
|
|
|
After starting your hold, verify it's working:
|
|
|
|
```bash
|
|
# Health check — should return {"version":"..."}
|
|
curl https://hold.example.com/xrpc/_health
|
|
|
|
# DID document — should return valid JSON with service endpoints
|
|
curl https://hold.example.com/.well-known/did.json
|
|
|
|
# Captain record — should show your owner DID
|
|
curl "https://hold.example.com/xrpc/com.atproto.repo.listRecords?repo=HOLD_DID&collection=io.atcr.hold.captain"
|
|
|
|
# Crew records
|
|
curl "https://hold.example.com/xrpc/com.atproto.repo.listRecords?repo=HOLD_DID&collection=io.atcr.hold.crew"
|
|
```
|
|
|
|
Replace `HOLD_DID` with your hold's DID (from the `/.well-known/did.json` response).
|
|
|
|
## Docker Compose
|
|
|
|
```yaml
|
|
services:
|
|
atcr-hold:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.hold
|
|
command: ["serve", "--config", "/config.yaml"]
|
|
volumes:
|
|
- ./config-hold.yaml:/config.yaml:ro
|
|
- atcr-hold-data:/var/lib/atcr-hold
|
|
ports:
|
|
- "8080:8080"
|
|
healthcheck:
|
|
test: ["CMD", "/healthcheck", "http://localhost:8080/xrpc/_health"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
volumes:
|
|
atcr-hold-data:
|
|
```
|
|
|
|
For production with TLS termination, see [`deploy/docker-compose.prod.yml`](../deploy/docker-compose.prod.yml) which includes a Caddy reverse proxy.
|
|
|
|
## Further Reading
|
|
|
|
- [`config-hold.example.yaml`](../config-hold.example.yaml) — Complete configuration reference with inline comments
|
|
- [BYOS.md](BYOS.md) — Bring Your Own Storage architecture and authorization model
|
|
- [HOLD_XRPC_ENDPOINTS.md](HOLD_XRPC_ENDPOINTS.md) — XRPC endpoint reference
|
|
- [BILLING.md](BILLING.md) — Stripe billing integration
|
|
- [QUOTAS.md](QUOTAS.md) — Quota management
|
|
- [SBOM_SCANNING.md](SBOM_SCANNING.md) — Vulnerability scanning
|