From e67d25b191298f1f8461bb57b0a80c08ddb8cbe4 Mon Sep 17 00:00:00 2001 From: puttaalu Date: Sun, 18 Oct 2026 23:16:26 +0200 Subject: [PATCH] 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. --- README.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f0643ec..f832d4d 100644 --- a/README.md +++ b/README.md @@ -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//`) linked from each timeline date heading. +- **Albums**: tag photos with free-text, lowercase tags; each tag is an album at `/album//`, 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"; + enable = true; + domain = "photos.yourdomain.tld"; }; }; ``` -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/`.