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

Configure Feed

Select the types of activity you want to include in your feed.

more linting fixes

+110 -23
+26
.golangci.yml
··· 1 + # golangci-lint configuration for ATCR 2 + # See: https://golangci-lint.run/usage/configuration/ 3 + version: "2" 4 + linters: 5 + settings: 6 + staticcheck: 7 + checks: 8 + - "all" 9 + - "-SA1019" # Ignore deprecated package warnings for github.com/ipfs/go-ipfs-blockstore 10 + # Cannot upgrade to github.com/ipfs/boxo/blockstore due to opentelemetry 11 + # dependency conflicts with distribution/distribution 12 + errcheck: 13 + exclude-functions: 14 + - (github.com/distribution/distribution/v3/registry/storage/driver.FileWriter).Cancel 15 + - (github.com/distribution/distribution/v3.BlobWriter).Cancel 16 + - (*database/sql.Tx).Rollback 17 + - (*database/sql.Rows).Close 18 + - (*net/http.Server).Shutdown 19 + 20 + exclusions: 21 + presets: 22 + - std-error-handling 23 + formatters: 24 + enable: 25 + - gofmt 26 + - goimports
+5
pkg/appview/config.go
··· 1 + // Package appview implements the ATCR AppView component, which serves as the main 2 + // OCI Distribution API server. It resolves identities (handle/DID to PDS endpoint), 3 + // routes manifests to user's PDS, routes blobs to hold services, validates OAuth tokens, 4 + // and issues registry JWTs. This package provides environment-based configuration, 5 + // middleware registration, and HTTP server setup for the AppView service. 1 6 package appview 2 7 3 8 import (
+4
pkg/appview/db/schema.go
··· 1 + // Package db provides the database layer for the AppView web UI, including 2 + // SQLite schema initialization, migrations, and query functions for OAuth 3 + // sessions, device flows, repository metadata, stars, pull counts, and 4 + // user profiles. 1 5 package db 2 6 3 7 import (
+3
pkg/appview/handlers/home.go
··· 1 + // Package handlers provides HTTP handlers for the AppView web UI, including 2 + // home page, repository browsing, search, user authentication, settings, 3 + // device management, and API endpoints for the web interface. 1 4 package handlers 2 5 3 6 import (
+3
pkg/appview/holdhealth/checker.go
··· 1 + // Package holdhealth provides health checking for hold service endpoints. 2 + // It periodically checks hold availability and caches health status with 3 + // configurable TTL to avoid excessive health check requests. 1 4 package holdhealth 2 5 3 6 import (
+3
pkg/appview/holdhealth/checker_test.go
··· 131 131 status := checker.GetStatus(context.Background(), endpoint) 132 132 if status == nil { 133 133 t.Fatal("GetStatus returned nil") 134 + return 134 135 } 135 136 136 137 if !status.Reachable { ··· 155 156 status := checker.GetStatus(context.Background(), server.URL) 156 157 if status == nil { 157 158 t.Fatal("GetStatus returned nil") 159 + return 158 160 } 159 161 160 162 if !status.Reachable { ··· 191 193 status := checker.GetCachedStatus(endpoint) 192 194 if status == nil { 193 195 t.Fatal("Status not found in cache") 196 + return 194 197 } 195 198 196 199 if !status.Reachable {
+3
pkg/appview/jetstream/worker.go
··· 1 + // Package jetstream provides an ATProto Jetstream consumer for real-time updates. 2 + // It connects to the Bluesky Jetstream WebSocket, processes repository events, 3 + // indexes manifests and tags, and populates the AppView database for the web UI. 1 4 package jetstream 2 5 3 6 import (
+4
pkg/appview/licenses/licenses.go
··· 1 + // Package licenses provides SPDX license validation and parsing for container 2 + // image annotations. It embeds the official SPDX license list and provides 3 + // functions to look up license identifiers, validate them, and parse 4 + // multi-license strings with fuzzy matching support. 1 5 package licenses 2 6 3 7 //go:generate curl -fsSL -o spdx-licenses.json https://spdx.org/licenses/licenses.json
+4
pkg/appview/middleware/auth.go
··· 1 + // Package middleware provides HTTP middleware for AppView, including 2 + // authentication (session-based for web UI, token-based for registry), 3 + // identity resolution (handle/DID to PDS endpoint), and hold discovery 4 + // for routing blobs to storage endpoints. 1 5 package middleware 2 6 3 7 import (
+4
pkg/appview/readme/cache.go
··· 1 + // Package readme provides README fetching, rendering, and caching functionality 2 + // for container repositories. It fetches markdown content from URLs, renders it 3 + // to sanitized HTML using GitHub-flavored markdown, and caches the results in 4 + // a database with configurable TTL. 1 5 package readme 2 6 3 7 import (
+1 -1
pkg/appview/storage/proxy_blob_store.go
··· 451 451 return nil, err 452 452 } 453 453 454 - url := fmt.Sprintf("%s%s", p.holdURL, atproto.HoldGetPartUploadUrl) 454 + url := fmt.Sprintf("%s%s", p.holdURL, atproto.HoldGetPartUploadURL) 455 455 req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) 456 456 if err != nil { 457 457 return nil, err
+1 -1
pkg/appview/storage/proxy_blob_store_test.go
··· 551 551 _, err := store.getPartUploadInfo(context.Background(), "sha256:test", "upload-123", 1) 552 552 return err 553 553 }, 554 - expectedPath: atproto.HoldGetPartUploadUrl, 554 + expectedPath: atproto.HoldGetPartUploadURL, 555 555 }, 556 556 { 557 557 name: "completeMultipartUpload",
+4
pkg/appview/storage/routing_repository.go
··· 1 + // Package storage implements the storage routing layer for AppView. 2 + // It routes manifests to ATProto PDS (as io.atcr.manifest records) and 3 + // blobs to hold services via XRPC, with hold DID caching for efficient pulls. 4 + // All storage operations are proxied - AppView stores nothing locally. 1 5 package storage 2 6 3 7 import (
+3 -3
pkg/atproto/endpoints.go
··· 1 - // Package xrpc provides constants for XRPC endpoint paths used throughout ATCR. 1 + // Package atproto provides constants for XRPC endpoint paths used throughout ATCR. 2 2 // 3 3 // This package serves as a single source of truth for all XRPC endpoint URLs, 4 4 // preventing typos and making refactoring easier. All endpoint paths follow the ··· 15 15 // Response: {"uploadId": "..."} 16 16 HoldInitiateUpload = "/xrpc/io.atcr.hold.initiateUpload" 17 17 18 - // HoldGetPartUploadUrl gets a presigned URL or endpoint info for uploading a specific part. 18 + // HoldGetPartUploadURL gets a presigned URL or endpoint info for uploading a specific part. 19 19 // Method: POST 20 20 // Request: {"uploadId": "...", "partNumber": 1} 21 21 // Response: {"url": "...", "method": "PUT", "headers": {...}} 22 - HoldGetPartUploadUrl = "/xrpc/io.atcr.hold.getPartUploadUrl" 22 + HoldGetPartUploadURL = "/xrpc/io.atcr.hold.getPartUploadUrl" 23 23 24 24 // HoldUploadPart handles direct buffered part uploads (alternative to presigned URLs). 25 25 // Method: PUT
+1 -1
pkg/atproto/profile.go
··· 9 9 "time" 10 10 ) 11 11 12 - // Profile record key is always "self" per lexicon 12 + // ProfileRKey is always "self" per lexicon 13 13 const ProfileRKey = "self" 14 14 15 15 // Global map to track in-flight profile migrations (DID -> true)
+4
pkg/auth/oauth/client.go
··· 1 + // Package oauth provides OAuth client and flow implementation for ATCR. 2 + // It wraps indigo's OAuth library with ATCR-specific configuration, 3 + // including default scopes, client metadata, token refreshing, and 4 + // interactive browser-based authentication flows. 1 5 package oauth 2 6 3 7 import (
+5 -1
pkg/auth/session.go
··· 1 + // Package auth provides authentication and authorization for ATCR, including 2 + // ATProto session validation, hold authorization (captain/crew membership), 3 + // scope parsing, and token caching for OAuth and service tokens. 1 4 package auth 2 5 3 6 import ( 4 - "atcr.io/pkg/atproto" 5 7 "bytes" 6 8 "context" 7 9 "crypto/sha256" ··· 12 14 "net/http" 13 15 "sync" 14 16 "time" 17 + 18 + "atcr.io/pkg/atproto" 15 19 16 20 "github.com/bluesky-social/indigo/atproto/identity" 17 21 "github.com/bluesky-social/indigo/atproto/syntax"
+4
pkg/auth/token/cache.go
··· 1 + // Package token provides service token caching and management for AppView. 2 + // Service tokens are JWTs issued by a user's PDS to authorize AppView to 3 + // act on their behalf when communicating with hold services. Tokens are 4 + // cached with automatic expiry parsing and 10-second safety margins. 1 5 package token 2 6 3 7 import (
+5
pkg/hold/config.go
··· 1 + // Package hold implements the ATCR hold service, which provides BYOS 2 + // (Bring Your Own Storage) functionality. It includes an embedded PDS for 3 + // storing captain and crew records, generates presigned URLs for blob storage, 4 + // and handles authorization based on crew membership. Configuration is loaded 5 + // entirely from environment variables. 1 6 package hold 2 7 3 8 import (
+3
pkg/hold/oci/http_helpers.go
··· 1 + // Package oci provides HTTP helpers for OCI registry endpoints in the hold service. 2 + // It includes utilities for JSON encoding/decoding of request/response bodies 3 + // and standardized error responses for XRPC endpoints. 1 4 package oci 2 5 3 6 import (
+1 -1
pkg/hold/oci/multipart.go
··· 26 26 Buffered 27 27 ) 28 28 29 - // CompletedPart represents an uploaded part with its ETag 29 + // PartInfo represents an uploaded part with its ETag 30 30 type PartInfo struct { 31 31 PartNumber int `json:"part_number"` 32 32 ETag string `json:"etag"`
+3 -3
pkg/hold/oci/xrpc.go
··· 44 44 r.Use(h.requireBlobWriteAccess) 45 45 46 46 r.Post(atproto.HoldInitiateUpload, h.HandleInitiateUpload) 47 - r.Post(atproto.HoldGetPartUploadUrl, h.HandleGetPartUploadUrl) 47 + r.Post(atproto.HoldGetPartUploadURL, h.HandleGetPartUploadURL) 48 48 r.Put(atproto.HoldUploadPart, h.HandleUploadPart) 49 49 r.Post(atproto.HoldCompleteUpload, h.HandleCompleteUpload) 50 50 r.Post(atproto.HoldAbortUpload, h.HandleAbortUpload) ··· 80 80 }) 81 81 } 82 82 83 - // HandleGetPartUploadUrl returns a presigned URL or endpoint info for uploading a part 83 + // HandleGetPartUploadURL returns a presigned URL or endpoint info for uploading a part 84 84 // Replaces the old "action: part" pattern 85 - func (h *XRPCHandler) HandleGetPartUploadUrl(w http.ResponseWriter, r *http.Request) { 85 + func (h *XRPCHandler) HandleGetPartUploadURL(w http.ResponseWriter, r *http.Request) { 86 86 var req struct { 87 87 UploadID string `json:"uploadId"` 88 88 PartNumber int `json:"partNumber"`
+6 -6
pkg/hold/oci/xrpc_test.go
··· 218 218 uploadID := initResp["uploadId"].(string) 219 219 220 220 // Now get part upload URL 221 - req := makeJSONRequest("POST", atproto.HoldGetPartUploadUrl, map[string]any{ 221 + req := makeJSONRequest("POST", atproto.HoldGetPartUploadURL, map[string]any{ 222 222 "uploadId": uploadID, 223 223 "partNumber": 1, 224 224 }) 225 225 addMockAuth(req) 226 226 227 227 w := httptest.NewRecorder() 228 - handler.HandleGetPartUploadUrl(w, req) 228 + handler.HandleGetPartUploadURL(w, req) 229 229 230 230 if w.Code != http.StatusOK { 231 231 t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String()) ··· 246 246 func TestHandleGetPartUploadUrl_InvalidSession(t *testing.T) { 247 247 handler, _ := setupTestOCIHandler(t) 248 248 249 - req := makeJSONRequest("POST", atproto.HoldGetPartUploadUrl, map[string]any{ 249 + req := makeJSONRequest("POST", atproto.HoldGetPartUploadURL, map[string]any{ 250 250 "uploadId": "invalid-upload-id", 251 251 "partNumber": 1, 252 252 }) 253 253 addMockAuth(req) 254 254 255 255 w := httptest.NewRecorder() 256 - handler.HandleGetPartUploadUrl(w, req) 256 + handler.HandleGetPartUploadURL(w, req) 257 257 258 258 if w.Code != http.StatusInternalServerError { 259 259 t.Errorf("Expected status 500, got %d", w.Code) ··· 274 274 275 275 for _, tt := range tests { 276 276 t.Run(tt.name, func(t *testing.T) { 277 - req := makeJSONRequest("POST", atproto.HoldGetPartUploadUrl, tt.body) 277 + req := makeJSONRequest("POST", atproto.HoldGetPartUploadURL, tt.body) 278 278 addMockAuth(req) 279 279 280 280 w := httptest.NewRecorder() 281 - handler.HandleGetPartUploadUrl(w, req) 281 + handler.HandleGetPartUploadURL(w, req) 282 282 283 283 if w.Code != http.StatusBadRequest { 284 284 t.Errorf("Expected status 400, got %d", w.Code)
+1 -1
pkg/hold/pds/repomgr.go
··· 1250 1250 return rm.cs.WipeUserData(ctx, uid) 1251 1251 } 1252 1252 1253 - // technically identical to TakeDownRepo, for now 1253 + // ResetRepo is technically identical to TakeDownRepo, for now 1254 1254 func (rm *RepoManager) ResetRepo(ctx context.Context, uid models.Uid) error { 1255 1255 unlock := rm.lockUser(ctx, uid) 1256 1256 defer unlock()
+1 -1
pkg/hold/pds/xrpc.go
··· 1223 1223 json.NewEncoder(w).Encode(response) 1224 1224 } 1225 1225 1226 - // getPresignedURL generates a presigned URL for GET, HEAD, or PUT operations 1226 + // GetPresignedURL generates a presigned URL for GET, HEAD, or PUT operations 1227 1227 // Distinguishes between ATProto blobs (per-DID) and OCI blobs (content-addressed) 1228 1228 func (h *XRPCHandler) GetPresignedURL(ctx context.Context, operation string, digest string, did string) (string, error) { 1229 1229 var path string
+8 -4
pkg/s3/types.go
··· 1 + // Package s3 provides S3 client initialization and presigned URL generation 2 + // for hold services. It supports S3, Storj, and Minio storage backends, 3 + // with fallback to buffered proxy mode when presigned URLs are unavailable. 1 4 package s3 2 5 3 6 import ( 4 7 "fmt" 8 + "log" 9 + "strings" 10 + 5 11 "github.com/aws/aws-sdk-go/aws" 6 12 "github.com/aws/aws-sdk-go/aws/credentials" 7 13 "github.com/aws/aws-sdk-go/aws/session" 8 14 "github.com/aws/aws-sdk-go/service/s3" 9 - "log" 10 - "strings" 11 15 ) 12 16 13 17 type S3Service struct { ··· 16 20 PathPrefix string // S3 path prefix (if any) 17 21 } 18 22 19 - // initializes the S3 client for presigned URL generation 23 + // NewS3Service initializes the S3 client for presigned URL generation 20 24 // Returns nil error if S3 client is successfully initialized 21 25 // Returns error if storage is not S3 or if initialization fails (service will fall back to proxy mode) 22 26 func NewS3Service(params map[string]any, disablePresigned bool, storageType string) (*S3Service, error) { ··· 85 89 }, nil 86 90 } 87 91 88 - // blobPath converts a digest (e.g., "sha256:abc123...") or temp path to a storage path 92 + // BlobPath converts a digest (e.g., "sha256:abc123...") or temp path to a storage path 89 93 // Distribution stores blobs as: /docker/registry/v2/blobs/{algorithm}/{xx}/{hash}/data 90 94 // where xx is the first 2 characters of the hash for directory sharding 91 95 // NOTE: Path must start with / for filesystem driver