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:
1 parent
94859fb0d6
commit
e67d25b191
1 file changed
+71
-14
@@ -1,6 +1,9 @@
|
|||||||
# photogallery
|
# 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
|
## 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
|
grid as the site root). Both landed here as independent implementations
|
||||||
building on that work.
|
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
|
## How it works
|
||||||
|
|
||||||
1. Drop `photo.jpg` + `photo.toml` into the content directory (or upload via the web UI at `/upload`).
|
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.
|
2. The binary watches the directory and rebuilds the static site on any change (and on every upload/edit/delete).
|
||||||
3. nginx serves `public/` and proxies `/upload` to the local API, secured with htpasswd.
|
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
|
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.
|
and an aspect-preserved medium JPEG (longest side 1600) for the detail page.
|
||||||
@@ -30,9 +61,13 @@ editable before submitting.
|
|||||||
```toml
|
```toml
|
||||||
caption = "Tempelhof at sunrise"
|
caption = "Tempelhof at sunrise"
|
||||||
date = "2026-06-14"
|
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;
|
enable = true;
|
||||||
baseURL = "https://photos.yourdomain.tld";
|
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
|
# Optional: identity shown on the header and copyright footer
|
||||||
author = "Ada Lovelace";
|
author = "Ada Lovelace";
|
||||||
handle = "@ada@example.com";
|
handle = "@ada@example.com";
|
||||||
@@ -76,20 +119,19 @@ services.photogallery = {
|
|||||||
serifFamily = "EB Garamond";
|
serifFamily = "EB Garamond";
|
||||||
mlFamily = "Manjari";
|
mlFamily = "Manjari";
|
||||||
|
|
||||||
# Optional: built-in nginx vhost
|
# Optional: built-in nginx vhost (TLS + proxy only; auth is app-side above)
|
||||||
nginx = {
|
nginx = {
|
||||||
enable = true;
|
enable = true;
|
||||||
domain = "photos.yourdomain.tld";
|
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
|
```bash
|
||||||
nix-shell -p apacheHttpd --run \
|
nix-shell -p apacheHttpd --run \
|
||||||
"htpasswd -c /etc/nginx/.htpasswd-gallery youruser"
|
"htpasswd -B -c /run/secrets/photogallery-htpasswd youruser"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Available options
|
### Available options
|
||||||
@@ -101,6 +143,9 @@ nix-shell -p apacheHttpd --run \
|
|||||||
| `contentDir` | `/var/lib/photogallery/content` | Images and .toml sidecars |
|
| `contentDir` | `/var/lib/photogallery/content` | Images and .toml sidecars |
|
||||||
| `outputDir` | `/var/lib/photogallery/public` | Generated site output |
|
| `outputDir` | `/var/lib/photogallery/public` | Generated site output |
|
||||||
| `port` | `8080` | Upload API port (localhost only) |
|
| `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 |
|
| `author` | `Ashik Salahudeen` | Name shown on the header, photo pages and copyright footer |
|
||||||
| `handle` | `@puttaalu@inflo.ws` | Handle line under the author name |
|
| `handle` | `@puttaalu@inflo.ws` | Handle line under the author name |
|
||||||
| `bio` | `Personal photo journal.` | Primary bio line |
|
| `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 |
|
| `fontsURL` | Crimson Pro + Manjari | Stylesheet URL loading both web fonts |
|
||||||
| `serifFamily` | `Crimson Pro` | CSS font-family for the primary serif |
|
| `serifFamily` | `Crimson Pro` | CSS font-family for the primary serif |
|
||||||
| `mlFamily` | `Manjari` | CSS font-family for the alt-script bio |
|
| `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.domain` | — | Domain for the vhost |
|
||||||
| `nginx.htpasswdFile` | — | htpasswd file for `/upload` |
|
|
||||||
| `nginx.enableACME` | `true` | Enable Let's Encrypt |
|
| `nginx.enableACME` | `true` | Enable Let's Encrypt |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -141,7 +185,20 @@ curl -u user:password \
|
|||||||
-F "image=@photo.jpg" \
|
-F "image=@photo.jpg" \
|
||||||
-F "caption=Tempelhof at sunrise" \
|
-F "caption=Tempelhof at sunrise" \
|
||||||
-F "date=2026-06-14" \
|
-F "date=2026-06-14" \
|
||||||
|
-F "tags=berlin, sunrise" \
|
||||||
https://photos.yourdomain.tld/upload
|
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/`.
|
||||||
Reference in new issue
Block a user