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

try and use pull_zone

evan.jarrett.net 434a5f1e 07bc924a

verified
+38 -4
+4
config-hold.example.yaml
··· 31 bucket: "" 32 # Custom S3 endpoint for non-AWS providers (e.g. "https://gateway.storjshare.io"). 33 endpoint: "" 34 # HTTP server and identity settings. 35 server: 36 # Listen address, e.g. ":8080" or "0.0.0.0:8080". ··· 39 public_url: "" 40 # Allow unauthenticated blob reads. If false, readers need crew membership. 41 public: false 42 # Use localhost for OAuth redirects during development. 43 test_mode: false 44 # Request crawl from this relay on startup to make the embedded PDS discoverable.
··· 31 bucket: "" 32 # Custom S3 endpoint for non-AWS providers (e.g. "https://gateway.storjshare.io"). 33 endpoint: "" 34 + # CDN pull zone URL for downloads. When set, presigned GET/HEAD URLs use this host instead of the S3 endpoint. Uploads and API calls still use the S3 endpoint. 35 + pull_zone: "" 36 # HTTP server and identity settings. 37 server: 38 # Listen address, e.g. ":8080" or "0.0.0.0:8080". ··· 41 public_url: "" 42 # Allow unauthenticated blob reads. If false, readers need crew membership. 43 public: false 44 + # DID of successor hold for migration. Appview redirects all requests to the successor. 45 + successor: "" 46 # Use localhost for OAuth redirects during development. 47 test_mode: false 48 # Request crawl from this relay on startup to make the embedded PDS discoverable.
+4
deploy/upcloud/configs/hold.yaml.tmpl
··· 13 region: "{{.S3Region}}" 14 bucket: "{{.S3Bucket}}" 15 endpoint: "{{.S3Endpoint}}" 16 server: 17 addr: :8080 18 public_url: "https://{{.HoldDomain}}" 19 public: false 20 test_mode: false 21 relay_endpoint: "" 22 read_timeout: 5m0s ··· 35 libsql_sync_interval: 1m0s 36 admin: 37 enabled: true 38 quota: 39 tiers: 40 deckhand:
··· 13 region: "{{.S3Region}}" 14 bucket: "{{.S3Bucket}}" 15 endpoint: "{{.S3Endpoint}}" 16 + pull_zone: "" 17 server: 18 addr: :8080 19 public_url: "https://{{.HoldDomain}}" 20 public: false 21 + successor: "" 22 test_mode: false 23 relay_endpoint: "" 24 read_timeout: 5m0s ··· 37 libsql_sync_interval: 1m0s 38 admin: 39 enabled: true 40 + gc: 41 + enabled: false 42 quota: 43 tiers: 44 deckhand:
+8
pkg/hold/config.go
··· 81 // Custom S3 endpoint for non-AWS providers. 82 Endpoint string `yaml:"endpoint" comment:"Custom S3 endpoint for non-AWS providers (e.g. \"https://gateway.storjshare.io\")."` 83 84 // Internal distribution storage config, built from the above fields. 85 distStorage configuration.Storage `yaml:"-"` 86 } ··· 188 v.SetDefault("storage.region", "us-east-1") 189 v.SetDefault("storage.bucket", "") 190 v.SetDefault("storage.endpoint", "") 191 192 // GC defaults 193 v.SetDefault("gc.enabled", false) ··· 244 _ = v.BindEnv("storage.region", "AWS_REGION") 245 _ = v.BindEnv("storage.bucket", "S3_BUCKET") 246 _ = v.BindEnv("storage.endpoint", "S3_ENDPOINT") 247 248 // Bind legacy GC env vars (backward compat) 249 _ = v.BindEnv("gc.enabled", "GC_ENABLED") ··· 297 if sc.Endpoint != "" { 298 params["regionendpoint"] = sc.Endpoint 299 params["forcepathstyle"] = true 300 } 301 302 storageCfg := configuration.Storage{}
··· 81 // Custom S3 endpoint for non-AWS providers. 82 Endpoint string `yaml:"endpoint" comment:"Custom S3 endpoint for non-AWS providers (e.g. \"https://gateway.storjshare.io\")."` 83 84 + // CDN pull zone URL for presigned download URLs. 85 + PullZone string `yaml:"pull_zone" comment:"CDN pull zone URL for downloads. When set, presigned GET/HEAD URLs use this host instead of the S3 endpoint. Uploads and API calls still use the S3 endpoint."` 86 + 87 // Internal distribution storage config, built from the above fields. 88 distStorage configuration.Storage `yaml:"-"` 89 } ··· 191 v.SetDefault("storage.region", "us-east-1") 192 v.SetDefault("storage.bucket", "") 193 v.SetDefault("storage.endpoint", "") 194 + v.SetDefault("storage.pull_zone", "") 195 196 // GC defaults 197 v.SetDefault("gc.enabled", false) ··· 248 _ = v.BindEnv("storage.region", "AWS_REGION") 249 _ = v.BindEnv("storage.bucket", "S3_BUCKET") 250 _ = v.BindEnv("storage.endpoint", "S3_ENDPOINT") 251 + _ = v.BindEnv("storage.pull_zone", "S3_PULL_ZONE") 252 253 // Bind legacy GC env vars (backward compat) 254 _ = v.BindEnv("gc.enabled", "GC_ENABLED") ··· 302 if sc.Endpoint != "" { 303 params["regionendpoint"] = sc.Endpoint 304 params["forcepathstyle"] = true 305 + } 306 + if sc.PullZone != "" { 307 + params["pullzone"] = sc.PullZone 308 } 309 310 storageCfg := configuration.Storage{}
+22 -4
pkg/s3/types.go
··· 33 34 // RealS3Client wraps AWS SDK v2 *s3.Client to implement S3Client interface 35 type RealS3Client struct { 36 - client *awss3.Client 37 - presign *awss3.PresignClient 38 } 39 40 // NewRealS3Client creates a new RealS3Client wrapper ··· 64 func (r *RealS3Client) PresignGetObject(ctx context.Context, input *awss3.GetObjectInput, expires time.Duration) (string, error) { 65 result, err := r.presign.PresignGetObject(ctx, input, func(opts *awss3.PresignOptions) { 66 opts.Expires = expires 67 }) 68 if err != nil { 69 return "", err ··· 75 func (r *RealS3Client) PresignHeadObject(ctx context.Context, input *awss3.HeadObjectInput, expires time.Duration) (string, error) { 76 result, err := r.presign.PresignHeadObject(ctx, input, func(opts *awss3.PresignOptions) { 77 opts.Expires = expires 78 }) 79 if err != nil { 80 return "", err ··· 162 s3PathPrefix = strings.TrimPrefix(rootDir, "/") 163 } 164 165 slog.Info("S3 presigned URLs enabled", 166 "bucket", bucket, 167 "region", region, 168 - "pathPrefix", s3PathPrefix) 169 170 // Create S3 client wrapped in RealS3Client for interface compatibility 171 return &S3Service{ 172 - Client: NewRealS3Client(client), 173 Bucket: bucket, 174 PathPrefix: s3PathPrefix, 175 }, nil
··· 33 34 // RealS3Client wraps AWS SDK v2 *s3.Client to implement S3Client interface 35 type RealS3Client struct { 36 + client *awss3.Client 37 + presign *awss3.PresignClient 38 + pullZone string // CDN pull zone URL for presigned GET/HEAD URLs 39 } 40 41 // NewRealS3Client creates a new RealS3Client wrapper ··· 65 func (r *RealS3Client) PresignGetObject(ctx context.Context, input *awss3.GetObjectInput, expires time.Duration) (string, error) { 66 result, err := r.presign.PresignGetObject(ctx, input, func(opts *awss3.PresignOptions) { 67 opts.Expires = expires 68 + if r.pullZone != "" { 69 + opts.ClientOptions = append(opts.ClientOptions, func(o *awss3.Options) { 70 + o.BaseEndpoint = aws.String(r.pullZone) 71 + }) 72 + } 73 }) 74 if err != nil { 75 return "", err ··· 81 func (r *RealS3Client) PresignHeadObject(ctx context.Context, input *awss3.HeadObjectInput, expires time.Duration) (string, error) { 82 result, err := r.presign.PresignHeadObject(ctx, input, func(opts *awss3.PresignOptions) { 83 opts.Expires = expires 84 + if r.pullZone != "" { 85 + opts.ClientOptions = append(opts.ClientOptions, func(o *awss3.Options) { 86 + o.BaseEndpoint = aws.String(r.pullZone) 87 + }) 88 + } 89 }) 90 if err != nil { 91 return "", err ··· 173 s3PathPrefix = strings.TrimPrefix(rootDir, "/") 174 } 175 176 + // CDN pull zone for presigned download URLs 177 + pullZone, _ := params["pullzone"].(string) 178 + 179 slog.Info("S3 presigned URLs enabled", 180 "bucket", bucket, 181 "region", region, 182 + "pathPrefix", s3PathPrefix, 183 + "pullZone", pullZone) 184 185 // Create S3 client wrapped in RealS3Client for interface compatibility 186 + realClient := NewRealS3Client(client) 187 + realClient.pullZone = pullZone 188 + 189 return &S3Service{ 190 + Client: realClient, 191 Bucket: bucket, 192 PathPrefix: s3PathPrefix, 193 }, nil