# Photo Gallery — Design Document ## 0. Solution **Go app (Option B)** — a single static binary reverse-proxied by Caddy. - Caddy handles TLS and serves the app on a secret URL path (e.g., `https://photos.example.com/trip-crete-2026-a8f3b2c1/`). - The Go binary serves an HTML page with multi-file upload (drag-and-drop), a file listing, and a "download all as zip" button. - Photos land in a configurable directory (one per trip). When the trip is done, stop the binary, move the folder to cold storage. - No database, no runtime deps, no Docker. ~200-400 lines of Go, ~10 MB binary. **Why this over pure Caddy (Option A):** At this scale (10 friends, 200 photos, ~4 GB max), both work. But the Go app gives a noticeably better mobile upload experience (multi-file, progress feedback) and zip download for free — worth the cost of one extra process. --- ## 1. Context & Scope After a holiday with friends, everyone has photos scattered across their phones. The goal is a self-hosted, lightweight web application where: - Friends open a link in their browser, upload their photos. - Friends can browse and download photos others have uploaded. - Once everyone has contributed, all photos live on the server — a single consolidated archive. - The host (you) keeps everything on their own infrastructure. No third-party cloud involved. **Current landscape:** Caddy is already used as the primary web server/reverse proxy. The solution should integrate naturally with it. The server is personal infrastructure — simplicity and low resource usage matter more than scaling to thousands of users. --- ## 2. Goals & Non-Goals ### Goals - **Browser-only UX** — no app install, no client software, no account creation. - **Upload & download** — friends can push photos and pull the full set. - **Lightweight** — minimal dependencies, low memory/CPU footprint, easy to deploy and tear down. - **Self-hosted** — all data stays on your server, full ownership. - **Simple ops** — ideally a single binary or a Caddyfile tweak, no database. - **Caddy-friendly** — works behind or with Caddy with minimal config. ### Non-Goals - User accounts or authentication beyond a shared secret/link. - Image editing, resizing, or transcoding. - Long-lived public gallery (this is ephemeral, per-trip). - Mobile app. - Handling thousands of concurrent users. - Video support (nice-to-have at best, not a driver). --- ## 3. Design The system is intentionally shallow. One server, one directory of files, one web interface. ```mermaid graph LR subgraph Friends A[Browser] B[Browser] C[Browser] end subgraph Server D[Caddy] E[Application / File Backend] F[/photos directory/] end A -- "upload/download via HTTPS" --> D B -- "upload/download via HTTPS" --> D C -- "upload/download via HTTPS" --> D D -- "reverse proxy or direct serve" --> E E -- "read/write" --> F ``` ### Features #### Friends (end users) | Feature | Detail | |---|---| | **Batch upload** | Select multiple photos at once (file picker or drag-and-drop) and upload them in a single action. Progress feedback per file. | | **Browse gallery** | View all uploaded photos directly in the browser — no download required. Images are served as compressed/resized previews to keep page load fast. | | **Download as zip** | Download the entire directory as a single `.zip` file with one click. | | **Password access** | Enter a directory-specific password to access the gallery. No account creation. | #### Operator (you) | Feature | Detail | |---|---| | **Create directory + password** | Create a new photo directory (one per trip/event) and assign it a password. This is the only admin action needed before sharing the link with friends. | | **Tear down** | When collection is done, stop the service or remove the route. Archive the photos directory to cold storage. | ### Key design points - **One directory per event** — each trip/event gets its own directory and its own password. No shared state between directories. - **Caddy terminates TLS** — every approach sits behind Caddy for HTTPS + domain. - **Access control** — password per directory, set by the operator at creation time. Friends enter the password once to access upload/browse/download. - **Ephemeral lifecycle** — spin up before sharing the link, tear down after collection is done. The photos directory persists. - **Compressed previews** — the gallery view serves resized/compressed versions of photos for fast browsing. Original full-resolution files are preserved on disk and served in the zip download. --- ## 4. Alternatives ### A. Caddy `file_server` + `upload` module (pure Caddy) Use Caddy's built-in `file_server` for browsing/downloading, combined with a community Caddy upload handler plugin ([caddyserver/caddy-upload](https://github.com/caddyserver/caddy-upload) or similar). | Aspect | Detail | |---|---| | **How it works** | Caddy serves the directory with `browse`. Uploads go through a Caddy handler that accepts `PUT`/`POST` and writes to disk. A minimal HTML page with a `
` or JS fetch provides the upload UI. | | **Dependencies** | Caddy + one plugin (or a custom build via `xcaddy`). | | **Upload UX** | Requires a small static HTML page with a drag-and-drop or file picker form. No server-side code beyond Caddy. | | **Download UX** | Caddy's built-in browse UI lists files; click to download individually. Bulk download would need an external script or zip-on-the-fly. | **Pros:** - Zero extra processes — it's just Caddy. - Already in your stack; nothing new to learn operationally. - TLS, basic auth, rate-limiting all native. **Cons:** - Upload plugin maturity varies; may need `xcaddy build` to include it. - Browse UI is functional but bare — no thumbnails, no gallery feel. - No bulk zip download out of the box. - Multi-file upload from a phone browser can be clunky without a custom HTML page. --- ### B. Minimal Go application (custom binary) A small, purpose-built Go HTTP server: one binary, no dependencies, serves an HTML page with upload form + file listing. | Aspect | Detail | |---|---| | **How it works** | Go binary listens on a local port. Caddy reverse-proxies to it. Handles `POST /upload` (multipart form), serves `GET /` with a gallery page listing files, and `GET /photos/*` to serve files. | | **Dependencies** | Go binary (~5-10 MB). No runtime deps, no database. | | **Upload UX** | Full control — can build a drag-and-drop multi-file UI, show progress bars, rename duplicates. | | **Download UX** | Can serve individual files and generate a zip of all photos on-the-fly for bulk download. Thumbnails possible via Go's image stdlib. | **Pros:** - Total control over UX — can be exactly what you want. - Single static binary, trivially deployable. Copy & run. - Zip download, thumbnail generation, progress feedback — all easy in Go. - No plugin ecosystem dependency; you own the code. **Cons:** - You have to write and maintain it (even if small, ~200-400 lines). - Another process to manage alongside Caddy. - Feature creep risk — temptation to add more and more. --- ### C. WebDAV via Caddy + Caddy browse Caddy has a `webdav` plugin. WebDAV is HTTP-native file access — upload, download, directory listing — and modern browsers/OS file managers support it. | Aspect | Detail | |---|---| | **How it works** | Caddy serves a WebDAV endpoint for the photos directory. A static HTML page provides a browser-based upload form. File listing served via `file_server browse` on a separate route. | | **Dependencies** | Caddy + `webdav` plugin (`xcaddy build`). | | **Upload UX** | Friends can use the HTML form in browser, or mount the WebDAV share on their OS and drag files in. | | **Download UX** | Browse + click to download. OS-level mount allows bulk copy. | **Pros:** - Standard protocol — works with native OS file managers (macOS Finder, Windows Explorer, Linux Nautilus). - Upload and download in one protocol; no custom code. - Caddy ecosystem, single process. **Cons:** - WebDAV in browsers is inconsistent — you still need a static HTML upload form for reliable mobile UX. - Plugin needed (`xcaddy build`). - The "mount a network drive" step is too technical for most friends. - No gallery view, no thumbnails. --- ### D. FTP server with web frontend Run a lightweight FTP server (e.g., `vsftpd`, or a Go-based one like `ftpgrab/ftpserver`) and pair it with a web file manager frontend. | Aspect | Detail | |---|---| | **How it works** | FTP server exposes the photos directory. A web-based FTP client (e.g., Monsta FTP, or a minimal custom page) served by Caddy provides the browser UI. | | **Dependencies** | FTP daemon + web frontend + Caddy. | | **Upload UX** | Through the web frontend's upload widget. | | **Download UX** | Through the web frontend's file listing. | **Pros:** - FTP is battle-tested for file transfer. - Some web FTP clients provide decent UIs. **Cons:** - **Heaviest approach** — three components (FTP server, web UI, Caddy). - FTP is a legacy protocol with known security quirks (passive ports, plaintext by default). FTPS adds complexity. - Web FTP clients are often clunky, unmaintained, or PHP-based. - Overkill for the problem. Friends don't need FTP; they need a web page. - Firewall/NAT unfriendly compared to pure HTTP. --- ### E. Existing lightweight gallery software Deploy an existing open-source photo gallery (e.g., **PiGallery2**, **Photoview**, **Lychee**, **Photoprism**). | Aspect | Detail | |---|---| | **How it works** | Docker container or binary, reverse-proxied by Caddy. Provides web UI for upload, gallery view with thumbnails, download. | | **Dependencies** | Varies — some need a database (SQLite at minimum), Node.js, or Docker. | | **Upload UX** | Usually polished with drag-and-drop. | | **Download UX** | Gallery browsing with thumbnails, album zip downloads. | **Pros:** - Best UX out of the box — thumbnail grids, slideshows, EXIF data. - Actively maintained projects with communities. - Handles edge cases (duplicate detection, format support). **Cons:** - **Not lightweight** — most pull in a database, runtime, or Docker image. - Opinionated features you don't need (user management, albums, face detection). - Heavier operational footprint — updates, config, database migrations. - Misaligned: these are permanent gallery tools, not ephemeral upload-and-collect tools. --- ### Comparison Summary | Criterion | A. Pure Caddy | B. Go App | C. WebDAV | D. FTP + Web | E. Existing Gallery | |---|:---:|:---:|:---:|:---:|:---:| | Lightweight | ★★★★★ | ★★★★ | ★★★★ | ★★ | ★★ | | Browser UX | ★★ | ★★★★★ | ★★★ | ★★ | ★★★★★ | | Upload ease (mobile) | ★★★ | ★★★★★ | ★★★ | ★★ | ★★★★ | | Bulk download | ★★ | ★★★★★ | ★★★ | ★★★ | ★★★★ | | Ops simplicity | ★★★★★ | ★★★★ | ★★★★ | ★★ | ★★ | | Caddy integration | ★★★★★ | ★★★★ | ★★★★ | ★★★ | ★★★ | | Nothing to write | ★★★★ | ★★ | ★★★★ | ★★★★ | ★★★★★ | | Time to working MVP | ★★★★ | ★★★ | ★★★ | ★★ | ★★★★ | --- ## 5. Open Questions (Resolved) 1. **Access control model** — Secret/unguessable URL path or basic auth. No accounts. 2. **Photo volume** — ~10 people, ~200 photos total. Worst case ~4 GB (200 × 20 MB). Zip-on-the-fly is feasible at this scale. 3. **Retention** — Ephemeral. Spin up for the collection window, then archive photos to personal storage and tear down. 4. **Duplicate handling** — None. Keep everything as-is. 5. **Upload size limits** — 20 MB per file as a safety cap. 6. **Multi-trip reuse** — One directory per trip. Either clean the folder between trips or configure a new folder path. The Go binary takes the photo directory as a flag (e.g., `--dir ./crete-2026`). --- ## 6. Parties Involved | Role | Who | |---|---| | **Author** | You (server owner/operator) | | **Users** | Friends (non-technical, browser-only) | | **Operator** | You — deployment, maintenance, teardown | --- ## 7. Cross-Concerns ### Security - **TLS mandatory** — Caddy handles this automatically with HTTPS. - **Access control** — at minimum, an unguessable URL path (e.g., `/holiday-crete-2026-a8f3b2c1/`). Optionally, Caddy `basicauth` for a shared password. - **Upload validation** — accept only image MIME types; reject executables. Enforce a max file size. - **Path traversal** — the application or Caddy handler must sanitize filenames to prevent `../../etc/passwd` style writes. ### Privacy - Photos are personal. No analytics, no third-party scripts, no CDN. - Server-side: photos stored as flat files with no metadata indexing unless explicitly built. - After collection, the upload endpoint should be disabled (remove the route or stop the service). ### Observability - Caddy access logs are sufficient. No need for metrics, APM, or dashboards for this scale. - Optional: log upload events (filename, size, timestamp) to a simple text file for your own tracking.