Files
zeekay d884d9b292 feat(staticfiles): object-store origin so one ingress runs unlimited sites
Root becomes a union: a local path serves from disk (unchanged), and an
"s3://bucket/prefix" URL serves from an object store through the same handler,
so index resolution, spaMode fallback, errorPage404 and cacheControl behave
identically. spaMode stays the only difference between an SPA site and a plain
static site.

- s3.go: an http.FileSystem over hanzos3/go-sdk. Serving delegates to the SDK's
  seekable object, so http.ServeContent streams and answers Range requests
  without buffering the whole bundle. Keys are joined traversal-safe under the
  configured prefix; ETag comes from the object. Credentials come only from the
  process environment (AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY); endpoint/region
  default from S3_ENDPOINT/S3_REGION. A missing endpoint or credentials fails
  the middleware build rather than serving open.
- serveFile (SPA index / 404 page) now opens through the root http.FileSystem
  instead of os.Open, so the fallback works over either origin.
- Middleware CRD: model staticFiles (+ endpoint, region) on MiddlewareSpec so a
  Middleware CR carrying spec.staticFiles is accepted by the apiserver instead
  of pruned. Regenerated the CRD schema block, deepcopy and applyconfig.

Tests: hermetic handler/origin tests over an in-memory store (index, spaMode,
404, cache, ETag, Range, traversal, prefix isolation, fail-closed) plus a
shadow proof (real object store, real bundle, 60 MiB streamed) behind the
`shadow` build tag. Local-origin behavior is covered to prove it is unchanged.
2026-07-18 17:18:27 -07:00
..