Add trailing filename to encrypted URLs for better browser compatibility.
The filename is ignored by the server but helps browsers identify content type.
Since signing_key is now required at config load time, sessMgr, encGen,
and signer are always initialized. Remove unnecessary nil checks that
were runtime failure paths that can no longer be reached.
- handlers.go: Remove conditional init, always create sessMgr/encGen
- auth.go: Remove nil checks for sessMgr
- imageenc.go: Remove nil check for encGen
- service.go: Require signing_key in NewService, remove signer nil checks
- Update tests to provide signing_key
- Add config validation: signing_key required, minimum 32 characters
- Server now fails to start without valid signing_key (no more runtime errors)
- Add config.example.yml with default whitelist hosts
- Copy config to /etc/pixa/config.yml in Docker image
- Update entrypoint to use --config /etc/pixa/config.yml
- Add config.dev.yml for local Docker development
- Mount dev config in make devserver
- Change default StateDir from ./data to /var/lib/pixa (proper Unix convention)
- Create directory owned by pixad user in Dockerfile
- Set WORKDIR to /var/lib/pixa
- Replace gen2brain/avif, gen2brain/webp, disintegration/imaging with govips
- govips uses libvips via CGO for fast native image processing
- Add libheif-dev to Dockerfile for AVIF support
- Add docker-test Makefile target for running tests in Docker
- Update processor.go to use vips API for decode, resize, encode
- Add TestMain to initialize/shutdown vips in tests
- Remove WASM-based libraries (gen2brain) in favor of native codecs
Performance improvement: AVIF encoding now uses native libheif instead of
WASM, significantly reducing encoding time for large images.
Test verifies that images can be encoded to AVIF format.
Currently fails because AVIF encoding is not implemented.
Removes the rejection test for AVIF output format.
- Middleware now tracks and logs bytes written via response_bytes
- Handler logs cache_key for cache hit debugging
- Changed "served encrypted image" to "image served" (only URL is encrypted)
Quality is now a dropdown with named presets:
- Potato (25), Low (50), Medium (70), High (85), Ultra (100)
Added 1-minute TTL option for testing short-lived URLs.
- Capture TLS version, cipher suite, HTTP version, and remote addr
- Add download bitrate using go-humanize SI formatting
- Use consistent WxH format for dimensions (not struct notation)
- Rename input/output to src/dst for consistency
- Add separate "upstream fetched" log with connection details
FetchResult now includes:
- StatusCode: HTTP status from upstream
- FetchDurationMs: time to fetch from upstream
- RemoteAddr: upstream server address
SourceMetadata now stores:
- ContentLength: size from upstream
- FetchDurationMs: fetch timing
- RemoteAddr: for debugging
Image conversion log now includes:
- host: source hostname (was missing)
- path: source path (renamed from file)
- convert_ms: image processing time
- quality: requested quality setting
- fit: requested fit mode
Hot cache entries now store all data needed to serve a cache hit
without any database access:
- OutputHash (for file lookup)
- ContentType (for Content-Type header)
- SizeBytes (for Content-Length header)
Previously hot cache only stored OutputHash, causing empty
Content-Type headers on cached WebP responses.
When only width or height is specified (the other being 0), scale the
image proportionally to maintain aspect ratio. Previously, 0 was passed
directly to the resize function which produced a 0x0 image.
Uses github.com/gen2brain/webp - a CGO-free library that uses WASM via
wazero runtime for encoding. WebP decoding was already supported.
- Add gen2brain/webp dependency for encoding
- Implement WebP encoding in processor.go
- Add FormatWebP to SupportedOutputFormats
- Re-enable WebP option in generator form dropdown
- Mark WebP encoding as complete in TODO.md
TDD: This test expects WebP encoding to succeed. It currently fails
because WebP encoding is not implemented (returns ErrUnsupportedOutputFormat).
The test will pass once we add the gen2brain/webp library.
- Return ErrUnsupportedOutputFormat for WebP/AVIF encoding
- Return ErrInvalidFitMode for unknown fit mode values
- Add ValidateFitMode() for input validation
- Validate fit mode at handler level before processing
Silent fallbacks violate the principle of least surprise and mask bugs.
When a user explicitly specifies a value, we should either honor it or
return an error - never silently substitute a different value.
- Make ExpiresAt optional in CBOR (omitempty) for smaller tokens
- Treat ExpiresAt=0 as 'never expires' in parser
- URL-encode token with url.PathEscape() for safety
- Add 'Never' as default TTL option in generator form
TDD: Write tests first before implementation for:
- ETag generation and consistency in service layer
- HEAD request support (headers only, no body)
- Conditional requests with If-None-Match header (304 responses)
- StoreOutput now returns output hash for immediate retrieval
- Cache misses now serve from disk file after storing (same as hits)
- Log served_bytes from actual io.Copy result (avoids stat calls)
- Remove ContentLength field usage for cache hits (stream from file)
- Fix tests to properly check all return values
Implements the Processor interface using disintegration/imaging library.
Supports JPEG, PNG, GIF, WebP decoding and JPEG, PNG, GIF encoding.
Includes all fit modes: cover, contain, fill, inside, outside.