/manage lists every photo; /manage/edit/{slug} rewrites the photo's
.toml sidecar (caption, date), /manage/delete/{slug} asks for
confirmation and then removes the image, its sidecar and everything
generated from them. Builds now drop day pages of days without photos
and pager pages past the last one, so a delete or a date change leaves
nothing behind.
The pages are off by default: the routes are only registered with
-manage, so an install that does not opt in has no such code path and
answers 404. NixOS gets services.photogallery.manage.enable, which
also adds the nginx location only when on. Like /upload, the pages
carry no auth of their own and are not linked from the public site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
164 lines
6.3 KiB
Markdown
164 lines
6.3 KiB
Markdown
# photogallery
|
||
|
||
A minimal static photo gallery with an Atom feed and an upload API.
|
||
|
||
## 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.
|
||
|
||
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.
|
||
The original file stays reachable from a download link on the detail page.
|
||
Regeneration is skipped when the destination is at least as new as the source,
|
||
so editing one sidecar doesn't reprocess every photo.
|
||
|
||
The upload page reads `DateTimeOriginal` from a JPEG's EXIF (or falls back to
|
||
the file's modification time) and prefills the date field, which stays
|
||
editable before submitting.
|
||
|
||
## Sidecar format
|
||
|
||
```toml
|
||
caption = "Tempelhof at sunrise"
|
||
date = "2026-06-14"
|
||
```
|
||
|
||
If no `.toml` is present the filename is used as the caption and today's date as the date.
|
||
|
||
---
|
||
|
||
## Using as a NixOS flake module
|
||
|
||
### 1. Add to your flake inputs
|
||
|
||
```nix
|
||
inputs = {
|
||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||
photogallery.url = "github:youruser/photogallery";
|
||
};
|
||
```
|
||
|
||
### 2. Import the module
|
||
|
||
```nix
|
||
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
|
||
modules = [
|
||
inputs.photogallery.nixosModules.default
|
||
./configuration.nix
|
||
];
|
||
};
|
||
```
|
||
|
||
### 3. Configure
|
||
|
||
```nix
|
||
services.photogallery = {
|
||
enable = true;
|
||
baseURL = "https://photos.yourdomain.tld";
|
||
|
||
# Optional: identity shown on the header and copyright footer
|
||
author = "Ada Lovelace";
|
||
handle = "@ada@example.com";
|
||
bio = "Photos from the field.";
|
||
bioAlt = ""; # optional secondary line in the alt-script font
|
||
|
||
# Optional: swap fonts
|
||
fontsURL = "https://fonts.googleapis.com/css2?family=EB+Garamond:wght@400;600;700&family=Manjari:wght@400;700&display=swap";
|
||
serifFamily = "EB Garamond";
|
||
mlFamily = "Manjari";
|
||
|
||
# Optional: edit and delete published photos at /manage (off by default)
|
||
manage.enable = true;
|
||
|
||
# Optional: built-in nginx vhost
|
||
nginx = {
|
||
enable = true;
|
||
domain = "photos.yourdomain.tld";
|
||
htpasswdFile = "/etc/nginx/.htpasswd-gallery";
|
||
};
|
||
};
|
||
```
|
||
|
||
Create the htpasswd file:
|
||
|
||
```bash
|
||
nix-shell -p apacheHttpd --run \
|
||
"htpasswd -c /etc/nginx/.htpasswd-gallery youruser"
|
||
```
|
||
|
||
### Available options
|
||
|
||
| Option | Default | Description |
|
||
|-----------------------------|------------------------------------|--------------------------------------|
|
||
| `enable` | `false` | Enable the service |
|
||
| `baseURL` | — | Public URL, no trailing slash |
|
||
| `contentDir` | `/var/lib/photogallery/content` | Images and .toml sidecars |
|
||
| `outputDir` | `/var/lib/photogallery/public` | Generated site output |
|
||
| `port` | `8080` | Upload API port (localhost only) |
|
||
| `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 |
|
||
| `bioAlt` | Malayalam default | Secondary bio line in the alt-script font (`""` to omit) |
|
||
| `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 |
|
||
| `pageSize` | `60` | Photos per page on the timeline and grid views |
|
||
| `defaultView` | `timeline` | View at the site root, `timeline` or `grid`; the other is served under `/timeline/` or `/grid/` |
|
||
| `manage.enable` | `false` | Serve the `/manage` pages (edit, delete) and, with `nginx.enable`, protect them with the same htpasswd |
|
||
| `nginx.enable` | `false` | Configure an nginx vhost |
|
||
| `nginx.domain` | — | Domain for the vhost |
|
||
| `nginx.htpasswdFile` | — | htpasswd file for `/upload` and `/manage` |
|
||
| `nginx.enableACME` | `true` | Enable Let's Encrypt |
|
||
|
||
---
|
||
|
||
## Bootstrap (first build)
|
||
|
||
`buildGoModule` needs a `go.sum` and the correct `vendorHash`.
|
||
|
||
```bash
|
||
# 1. Generate go.sum
|
||
go mod tidy
|
||
|
||
# 2. Commit go.sum
|
||
git add go.sum && git commit -m "add go.sum"
|
||
|
||
# 3. Get the vendorHash — this will fail and print the correct hash
|
||
nix build
|
||
# error: hash mismatch ... got: sha256-XXXXX...
|
||
|
||
# 4. Paste the hash into flake.nix, then build again
|
||
nix build
|
||
```
|
||
|
||
## Uploading via curl
|
||
|
||
```bash
|
||
curl -u user:password \
|
||
-F "image=@photo.jpg" \
|
||
-F "caption=Tempelhof at sunrise" \
|
||
-F "date=2026-06-14" \
|
||
https://photos.yourdomain.tld/upload
|
||
```
|
||
|
||
Or open `https://photos.yourdomain.tld/upload` in a browser.
|
||
|
||
## Editing and deleting photos
|
||
|
||
The pages are off by default: start the binary with `-manage` (NixOS:
|
||
`services.photogallery.manage.enable = true`). Then open
|
||
`https://photos.yourdomain.tld/manage` in a browser. It lists every
|
||
photo; *Edit* opens a form for the caption and date, and saving rewrites the
|
||
photo's `.toml` sidecar, which triggers a rebuild.
|
||
|
||
*Delete* asks for confirmation, then removes the image, its sidecar and
|
||
everything generated from them. This is permanent — there is no trash folder,
|
||
so keep a backup of the content directory.
|
||
|
||
`/manage` sits behind the same htpasswd as `/upload`, and nothing on the
|
||
public site links to it. If you run your own nginx instead of the NixOS
|
||
module, add a `location /manage` block alongside the `/upload` one — without
|
||
it the path is not proxied and returns 404.
|