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

more linting fixes

evan.jarrett.net 9eb69e2e dd79b8a0

verified
+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