aboutsummaryrefslogtreecommitdiff
path: root/DESIGN.md
blob: 39f7db42504a2cc2bab2c7f3b7001fbedf92d2ff (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277

# 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 `<form>` 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.