A new auth-gated web UI at /manage lists every post for inline editing of
caption, date and tags, with per-row and bulk delete and bulk tag add/remove.
It is backed by a small JSON API (all behind the same htpasswd Basic auth as
/upload, and unlinked from the public site):
GET /api/photos list posts (newest first)
POST /api/edit rewrite one sidecar (caption, date, tags)
POST /api/tags bulk add/remove tags across posts
POST /api/delete move posts to content/.trash/ (reversible)
GET /api/trash list trashed items
POST /api/restore move a trashed item back (409 on live-slug clash)
POST /api/trash/empty hard delete trashed items (id… or all=1)
Design notes:
- Editing only rewrites the .toml; the slug (and URL) never changes.
- Delete is reversible: image + sidecar move to content/.trash/ with a
timestamp suffix, excluded from the site (loadPhotos skips dirs). The only
hard delete is emptying the trash.
- Build now prunes: it drops the derived HTML trees (photo/ day/ album/ page/
grid/page/) and regenerates them, and pruneRenditions removes orphaned
images/thumbs/medium/og. Without this a deleted post would linger at its old
URL and on stale day/album pages; this also cleans up after a hand-removed
sidecar.
- Sidecars are written atomically (temp + rename) so a concurrent build never
observes a half-written file.
- Slugs and trash ids are validated as bare filenames, closing path traversal
and dotfile access.
- Every state-changing POST passes a sameOrigin (CSRF) check, since Basic-auth
credentials are replayed by the browser on every request to the host. It
honours Sec-Fetch-Site, falls back to Origin vs base-url/Host, and allows
header-less non-browser clients. Ported from the independent /manage on
plex/trunk-ng.
module.nix proxies /manage and /api/ to the app (nginx otherwise serves them
as missing static files). The apps are unchanged; the API is there for them to
adopt edit/delete later.
Add HTTP Basic auth on /upload, reading an Apache htpasswd file (bcrypt
only, `htpasswd -B`) via a new -htpasswd flag. This lets nginx drop to a
plain TLS-terminating reverse proxy with no auth of its own; the existing
bcrypt htpasswd file is reused unchanged.
auth.go:
- LoadHtpasswd parses user:hash lines, keeps bcrypt entries ($2a/$2b/$2y,
so Apache's $2y$ works), skips MD5/SHA/crypt with a warning.
- Unknown users still pay for one bcrypt comparison against a dummy hash,
so response timing can't enumerate valid usernames.
- Failed attempts draw from a token-bucket limiter (x/time/rate, ~1/s,
burst 10) returning 429 once drained; successful and bulk uploads never
touch it.
server.go guards both /upload routes (nil authenticator = passthrough for
local --serve). /health stays open. Empty -htpasswd disables auth and logs
a warning.
module.nix: new top-level services.photogallery.htpasswdFile, delivered to
the service via systemd LoadCredential (so a root-owned 0400 secret works).
Removed nginx.htpasswdFile and basic_auth from the vhost.
Both uploader apps already send Basic credentials, so no client change.
Deps: golang.org/x/crypto/bcrypt, golang.org/x/time/rate (vendorHash bump).
The day-grouped timeline is now the site root (/, /page/N/); the flat
square grid moves to /grid/ (/grid/page/N/). Both are linked from the
profile header, separated by a divider.
Timeline pages pack whole day-groups until they reach the page size,
never splitting a day across a page boundary, so appended pages never
repeat a date heading — the infinite-scroll handler relies on this.
Page size is configurable via -page-size (default 60), threaded through
the Generator and exposed as services.photogallery.pageSize in the
NixOS module.
The profile header, photo page byline and copyright footer previously
hardcoded the author name, handle, bio, and font choices. Move all of
these to CLI flags on the binary and matching options on the NixOS
module so the same build can serve any single-user gallery.
New surface, all with defaults matching the previous hardcoded values:
--author / services.photogallery.author
--handle / services.photogallery.handle
--bio / services.photogallery.bio
--bio-alt / services.photogallery.bioAlt ("" to omit)
--fonts-url / services.photogallery.fontsURL
--serif-family / services.photogallery.serifFamily
--ml-family / services.photogallery.mlFamily
Templates read the two font families from a small inline <style> that
overrides the :root variables, so the stylesheet stays fully static and
the font URL is loaded via the configured stylesheet link.
README updated to describe the new options, the medium/full-size photo
detail flow, and the EXIF-based date prefill on the upload page.