{"id":51007488,"url":"https://github.com/synapticloop/caddy_image_gallery","last_synced_at":"2026-06-20T22:01:28.101Z","repository":{"id":365346385,"uuid":"1268872915","full_name":"synapticloop/caddy_image_gallery","owner":"synapticloop","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-17T01:10:34.000Z","size":148,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T01:19:57.973Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/synapticloop.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-14T03:17:27.000Z","updated_at":"2026-06-17T01:10:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/synapticloop/caddy_image_gallery","commit_stats":null,"previous_names":["synapticloop/caddy_image_gallery"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/synapticloop/caddy_image_gallery","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synapticloop%2Fcaddy_image_gallery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synapticloop%2Fcaddy_image_gallery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synapticloop%2Fcaddy_image_gallery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synapticloop%2Fcaddy_image_gallery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/synapticloop","download_url":"https://codeload.github.com/synapticloop/caddy_image_gallery/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/synapticloop%2Fcaddy_image_gallery/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34586666,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-20T22:01:27.220Z","updated_at":"2026-06-20T22:01:28.067Z","avatar_url":"https://github.com/synapticloop.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# caddy_image_gallery\n\nA Caddy v2 HTTP handler module that renders a directory as a light-themed\nimage gallery. Replaces Caddy's default `file_server browse` with a\nthumbnailed grid, click-to-expand lightbox, sortable + paginated layout,\nand a separate \"Other files\" strip above the image grid for non-image\ncontent.\n\n![preview](#) \u003c!-- TODO: add a screenshot if you have one handy --\u003e\n\n## Features\n\n- **Drop-in replacement** for `file_server browse` in a `handle_path` block.\n- **Recursive** — every subdirectory under the matched route is rendered as a gallery.\n- **WebP thumbnails** generated on the fly, cached on disk, invalidated by source mtime.\n- **Vanilla JS lightbox** for click-to-expand, no external JS dependencies.\n- **Light theme** with white card on grey background, blue accent links, Caddy-browse aesthetic.\n- **Native `loading=\"lazy\"`** on every thumbnail.\n- **Video support** — videos show a play-button overlay and link to the raw file.\n- **\"Other files\" section** for non-image/non-video content in a directory.\n\n## Install\n\nBuild a custom Caddy binary with this module baked in:\n\n```bash\nxcaddy build \\\n    --with github.com/caddyserver/caddy@v2.11.4 \\\n    --with github.com/synapticloop/caddy_image_gallery@latest \\\n```\n\nOr use the included build script (pins Caddy to v2.11.4 and the local module path):\n\n```bash\n./build.sh\n```\n\nThe build script also restarts Caddy via systemd (you may need to be root or use sudo).\n\n## Caddyfile usage\n\n```caddyfile\nhandle_path /images/* {\n    root * /var/www/html/images\n    image_gallery         # default: mtime desc, 320px WebP thumbs\n    file_server           # serves direct file requests (e.g. /images/foo.jpg)\n}\n\n# Or with explicit sort:\nhandle_path /images/crosswords/* {\n    root * /var/www/html/images/crosswords\n    image_gallery { sort name }   # alphabetical for curated content\n}\n```\n\nThe `image_gallery` directive MUST come before `file_server` in the handle block — that way it gets a chance to handle the request (gallery HTML, thumbnail requests), and only falls through to `file_server` for direct file access (e.g. `/images/foo.jpg`).\n\n### Auth\n\nThe gallery slots behind any standard Caddy auth layer (basic_auth, forward_auth, JWT, etc.) — it's just a regular HTTP handler. It does not implement its own auth.\n\n## Caddyfile directive options\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `sort`  | `mtime` | `mtime` (newest first by modification time) or `name` (alphabetical) |\n\nExample:\n```caddyfile\nimage_gallery { sort name }\n```\n\n## How thumbs work\n\nThumb URLs look like `/_thumbs/\u003cbasename\u003e.webp` (e.g. for source `photo.jpg`, the thumb is at `/_thumbs/photo.webp`). On first request, the module:\n\n1. Hashes the source's absolute path (sha256, first 16 bytes).\n2. Checks the cache at `/var/cache/caddy-gallery/\u003chash\u003e.webp`.\n3. If the cached file's mtime is older than the source, regenerates:\n   - Decode source (jpg, png, gif, webp via stdlib + golang.org/x/image)\n   - Resize to 320px wide, preserve aspect ratio\n   - Encode as lossless WebP (VP8L) using github.com/HugoSmits86/nativewebp\n   - Write to cache, return the bytes\n4. Subsequent requests serve the cached file directly.\n\nCache invalidation is purely mtime-based — no cron job, no inotify watcher.\n\n**Cache directory** is `/var/cache/caddy-gallery` by default. Override with the `GALLERY_THUMB_CACHE_DIR` env var (useful for testing).\n\n## Caching \u0026 performance\n\n- **Scan cache** — each directory is scanned at most once per minute (mtime-keyed). For 100+ image directories like `/images/generated/`, this drops per-request work from milliseconds to microseconds.\n- **Thumb cache** — WebP thumbs are written to disk and served from disk; subsequent requests are a single `os.ReadFile`. The thumb URL is content-addressed (sha256 of the source path), so the URL itself is cacheable.\n- **HTTP `Cache-Control: public, max-age=86400`** on thumb responses (24h, since thumbs are immutable per source mtime).\n- **HTTP `Cache-Control: no-cache`** on gallery HTML (so newly-added images show up on the next refresh).\n\n## Dependencies\n\n- [caddyserver/caddy](https://github.com/caddyserver/caddy) v2.11.4 (compile-time)\n- [golang.org/x/image](https://pkg.go.dev/golang.org/x/image) — for image resizing\n- [HugoSmits86/nativewebp](https://github.com/HugoSmits86/nativewebp) — pure-Go lossless WebP encoder (no CGO, no libwebp)\n\n## Build\n\n```bash\n# Clone\ngit clone https://github.com/synapticloop/caddy_image_gallery\ncd caddy_image_gallery\n\n# Build (requires xcaddy and Go 1.21+)\ngo mod download\n./build.sh\n```\n\n## Test\n\n```bash\ngo test ./... -v\ngo test ./... -race       # race detector\n```\n\n24 tests, all standard library + stdlib-friendly patterns. No test fixtures in the repo — the test for thumbnail generation uses a programmatically-generated 640x480 JPEG.\n\n## Architecture\n\n```\ncaddy_image_gallery/\n├── gallery.go          # Module registration, Caddyfile parser, ServeHTTP\n├── scanner.go          # Directory walker + file classification (image/video/other)\n├── scancache.go        # mtime-keyed in-memory cache of directory scans\n├── render.go           # HTML template + inlined dark CSS + inlined lightbox JS\n├── thumbnails.go       # WebP thumb generation (decode → resize → encode), mtime cache\n├── *_test.go           # Go tests (24 total)\n├── build.sh            # xcaddy build + systemd restart\n└── README.md           # this file\n```\n\n## Caddyfile example (full)\n\n```caddyfile\n{\n    admin off\n}\n\nyour.caddy.host:443 {\n    tls /etc/caddy/caddy.crt /etc/caddy/caddy.key\n\n    route {\n        basic_auth {\n            youruser $2a$14$bcrypt_hash_here\n        }\n\n        handle_path /images/* {\n            root * /var/www/html/images\n            image_gallery\n            file_server\n        }\n    }\n}\n```\n\n## Documentation\n\nThe full documentation is also available as a single\nPDF: [caddy-image-gallery-book.pdf](caddy-image-gallery-book.pdf)\n(19 pages, with cover page, table of contents, and the\nGoogle Fonts typography - Libre Baskerville for body text,\nJetBrains Mono for code).\n\nDetailed operator documentation lives in [`docs/`](docs/):\n\n- [docs/00-readme.md](docs/00-readme.md) — index\n- [docs/01-configuration.md](docs/01-configuration.md) — Caddyfile directive, JSON config, env vars\n- [docs/02-configuration-reference.md](docs/02-configuration-reference.md) — one-page reference of every config knob\n- [docs/03-templates.md](docs/03-templates.md) — template structure, all template variables, how to override\n- [docs/04-sort-and-pagination.md](docs/04-sort-and-pagination.md) — the `?sort=\u0026order=\u0026page=` URL API\n\n## License\n\nMIT. See [LICENSE](LICENSE) (add one if you haven't — the module code is yours to license).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynapticloop%2Fcaddy_image_gallery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsynapticloop%2Fcaddy_image_gallery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynapticloop%2Fcaddy_image_gallery/lists"}