Skip to content

Commit ee54ecb

Browse files
authored
[major] v1 Release (#30)
* [minor] simplify v1 release surface * Add remote URI metadata cache * Add derivative cache max age
1 parent 3880668 commit ee54ecb

44 files changed

Lines changed: 752 additions & 1612 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ results
66
site
77

88
deploy/compose/cache
9-
deploy/compose/images
109
deploy/compose/presentation
1110
deploy/compose/source-cache
1211

.github/workflows/github-release.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ jobs:
1212
if: github.event.pull_request.merged == true && !contains(github.event.pull_request.title, 'skip-release')
1313
uses: libops/actions/.github/workflows/bump-release.yaml@ef667db8c16533a257d841e75df5c3388152b2d7 # main
1414
with:
15+
workflow_file: build-push.yaml
1516
prefix: v
1617
permissions:
1718
contents: write

Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ RUN rm -rf \
216216
&& groupadd --system triplet \
217217
&& useradd --system --gid triplet --uid 100 --home-dir /nonexistent --shell /usr/sbin/nologin triplet
218218

219+
WORKDIR /var/lib/triplet
220+
RUN mkdir -p /var/lib/triplet/cache /var/lib/triplet/testdata/images \
221+
&& chown -R triplet:triplet /var/lib/triplet
222+
COPY --chown=triplet:triplet deploy/compose/images/ /var/lib/triplet/testdata/images/
223+
219224
COPY --from=build /out/triplet /usr/local/bin/triplet
220225
COPY --from=build /out/triplet-healthcheck /usr/local/bin/triplet-healthcheck
221226
COPY config.example.yaml /etc/triplet/config.yaml

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ All image processing is done by [libvips] through [govips].
1111
docker run -p 8080:8080 ghcr.io/libops/triplet:main
1212
```
1313

14+
Then try the bundled sample image:
15+
16+
```bash
17+
curl http://localhost:8080/iiif/3/sample.png/info.json
18+
```
19+
1420
## Documentation
1521

1622
The project documentation lives at <https://libops.github.io/triplet>.

config.example.yaml

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ vips:
5252
- VipsForeignLoadPdf
5353

5454
iiif:
55-
# Optional shared CORS allowlist for IIIF Presentation, Search, Auth, and
56-
# Image unless iiif.image.allowed_origins is set. Entries must be exact
55+
# Optional shared CORS allowlist for IIIF Presentation and Image unless
56+
# iiif.image.allowed_origins is set. Entries must be exact
5757
# origins (`https://viewer.example.edu`) or `*`.
5858
# When empty, no Access-Control-Allow-Origin header is emitted.
5959
# allowed_origins:
@@ -77,10 +77,13 @@ iiif:
7777
max_source_pixels: 250000000
7878
# Refuse or stop spooling encoded source files larger than this many bytes
7979
# when the source is not already available as a file path. 0 disables.
80-
max_source_bytes: 1073741824
81-
# Refuse encoded derivatives larger than this many bytes after libvips
82-
# export. 0 disables.
83-
max_derivative_bytes: 536870912
80+
max_source_bytes: 1GiB
81+
# Per-request encoded response limit. Refuse one generated derivative if it
82+
# is larger than this many bytes after libvips export. This protects the
83+
# server from returning or caching a single unexpectedly huge response.
84+
# This is not the total cache size; see cache.max_bytes for the aggregate
85+
# filesystem derivative-cache budget. 0 disables.
86+
max_derivative_bytes: 512MiB
8487
# Bound concurrent libvips jobs across image derivatives and info probes.
8588
max_concurrent_transforms: 4
8689
# Advertise additional transform limits in info.json so clients can avoid
@@ -91,8 +94,14 @@ iiif:
9194
# Cantaloupe behavior. `normalize` converts to sRGB/gray. `none` skips
9295
# profile conversion and strips metadata where the output codec supports it.
9396
color_management: preserve
94-
# `auto` uses random access for region crops and sequential access for
95-
# full/resize requests. You can force sequential or random for profiling.
97+
# How libvips should read source pixels from disk or spooled source files.
98+
# `auto` is the production default: it uses random access for region crops
99+
# and sequential access for full-image or resize requests. Sequential access
100+
# streams forward and can reduce memory and I/O for whole-image reads, but it
101+
# is a poor fit for tile/region workloads that need pixels from arbitrary
102+
# offsets. Random access is better for crops and tiled viewers, but can do
103+
# unnecessary work for simple full-image derivatives. Force `sequential` or
104+
# `random` only when profiling a specific deployment or source format.
96105
load_access: auto
97106
# Cache info.json dimensions by identifier plus source mtime/size.
98107
info_dimension_cache: true
@@ -119,20 +128,9 @@ iiif:
119128
# Bearer token. Prefer injecting this from the environment.
120129
write_enabled: false
121130
# write_token: ${TRIPLET_PRESENTATION_WRITE_TOKEN}
122-
search:
123-
# IIIF Content Search 2.0 surface. The default backend is a no-op that
124-
# returns an empty AnnotationPage; indexing adapters are future work.
125-
enabled: false
126-
prefix: /search/v2
127-
auth:
128-
# IIIF Authorization Flow API 2.0 surface. No production authorizer is
129-
# built in yet; permit-all must be explicitly enabled for development.
130-
enabled: false
131-
prefix: /auth/v2
132-
development_permit_all: false
133131

134132
# Identifier resolution. Exactly one source must be the default; additional
135-
# sources are selected by identifier scheme (e.g. `https://…`, `gs://…`).
133+
# sources are selected by identifier scheme (e.g. `https://…`).
136134
sources:
137135
default: file
138136
file:
@@ -148,10 +146,6 @@ sources:
148146
# - prefix: /system/files
149147
# root: /private
150148
# auth_probe: true
151-
# auth_anonymous_cache_ttl: 720h
152-
# auth_authenticated_cache_ttl: 168h
153-
# auth_error_cache_min_age: 5m
154-
# auth_cache_max_entries: 4096
155149
# - prefix: /fedora
156150
# root: /fcrepo
157151
# ocfl: true
@@ -161,10 +155,8 @@ sources:
161155
# `auth_probe: true` means Triplet forwards browser Cookie/Authorization
162156
# headers to the original URL and requires 200/206 before reading locally.
163157
# Auth probes are tiered: anonymous is checked first and cached separately;
164-
# credentialed probes only run when anonymous access is denied. Long TTLs
165-
# are explicit access-staleness windows; defaults are short when omitted.
166-
# 403/404 probe results are not cached when Last-Modified is newer than
167-
# auth_error_cache_min_age, avoiding permission-publication races.
158+
# credentialed probes only run when anonymous access is denied. Probe
159+
# decisions inherit sources.http.metadata_cache_ttl.
168160
# Optional HTTP(S) source. When configured alongside the file source,
169161
# `http://...` and `https://...` identifiers are routed here automatically.
170162
#
@@ -180,27 +172,32 @@ sources:
180172
# allowed_origins: [https://islandora-stage.lib.lehigh.edu]
181173
# allow_private_hosts: false
182174
# request_timeout: 2m
183-
# max_bytes: 52428800
184-
# gcs:
185-
# bucket_url: gs://my-bucket
186-
# prefix: images
175+
# max_bytes: 50MiB
176+
# # Optional in-process metadata cache for remote URL identifiers. This lets
177+
# # derivative cache hits reuse recent ETag/Last-Modified/size metadata
178+
# # instead of making a HEAD or range request to the upstream source every
179+
# # time. During this TTL, Triplet may serve a cached derivative without
180+
# # noticing that the remote source changed or disappeared.
181+
# metadata_cache_ttl: 5m
187182

188183
cache:
189-
# Derivative cache. Configure either a filesystem root or a blob bucket URL.
184+
# Derivative cache. Configure a filesystem root.
190185
root: /var/lib/triplet/cache
191-
# bucket_url: gs://triplet-cache
192-
# prefix: derivatives
193-
# Best-effort eviction target for the file cache. 0 disables size-based
194-
# eviction.
195-
max_bytes: 1073741824
196-
# Optional source cache for fetched source bytes (primarily HTTP
197-
# identifiers). Configure either a filesystem root or a blob bucket URL.
186+
# Best-effort aggregate size target for all cached derivative payload files
187+
# under cache.root. This controls retained cache footprint over time, not the
188+
# size of any single generated response. A write may temporarily exceed this
189+
# target before eviction runs, and metadata sidecar files are not counted.
190+
# 0 disables size-based eviction.
191+
max_bytes: 500GiB
192+
# Optional age limit for derivative entries. Expired entries are removed on
193+
# read and opportunistically during writes. 0 disables age-based eviction.
194+
max_age: 720h
195+
# Optional filesystem source cache for fetched source bytes (primarily HTTP
196+
# identifiers).
198197
# source_root: /var/lib/triplet/source-cache
199-
# source_bucket_url: gs://triplet-source-cache
200-
# source_prefix: sources
201198
# Best-effort eviction target for the source cache. 0 disables size-based
202199
# eviction.
203-
source_max_bytes: 1073741824
200+
source_max_bytes: 1GiB
204201
# When non-zero, stale source-cache hits are served immediately while a
205202
# background refresh fetches a fresh copy for later requests.
206203
source_stale_after: 24h
@@ -210,7 +207,7 @@ extensions:
210207
# → encoded derivative. Same pipeline as the spec routes.
211208
transform:
212209
enabled: true
213-
max_upload_bytes: 52428800 # 50 MiB
210+
max_upload_bytes: 50MiB
214211
# Non-spec endpoint: POST bytes → mints an opaque identifier resolvable
215212
# via the standard /iiif/3/{id}/... routes.
216213
uploads:

deploy/compose/config.yaml

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ iiif:
2020
max_output_pixels: 100000000
2121
allow_unsafe_unlimited_output_pixels: false
2222
max_source_pixels: 250000000
23-
max_source_bytes: 1073741824
24-
max_derivative_bytes: 536870912
23+
max_source_bytes: 1GiB
24+
max_derivative_bytes: 512MiB
2525
max_concurrent_transforms: 4
2626
color_management: preserve
2727
load_access: auto
@@ -32,13 +32,6 @@ iiif:
3232
root: /var/lib/triplet/presentation
3333
write_enabled: ${TRIPLET_PRESENTATION_WRITE_ENABLED}
3434
write_token: "${TRIPLET_PRESENTATION_WRITE_TOKEN}"
35-
search:
36-
enabled: false
37-
prefix: /search/v2
38-
auth:
39-
enabled: false
40-
prefix: /auth/v2
41-
development_permit_all: false
4235

4336
sources:
4437
default: file
@@ -52,13 +45,13 @@ sources:
5245
# allowed_origins: [https://images.example.org]
5346
# allow_private_hosts: false
5447
# request_timeout: 2m
55-
# max_bytes: 52428800
48+
# max_bytes: 50MiB
5649

5750
cache:
5851
root: /var/lib/triplet/cache
59-
max_bytes: 1073741824
52+
max_bytes: 500GiB
6053
source_root: /var/lib/triplet/source-cache
61-
source_max_bytes: 1073741824
54+
source_max_bytes: 1GiB
6255
source_stale_after: 24h
6356

6457
extensions:

docs/authorization.md

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,25 @@ sources:
6464
- prefix: /system/files
6565
root: /private
6666
auth_probe: true
67-
auth_anonymous_cache_ttl: 720h
68-
auth_authenticated_cache_ttl: 168h
69-
auth_error_cache_min_age: 5m
70-
auth_cache_max_entries: 4096
67+
http:
68+
allowed_origins:
69+
- https://repository.example.edu
70+
metadata_cache_ttl: 168h
7171
```
7272

7373
The probe answers whether the original source URL would let this request read
7474
the file. Triplet uses that source response as the authority before serving the
7575
local copy or a cached derivative.
7676

77-
Anonymous access is checked first. If the source allows anonymous access,
78-
Triplet caches that anonymous allow decision for the identifier and all callers
79-
can use it until the TTL expires. If anonymous access is denied and the incoming
80-
request has `Cookie` or `Authorization` headers, Triplet checks the source again
81-
with those headers. Credentialed decisions are cached separately by identifier
82-
and by the exact forwarded `Cookie` and `Authorization` header values, so two
83-
different sessions do not share one authenticated auth decision. A repeated
84-
request with the same headers uses the cached decision until its TTL expires.
77+
Anonymous access is checked first. If `sources.http.metadata_cache_ttl` is set
78+
and the source allows anonymous access, Triplet caches that anonymous allow
79+
decision for the identifier and all callers can use it until the TTL expires. If
80+
anonymous access is denied and the incoming request has `Cookie` or
81+
`Authorization` headers, Triplet checks the source again with those headers.
82+
Credentialed decisions are cached separately by identifier and by the exact
83+
forwarded `Cookie` and `Authorization` header values, so two different sessions
84+
do not share one authenticated auth decision. A repeated request with the same
85+
headers uses the cached decision until the TTL expires.
8586

8687
Derivative bytes are shared after authorization. The authorization probe or
8788
auth-cache lookup happens before Triplet serves a derivative-cache hit, but the
@@ -119,7 +120,7 @@ flowchart TD
119120
deriv -- No --> transform[Transform source] --> store[Store if cacheable] --> serve([Serve derivative])
120121
```
121122

122-
## IIIF Authorization Flow terminology
123+
## Source authorization terminology
123124

124125
Triplet's local URL `auth_probe` is a server-side source authorization check. It
125126
is related to, but not the same thing as, an IIIF Authorization Flow API 2.0
@@ -137,12 +138,7 @@ Triplet's `auth_probe` uses the same idea internally: before serving a local fil
137138
or a cached derivative, Triplet asks the original source URL what status this
138139
request would receive. The source response remains authoritative.
139140

140-
The IIIF auth service declarations can inform viewers and Presentation API
141-
responses, but they should not be treated as a standalone filesystem bypass
142-
inside the Image API path. A single Image API request does not necessarily carry
143-
the Manifest context that referenced it, manifests can be stale, and multiple
144-
manifests can point at the same image service with different access stories. For
145-
Triplet's local-file shortcut, the safe optimization is still based on the source
141+
For Triplet's local-file shortcut, the safe optimization is based on the source
146142
authorization result:
147143

148144
- If the source allows anonymous access, Triplet can cache that anonymous allow
@@ -153,11 +149,9 @@ authorization result:
153149

154150
## Auth decision TTLs
155151

156-
Anonymous and authenticated probe decisions default to 5 minutes. Override them
157-
per mapping with `auth_anonymous_cache_ttl` and
158-
`auth_authenticated_cache_ttl`. If either tier-specific value is omitted,
159-
Triplet falls back to `auth_cache_ttl`. If that is also omitted, the tier uses
160-
the 5 minute default.
152+
Anonymous and authenticated probe decisions inherit
153+
`sources.http.metadata_cache_ttl`. Leave the TTL unset or `0` to disable
154+
auth-probe decision caching and recheck the upstream source on every request.
161155

162156
Long TTLs are explicit access-staleness windows. They are appropriate when
163157
repository permissions change rarely or when the mapping is used for content
@@ -170,14 +164,10 @@ configured TTL. Other upstream errors are not cached.
170164
Negative auth-probe caching is conservative. For 401, 403, and 404 probe
171165
responses, Triplet checks the upstream `Last-Modified` header before caching the
172166
denial. If `Last-Modified` parses and is newer than
173-
`now - auth_error_cache_min_age`, the denial is not cached. This avoids holding a
174-
stale denial while repository access rules or file publication are still
175-
settling. If `Last-Modified` is absent, unparseable, or older than that minimum
176-
age window, the denial can be cached for the configured auth TTL.
177-
`auth_error_cache_min_age` defaults to 5 minutes; increase it when repository
178-
metadata and permissions are known to settle more slowly.
179-
180-
`auth_cache_max_entries` defaults to 4096 entries when omitted.
167+
5 minutes ago, the denial is not cached. This avoids holding a stale denial
168+
while repository access rules or file publication are still settling. If
169+
`Last-Modified` is absent, unparseable, or older than that minimum age window,
170+
the denial can be cached for the configured auth TTL.
181171

182172
The image cache invalidation route also clears matching auth-probe entries when
183173
the source backend supports it.
@@ -205,5 +195,5 @@ sources:
205195
- https://repository.example.edu
206196
allow_private_hosts: false
207197
request_timeout: 2m
208-
max_bytes: 52428800
198+
max_bytes: 50MiB
209199
```

0 commit comments

Comments
 (0)