From c75beda743dfd6af63f512e928d0889d9ead3973 Mon Sep 17 00:00:00 2001 From: Ken Date: Sat, 9 May 2026 04:41:03 +0000 Subject: =?UTF-8?q?Initial=20commit=20=E2=80=94=20Albumen=20photo=20album?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ruby/Sinatra self-hosted photo album with directory hierarchy, per-photo captions and visibility, lightbox, slideshow, admin UI, and Let's Encrypt HTTPS via Apache reverse proxy on prouter. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 README.md (limited to 'README.md') diff --git a/README.md b/README.md new file mode 100644 index 0000000..168edfd --- /dev/null +++ b/README.md @@ -0,0 +1,198 @@ +# Albumen — Photo Album Server + +Self-hosted photo/video album. Directory hierarchy = album hierarchy. Ruby/Sinatra +back end, plain HTML/CSS/JS front end. Live at **https://albumen.jots.org**. + +--- + +## Directory layout + +| Path | Purpose | +|---|---| +| `/opt/albumen/` | Application root | +| `/opt/albumen/app.rb` | Sinatra application | +| `/opt/albumen/views/` | ERB templates | +| `/opt/albumen/public/` | CSS, JS, static assets | +| `/opt/albumen/scripts/` | CLI utilities | +| `/opt/albumen/cache/thumbs/` | Auto-generated thumbnails (safe to delete/regenerate) | +| `/opt/albumen/config.yml` | Admin password hash + session secret (mode 600) | +| `/opt/albumen/log/` | Puma stdout/stderr logs | +| `/var/albumen/` | **Your photos live here** | + +Each subdirectory under `/var/albumen/` is an album. Nesting is unlimited. +The directory name is the album name; the admin UI lets you assign a prettier title. + +--- + +## Uploading photos + +Drop files into `/var/albumen/` (or any subdirectory) via `rsync` or `scp` +from your local machine: + +```bash +rsync -av ~/Pictures/2024-Italy/ root@albumen.jots.org:/var/albumen/2024-Italy/ + +# or a single file +scp photo.jpg root@albumen.jots.org:/var/albumen/2024-Italy/ +``` + +After any upload **run the update script** (see below). + +--- + +## Running the update script + +The update script walks the media tree, creates/updates `album.json` files with +EXIF dates and image dimensions, and pre-generates thumbnails. + +```bash +# On the server — process the entire tree +ruby /opt/albumen/scripts/update.rb + +# Process only one album (and its sub-albums) +ruby /opt/albumen/scripts/update.rb 2024-Italy + +# With an absolute path +ruby /opt/albumen/scripts/update.rb /var/albumen/2024-Italy +``` + +**Resilience guarantees — safe to interrupt and re-run at any point:** +- `album.json` is written atomically (temp file + rename); no partial writes. +- Thumbnails that already exist are skipped entirely. +- EXIF metadata already recorded is not re-extracted. +- Deleted files are pruned from `album.json` automatically. + +Typical workflow: + +```bash +rsync -av ~/Pictures/trip/ root@albumen.jots.org:/var/albumen/trip/ +ssh root@albumen.jots.org 'ruby /opt/albumen/scripts/update.rb trip' +``` + +--- + +## album.json reference + +Each directory gets an `album.json` created by the update script. +You can edit these by hand or through the admin UI at `/admin`. + +```jsonc +{ + "title": "Italy 2024", // overrides the directory name in the UI + "description": "Two weeks in Rome and Florence", + "cover": "DSC_0042.jpg", // which file to use as album thumbnail + "cover_dynamic": false, // true = play video/animation on hover + "visible": true, // false = hidden from non-admin users + "files": { + "DSC_0042.jpg": { + "title": "Colosseum", // shown in lightbox (defaults to filename) + "caption": "Just after sunrise, no crowds yet.", + "visible": true, + "taken_at": "2024-06-03T06:14:00", // from EXIF; set by update script + "width": 6000, + "height": 4000 + } + } +} +``` + +Fields set by the update script (`taken_at`, `width`, `height`) are not +overwritten if already present — safe to correct by hand. + +--- + +## Admin interface + +Go to `https://albumen.jots.org/admin` and log in. From there: + +- Edit album title, description, cover image, visibility +- Edit per-file title, caption, visibility +- Navigate into sub-albums + +### Changing the admin password + +```bash +ssh root@albumen.jots.org +ruby /opt/albumen/scripts/set_password.rb +``` + +The bcrypt hash is stored in `/opt/albumen/config.yml` (readable only by the +`albumen` service user). + +--- + +## Service management + +```bash +systemctl status albumen # is it running? +systemctl restart albumen # restart (e.g. after editing app.rb) +journalctl -u albumen -f # live service logs +tail -f /opt/albumen/log/puma.stdout.log # Puma access log +tail -f /opt/albumen/log/puma.stderr.log # Puma error log +``` + +The service runs as the `albumen` user. App code lives in `/opt/albumen/`. + +--- + +## Thumbnail cache + +Thumbnails are stored in `/opt/albumen/cache/thumbs/`, mirroring the media +tree. The cache is fully regenerable — delete any or all of it and the app +regenerates on demand (or run the update script to pre-generate). + +```bash +# Regenerate all thumbnails for one album +rm -rf /opt/albumen/cache/thumbs/2024-Italy +ruby /opt/albumen/scripts/update.rb 2024-Italy +``` + +--- + +## Infrastructure + +| Component | Location | Notes | +|---|---|---| +| App server | `192.168.10.245` | Puma on port 4567, nginx on 80 | +| Reverse proxy | `192.168.10.1` (prouter) | Apache, handles TLS termination | +| DNS | `mirkwood.jots.org` (209.141.48.158) | BIND 9, zone `/etc/bind/zones/jots.org.zone` | +| TLS cert | `/etc/letsencrypt/live/albumen.jots.org/` on prouter | Expires 2026-08-07; auto-renewed by certbot | +| Public URL | `https://albumen.jots.org` | → prouter → 192.168.10.245:80 | + +### Sinatra settings required for the proxy setup + +Two settings in `app.rb` are necessary when running behind an HTTPS reverse proxy: + +- `set :absolute_redirects, false` — Sinatra redirects use relative paths (`/foo`) + so the browser stays on HTTPS rather than following an `http://` Location header. +- `set :protection, except: :http_origin` — prevents Rack::Protection from + dropping the admin session when the `Origin` header's scheme (`https://`) + doesn't match the backend connection scheme (`http://`). + +### Cert renewal + +Certbot auto-renewal is managed by the system cron on prouter. To test: + +```bash +ssh root@192.168.10.1 'certbot renew --dry-run' +``` + +--- + +## Re-deploying after code changes + +```bash +# From your workstation +scp -r /home/ken/albumen/. root@albumen.jots.org:/opt/albumen/ +ssh root@albumen.jots.org 'chown -R albumen:albumen /opt/albumen && systemctl restart albumen' +``` + +--- + +## Supported file types + +| Category | Extensions | +|---|---| +| Images | jpg jpeg png gif webp heic heif tiff bmp | +| Videos | mp4 mov avi mkv webm m4v ogv | +| Audio | mp3 flac ogg wav m4a aac | -- cgit v1.2.3