No description
Find a file
Kristoffer Opsahl f17a11ec0f
All checks were successful
test / test (push) Successful in 44s
ci: handle missing go.sum in stdlib-only tidy check
`git diff --exit-code go.mod go.sum` errored 128 when go.sum didn't
exist (stdlib-only ⇒ tidy never creates one). Split into two assertions
that match the step's stated intent: fail if a go.sum appears, fail if
go.mod drifts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 13:11:14 +02:00
.forgejo/workflows ci: handle missing go.sum in stdlib-only tidy check 2026-05-11 13:11:14 +02:00
testdata/sigv4_vectors test: port AWS SigV4 GET-only test vectors 2026-05-11 10:11:13 +02:00
.gitignore chore: initialize s3lite module 2026-05-11 10:07:10 +02:00
client.go refactor: name the SigV4 service constant 'awsService' 2026-05-11 12:30:00 +02:00
client_test.go test: transport-level errors surface as wrapped errors (not ResponseError) 2026-05-11 12:31:22 +02:00
errors.go feat: error types — ResponseError and ErrNotFound 2026-05-11 10:31:11 +02:00
errors_test.go feat: error types — ResponseError and ErrNotFound 2026-05-11 10:31:11 +02:00
go.mod chore: initialize s3lite module 2026-05-11 10:07:10 +02:00
LICENSE chore: initialize s3lite module 2026-05-11 10:07:10 +02:00
live_test.go test: opt-in live GetObject against real S3-compatible endpoint 2026-05-11 10:41:34 +02:00
README.md docs: README — distinguish signer (parametric) from client (pins 's3') 2026-05-11 12:30:27 +02:00
signer.go feat: SigV4 signer passes full AWS GET test-vector suite 2026-05-11 10:26:43 +02:00
signer_test.go feat: SigV4 signer passes full AWS GET test-vector suite 2026-05-11 10:26:43 +02:00
validate_test.go test: cover validateBucket + validateKey directly 2026-05-11 12:31:04 +02:00

s3lite

Minimal, stdlib-only Go client for S3-compatible object storage.

go.mod license: MIT

Why this exists

The official AWS SDK works fine against non-AWS S3-compatible endpoints (MinIO, Hetzner Object Storage, R2, Backblaze, Spaces) but it drags in an AWS-flavoured configuration model and a non-trivial dependency tree. s3lite is the inverse: one package, no dependencies, one operation. Suitable when you need GetObject and nothing else.

Install

import "git.kristofferopsahl.com/kristofferopsahl/s3lite"
go get git.kristofferopsahl.com/kristofferopsahl/s3lite@latest

Quickstart

client, err := s3lite.New(s3lite.Config{
    Endpoint:  "https://hel1.your-objectstorage.com",
    Region:    "hel1",
    AccessKey: os.Getenv("S3_ACCESS_KEY"),
    SecretKey: os.Getenv("S3_SECRET_KEY"),
})
if err != nil { panic(err) }

body, hdr, err := client.GetObject(ctx, "my-bucket", "path/to/object.jpg")
if errors.Is(err, s3lite.ErrNotFound) {
    // 404 handling
}
if err != nil { panic(err) }
defer body.Close()

io.Copy(os.Stdout, body)

Supported endpoints

Tested against:

  • Hetzner Object Storage

By construction (path-style + SigV4) it should work against any S3-compatible service that supports path-style addressing — MinIO, Backblaze B2 (S3 API), Cloudflare R2 in path mode, DigitalOcean Spaces, Wasabi. Open an issue if it doesn't.

Scope

v0.1.0 ships exactly one operation: GetObject. Returns the response body (caller closes) and headers.

Not yet supported (open an issue if you need any of these): PutObject, DeleteObject, ListObjectsV2, CopyObject, HeadObject, multipart uploads, presigned URLs, virtual-hosted-style addressing, retries, server-side encryption.

SigV4

The library implements AWS Signature Version 4 in signer.go. It is exercised against AWS's published GET-only test vectors (in testdata/sigv4_vectors/). Path-style addressing only. The signer accepts any service name (the AWS-supplied vectors verify against the literal service string); Client always passes s3 in the credential scope. The region comes from Config.Region.

Tests

go test ./...                                  # unit tests; stdlib + httptest only
S3LITE_TEST_*=... go test -tags=live ./...     # opt-in real-endpoint test

License

MIT. See LICENSE.