Update README for current features

Bring the README in line with the gallery as it stands: add a Features section
(views and default-view, albums/tags, day pages, link previews, manage/trash,
tag autocomplete, app-side auth, the signed-in indicator, and the Android and
Qt desktop apps). Correct the auth story (bcrypt htpasswd in the app, nginx
only terminates TLS and proxies), move htpasswdFile to the top-level module
option, document defaultView/pageSize/tags, and add an Apps section.
This commit is contained in:
puttaalu committed 2026-10-18 23:16:26 +02:00
1 parent 94859fb0d6
commit e67d25b191
1 file changed
+69 -12
+69 -12
View File
@@ -1,6 +1,9 @@
# photogallery
A minimal static photo gallery with an Atom feed and an upload API.
A single-user static photo gallery: a Go binary watches a directory of images +
`.toml` sidecars and regenerates a static site, with an Atom feed, albums, an
authenticated upload/manage API, and Android + desktop uploader apps. No
database, no framework.
## Contributors
@@ -9,11 +12,39 @@ A minimal static photo gallery with an Atom feed and an upload API.
grid as the site root). Both landed here as independent implementations
building on that work.
## Features
**Gallery**
- Two views of the same photos — a day-grouped **timeline** and a flat **grid** — either one as the site root (`-default-view`); the other lives under `/timeline/` or `/grid/`. 3-column square grid (2 on mobile) with infinite-scroll pagination.
- **Single-day pages** (`/day/<date>/`) linked from each timeline date heading.
- **Albums**: tag photos with free-text, lowercase tags; each tag is an album at `/album/<tag>/`, indexed at `/albums/`, plus a machine-readable `/tags.json`.
- **Single photo pages** with newer/older navigation (arrow keys + touch swipe) and a link to the original.
- **Open Graph / Twitter-card link previews** so shared photo links unfurl.
- **Atom feed** at `/feed.xml`. Gruvbox-dark theme; configurable fonts and identity.
**Content & management (no database)**
- `.toml` sidecars are the source of truth; the binary watches the content dir and rebuilds on any change. Four renditions per photo (original, 600² thumbnail, 1600px medium, 1200px OG), each skipped when already up to date.
- **Web upload** (`/upload`) with EXIF-date prefill and tag autocomplete.
- **Manage UI** (`/manage`): edit caption/date/tags, bulk tag add/remove, and delete — delete is reversible (moves to `content/.trash/`, with restore / empty).
- Tag autocomplete across the upload and manage forms, fed by `/tags.json`.
**Auth & security**
- App-side **HTTP Basic auth** (bcrypt htpasswd, `-htpasswd`) on every write endpoint; nginx just terminates TLS and proxies. Failed attempts are rate-limited; state-changing requests get a same-origin (CSRF) check.
- A **signed-in indicator** + management toolbar (Upload / Manage / Edit this) appear on every page when authenticated, via a silent `/api/session` probe (no login dialog for anonymous visitors).
**Clients**
- The web upload + manage UI.
- **Android** uploader (Kotlin/Compose, `android/`): pick or share photos, multi-photo bulk upload, captions/dates/tags.
- **Qt desktop** uploader (`desktop/`): bulk upload with configurable concurrency, HEIC support, credentials in the system keychain.
**Deployment**
- One Go binary. NixOS flake + module (systemd service, tmpfiles, optional nginx vhost with ACME).
## How it works
1. Drop `photo.jpg` + `photo.toml` into the content directory (or upload via the web UI at `/upload`).
2. The binary watches the directory and rebuilds the static site on any change.
3. nginx serves `public/` and proxies `/upload` to the local API, secured with htpasswd.
1. Drop `photo.jpg` + `photo.toml` into the content directory (or upload via the web UI at `/upload`, the manage UI, or an app).
2. The binary watches the directory and rebuilds the static site on any change (and on every upload/edit/delete).
3. nginx terminates TLS, serves `public/`, and proxies the dynamic endpoints (`/upload`, `/manage`, `/api/`) to the local API. **Auth is the app's job** now (bcrypt htpasswd via `-htpasswd`), not nginx's.
For each photo the generator produces a 600×600 square thumbnail for the grid
and an aspect-preserved medium JPEG (longest side 1600) for the detail page.
@@ -30,9 +61,13 @@ editable before submitting.
```toml
caption = "Tempelhof at sunrise"
date = "2026-06-14"
tags = ["berlin", "sunrise"]
```
If no `.toml` is present the filename is used as the caption and today's date as the date.
If no `.toml` is present the filename is used as the caption and today's date as
the date. `tags` is optional, free-text and lowercased; each tag becomes an
album. A tag may contain spaces (`"berlin trip"`); its URL slug is derived
automatically.
---
@@ -65,6 +100,14 @@ services.photogallery = {
enable = true;
baseURL = "https://photos.yourdomain.tld";
# Auth for the upload/manage endpoints: a bcrypt htpasswd file. Handed to the
# service via systemd LoadCredential, so a root-owned 0400 secret is fine.
# Omit to leave /upload and /manage open.
htpasswdFile = "/run/secrets/photogallery-htpasswd";
# Optional: which view is the site root ("timeline" or "grid")
defaultView = "timeline";
# Optional: identity shown on the header and copyright footer
author = "Ada Lovelace";
handle = "@ada@example.com";
@@ -76,20 +119,19 @@ services.photogallery = {
serifFamily = "EB Garamond";
mlFamily = "Manjari";
# Optional: built-in nginx vhost
# Optional: built-in nginx vhost (TLS + proxy only; auth is app-side above)
nginx = {
enable = true;
domain = "photos.yourdomain.tld";
htpasswdFile = "/etc/nginx/.htpasswd-gallery";
};
};
```
Create the htpasswd file:
Create the htpasswd file (**bcrypt** — the app only accepts `-B` entries):
```bash
nix-shell -p apacheHttpd --run \
"htpasswd -c /etc/nginx/.htpasswd-gallery youruser"
"htpasswd -B -c /run/secrets/photogallery-htpasswd youruser"
```
### Available options
@@ -101,6 +143,9 @@ nix-shell -p apacheHttpd --run \
| `contentDir` | `/var/lib/photogallery/content` | Images and .toml sidecars |
| `outputDir` | `/var/lib/photogallery/public` | Generated site output |
| `port` | `8080` | Upload API port (localhost only) |
| `htpasswdFile` | `null` | bcrypt htpasswd guarding write endpoints (null = open) |
| `defaultView` | `timeline` | Site-root view: `timeline` or `grid` |
| `pageSize` | `60` | Photos per page (timeline/grid) |
| `author` | `Ashik Salahudeen` | Name shown on the header, photo pages and copyright footer |
| `handle` | `@puttaalu@inflo.ws` | Handle line under the author name |
| `bio` | `Personal photo journal.` | Primary bio line |
@@ -108,9 +153,8 @@ nix-shell -p apacheHttpd --run \
| `fontsURL` | Crimson Pro + Manjari | Stylesheet URL loading both web fonts |
| `serifFamily` | `Crimson Pro` | CSS font-family for the primary serif |
| `mlFamily` | `Manjari` | CSS font-family for the alt-script bio |
| `nginx.enable` | `false` | Configure an nginx vhost |
| `nginx.enable` | `false` | Configure an nginx vhost (TLS + proxy) |
| `nginx.domain` | — | Domain for the vhost |
| `nginx.htpasswdFile` | — | htpasswd file for `/upload` |
| `nginx.enableACME` | `true` | Enable Let's Encrypt |
---
@@ -141,7 +185,20 @@ curl -u user:password \
-F "image=@photo.jpg" \
-F "caption=Tempelhof at sunrise" \
-F "date=2026-06-14" \
-F "tags=berlin, sunrise" \
https://photos.yourdomain.tld/upload
```
Or open `https://photos.yourdomain.tld/upload` in a browser.
Or open `https://photos.yourdomain.tld/upload` in a browser, manage existing
posts at `/manage`, or use one of the apps below.
## Apps
Both post to the same `/upload` API (HTTP Basic auth) and need no server change.
- **Android** (`android/`) — Kotlin/Compose. Pick or share one or many photos,
set caption/date/tags, uploads run in a durable background queue. Has its own
flake; `cd android && nix develop && ./gradlew assembleRelease`.
- **Qt desktop** (`desktop/`) — Qt6/C++. Bulk upload with configurable
concurrency, HEIC support, credentials stored in the system keychain. Builds
via its own flake in `desktop/`.