diff --git a/README.md b/README.md index 91a29557..76aae2e6 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,48 @@ -# 📸 PicPeak - Open Source Photo Sharing for Events - -> [!IMPORTANT] -> **PicPeak has moved to its own GitHub organization.** -> -> - **Docker images** are now published at `ghcr.io/picpeak/picpeak/{backend,frontend}`. Update your `docker-compose.yml`. -> - ⚠️ The old path (`ghcr.io/the-luap/picpeak/...`) **still responds, but its tags are frozen** at 2026-05-27. `docker compose pull` succeeds and hands back the same build every time, so an out-of-date install looks like a broken download rather than a dead path. If PicPeak keeps reporting an update that never arrives, check your image path first. -> - **Branches**: active development is now on `main` (was `beta`); the curated stable channel is now `stable` (was `main`). Existing PRs and clones auto-redirect via GitHub. -> -> See **[`docs/migration-to-org.md`](docs/migration-to-org.md)** for the one-line `docker-compose.yml` edit and full details. -
PicPeak Logo - + + # 📸 PicPeak + + **Open-source, self-hosted photo sharing for events.** + [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=flat&logo=docker&logoColor=white)](https://www.docker.com/) - [![Node.js](https://img.shields.io/badge/node.js-6DA55F?style=flat&logo=node.js&logoColor=white)](https://nodejs.org/) - [![React](https://img.shields.io/badge/react-%2320232a.svg?style=flat&logo=react&logoColor=%2361DAFB)](https://reactjs.org/) [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-theluap-FFDD00?logo=buymeacoffee&logoColor=black)](https://buymeacoffee.com/theluap) - [Homepage](https://www.picpeak.app) · [Live Demo](https://demo.picpeak.app) · [Documentation](https://docs.picpeak.app) · [Support the project ☕](https://buymeacoffee.com/theluap) + [Homepage](https://www.picpeak.app) · [Live Demo](https://demo.picpeak.app) · [Documentation](https://docs.picpeak.app) · [Support ☕](https://buymeacoffee.com/theluap)
-**PicPeak** is a powerful, self-hosted open-source alternative to commercial photo-sharing platforms like PicDrop.com and Scrapbook.de. Designed specifically for photographers and event organizers, PicPeak makes it simple to share beautiful, time-limited photo galleries with clients while maintaining full control over your data and branding. +--- + +**PicPeak** is a powerful, self-hosted open-source alternative to commercial photo-sharing platforms like PicDrop.com and Scrapbook.de. Built for photographers and event organizers, it makes it simple to share beautiful, time-limited photo galleries with clients while keeping full control over your data and branding. ![PicPeak Gallery Preview](docs/screenshot-gallery.png) +> [!IMPORTANT] +> **PicPeak has moved to its own GitHub organization.** Docker images are now at `ghcr.io/picpeak/picpeak/{backend,frontend}` and active development is on `main`. The old `ghcr.io/the-luap/...` path still responds but its tags are **frozen** at 2026-05-27 — if updates never arrive, check your image path first. See **[`docs/migration-to-org.md`](docs/migration-to-org.md)** for the one-line `docker-compose.yml` edit. + +## Contents + +- [Live Demo](#-live-demo) +- [Quick Start](#-quick-start) +- [Why PicPeak?](#-why-picpeak) +- [Features](#-features) +- [Documentation](#-documentation) +- [Comparison](#-comparison-with-alternatives) +- [Tech Stack](#️-tech-stack) +- [Contributing & Support](#-contributing) +- [License](#-license) + ## 🎮 Live Demo -Try PicPeak without installing anything: +Try PicPeak without installing anything — [demo.picpeak.app](https://demo.picpeak.app) · [admin panel](https://demo.picpeak.app/admin) -| | | +| Email | Password | |---|---| -| **Demo URL** | [demo.picpeak.app](https://demo.picpeak.app) | -| **Admin Panel** | [demo.picpeak.app/admin](https://demo.picpeak.app/admin) | -| **Email** | `demo@picpeak.app` | -| **Password** | `Demo2026!` | +| `demo@picpeak.app` | `Demo2026!` | > The demo resets periodically. Uploaded content may be removed without notice. -## 🌟 Why Choose PicPeak? - -Unlike expensive SaaS solutions, PicPeak gives you: - -- **💰 No Monthly Fees** - One-time setup, unlimited galleries -- **🔒 Complete Data Control** - Your photos stay on your server -- **🎨 White-Label Ready** - Full branding customization -- **📱 Mobile-First Design** - Beautiful on all devices -- **🚀 Lightning Fast** - Optimized performance and caching -- **🌍 Multi-Language** - Built-in i18n support (EN, DE) - -## ✨ Key Features - -### For Photographers -- 📁 **Drag & Drop Upload** - Simply drop photos into folders -- 🔗 **External Media (Reference Mode)** - Browse and import from a read‑only external folder library without copying originals -- ⏰ **Auto-Expiring Galleries** - Set expiration dates (default: 30 days) -- 🔐 **Password Protection** - Secure client galleries -- 📧 **Automated Emails** - Creation confirmations and expiration warnings -- 📊 **Analytics Dashboard** - Track views, downloads, and engagement -- 📽️ **Live Slideshow** - A separate fullscreen "Diashow" link per event for projectors at live events — auto-picks-up new uploads while it runs, with transitions, a logo watermark, and image-fit/colour options ([guide](docs/live-slideshow.md)) -- 🎨 **Custom Themes** - Match your brand perfectly -- 🌐 **Public Landing Page** - Publish a curated marketing page when guests visit your root URL - -### For Clients -- 🖼️ **Beautiful Galleries** - Clean, modern interface -- 📱 **Mobile Optimized** - Swipe through photos on any device -- ⬇️ **Bulk Downloads** - Download all photos with one click -- 🔍 **Smart Search** - Find photos quickly -- 📤 **Guest Uploads** - Optional client photo uploads -- 🛡️ **Download Protection** - Advanced image protection with watermarking and right-click prevention - -### Technical Excellence -- 🐳 **Docker Ready** - Deploy in minutes -- 🔄 **Auto-Processing** - Automatic thumbnail generation -- 🗂️ **Reference Library Support** - Point PicPeak at `EXTERNAL_MEDIA_ROOT` to reference existing originals, index quickly, and generate thumbnails on demand -- 💾 **Smart Storage** - Automatic archiving of expired galleries -- 🛡️ **Security First** - JWT auth, rate limiting, CORS protection -- 📈 **Scalable** - From small studios to large agencies - -### For Studios — CRM & Accounting (Beta · off by default) -- 📝 **Quotes → Contracts → Invoices** - One deal lineage; cancel-and-reissue (Storno) keeps issued invoices immutable -- ⏱️ **Hours Logging & Calendar** - Per-customer time tracking; admin calendar of events, logged hours, and pending quotes/contracts -- 🧾 **Inbound Supplier Invoices & Expenses** - Capture received invoices (upload/camera, rasterised server-side), categorise, and re-bill costs to clients -- 📊 **Tax Report & Accountant Export** - Period-scoped income/cost report with VAT breakdown; PDF/CSV plus a Treuhänder/Banana (Swiss/LI) journal export, scopable to income-only or cost-only -- 🌍 **VAT & Multi-currency** - Single VAT-code registry snapshotted onto each document; data-driven per-country rates -- ⚠️ **Verify locally** - Feature-flagged off by default. Seeded contracts, QR/IBAN and tax defaults are **examples only** — review your own legal **and tax** regulations first (see disclaimers below) - ## 🚀 Quick Start Get PicPeak running in under 5 minutes: @@ -97,8 +54,8 @@ cd picpeak # Copy the environment template — the defaults work out of the box. # Machine secrets (JWT, DB, Redis) are auto-generated on first run, and the -# admin account is created in the browser (see below). Edit .env only to -# customise (domain, SMTP, storage paths, …) — nothing is required. +# admin account is created in the browser. Edit .env only to customise +# (domain, SMTP, storage paths, …) — nothing is required. cp .env.example .env # Start with Docker Compose @@ -107,293 +64,61 @@ docker compose up -d # Access at http://localhost:3000 ``` -### First run — create your admin account +On first start, open **http://localhost:3000/admin** and follow the in-browser setup to create your admin account. Full details — the one-time setup token, Docker file permissions, and ARM64 notes — are in **[First-run setup](docs/_to-migrate/first-run-setup.md)**. -On first start with no `ADMIN_PASSWORD` set, PicPeak has **no admin account yet** and greets you with an in-browser setup screen — no credentials in `.env`: +> **Updating / release channels:** set `PICPEAK_CHANNEL` (`stable` default, or `beta`) in `.env`, then `docker compose pull && docker compose up -d`. See [RELEASING.md](RELEASING.md) for the promotion cadence. -1. Open **http://localhost:3000/admin** — you'll be redirected to `/setup`. -2. Read the **one-time setup token** from the 0600 file the backend writes it to - (it is deliberately *not* printed to the logs — that would leave a live - bootstrap credential in `docker logs`): - ```bash - docker compose exec backend cat /app/data/SETUP_TOKEN - ``` - It is bind-mounted, so `sudo cat data/SETUP_TOKEN` on the host works too. Only - if that file could not be written does the backend fall back to logging the - token (`docker compose logs backend | grep -i "setup token"`). -3. Paste the token, set your admin **email + password**, and you're in. The token is single-use, and the setup screen closes permanently once an admin exists. +## 🌟 Why PicPeak? -> Prefer the old behaviour? Set `ADMIN_PASSWORD` in `.env` and PicPeak auto-creates the admin on first boot instead (credentials written to `data/ADMIN_CREDENTIALS.txt`). +Unlike expensive SaaS solutions, PicPeak gives you: -Note on Docker file permissions -- The backend container starts as root, chowns bind-mounted host directories (`./storage`, `./data`, `./logs`) to UID 1001 (`nodejs`), then drops privileges via `su-exec` before running the app. No host-side setup needed for fresh installs. -- If you pin `user:` in a compose override (e.g. to map a specific host UID), the self-chown is skipped and you must pre-chown the host directories to that UID — see [docs.picpeak.app/deployment/docker#permissions](https://docs.picpeak.app/deployment/docker#permissions). +- **💰 No Monthly Fees** — one-time setup, unlimited galleries +- **🔒 Complete Data Control** — your photos stay on your server +- **🎨 White-Label Ready** — full branding customization +- **📱 Mobile-First Design** — beautiful on all devices +- **🌍 Multi-Language** — built-in i18n (EN, DE) -**ARM64 (aarch64) systems:** Pre-built images include native `linux/arm64`, no platform flags or emulation needed. If you're on an older image tag that's still amd64-only, see [docker-compose.amd64.override.yml](docker-compose.amd64.override.yml) for a transitional fallback. +## ✨ Features -## 🔄 Release Channels +**For photographers** — drag & drop upload, auto-expiring & password-protected galleries, automated emails, an analytics dashboard, custom themes, a public landing page, and a [Live Slideshow](docs/live-slideshow.md) projector view that auto-picks-up new uploads during live events. -PicPeak offers two release channels for different needs. Stable promotions are cut from a known-good beta point every 4–6 weeks — see [RELEASING.md](RELEASING.md) for the maintainer's promotion criteria and cadence policy. +**For clients** — clean mobile-optimized galleries, one-click bulk downloads, smart search, optional guest uploads, and download protection (watermarking + right-click prevention). -### Stable Channel (Recommended) -- Production-ready releases -- Thoroughly tested before release -- Docker tags: `stable`, `latest`, or specific version like `v2.3.0` +**Technical** — Docker-ready, automatic thumbnail generation, external media reference mode, smart archiving of expired galleries, S3-compatible [storage backends](docs/_to-migrate/storage-backends.md), [webhooks](docs/_to-migrate/webhooks.md), and security-first defaults (JWT, rate limiting, CORS). -### Beta Channel -- Early access to new features -- May contain bugs or incomplete functionality -- Docker tags: `beta` or specific version like `v2.3.0-beta.1` +
+🧾 For studios — CRM & Accounting (Beta, off by default) -### Switching Channels +- 📝 **Quotes → Contracts → Invoices** — one deal lineage; cancel-and-reissue (Storno) keeps issued invoices immutable +- ⏱️ **Hours Logging & Calendar** — per-customer time tracking; admin calendar of events, logged hours, and pending quotes/contracts +- 🧾 **Inbound Supplier Invoices & Expenses** — capture received invoices (upload/camera, rasterised server-side), categorise, and re-bill costs to clients +- 📊 **Tax Report & Accountant Export** — period-scoped income/cost report with VAT breakdown; PDF/CSV plus a Treuhänder/Banana (Swiss/LI) journal export +- 🌍 **VAT & Multi-currency** — single VAT-code registry snapshotted onto each document -Set the `PICPEAK_CHANNEL` environment variable in your `.env` file: +
-```bash -# For stable releases (default) -PICPEAK_CHANNEL=stable - -# For beta releases -PICPEAK_CHANNEL=beta - -# For a specific version -PICPEAK_CHANNEL=v2.3.0 -``` - -Then update your containers: - -```bash -docker compose -f docker-compose.production.yml pull -docker compose -f docker-compose.production.yml up -d -``` - -### Update Notifications - -The admin dashboard automatically notifies you when updates are available for your channel. To disable update checks, set: - -```bash -UPDATE_CHECK_ENABLED=false -``` +> [!WARNING] +> **CRM & Accounting — examples only, verify locally.** Feature-flagged off by default. Seeded contract blocks are written by the maintainer, **not a lawyer**; QR-bills/SEPA payloads and every tax, VAT and Treuhänder/Banana figure are computed from your input and defaults and are **jurisdiction-specific guidance only**. Have your lawyer review contracts, scan a test QR with your bank's app, and verify all numbers with your accountant / Treuhänder / tax authority before customer-facing use. Read **[docs/crm-disclaimers.md](docs/crm-disclaimers.md)** first. ## 📖 Documentation -Full documentation lives at **[docs.picpeak.app](https://docs.picpeak.app)** — deployment, admin settings reference, API docs, webhooks, archive lifecycle, branding, and everything else. Some quick links: +Full documentation lives at **[docs.picpeak.app](https://docs.picpeak.app)** — deployment, admin settings, API, branding, and more. -- 🚀 [**Deployment**](https://docs.picpeak.app/deployment) - Docker, environment variables, reverse proxy, SSL -- ⚙️ [**Admin Settings**](https://docs.picpeak.app/guides/admin-settings) - Every tab in the Settings panel -- 🎯 [**Creating Events**](https://docs.picpeak.app/guides/creating-events) - Full event field reference -- 📽️ [**Live Slideshow**](https://docs.picpeak.app/features/live-slideshow) - Fullscreen projector view that auto-updates during live events -- 💾 [**Backup & Restore**](https://docs.picpeak.app/guides/backup-restore) - Backup configuration, restore wizard, full disaster recovery -- 🔌 [**API Reference**](https://docs.picpeak.app/api) - REST endpoints, OpenAPI spec, webhooks -- 🪝 [**Webhooks**](https://docs.picpeak.app/features/webhooks) - Event payloads, signing, filters, templates - -Project meta: - -- 🤝 [**Contributing**](CONTRIBUTING.md) - How to contribute -- 📜 [**License**](LICENSE) - MIT License -- 🔒 [**Security**](SECURITY.md) - Security policies -- 📋 [**Code of Conduct**](CODE_OF_CONDUCT.md) - Community guidelines - -## 🌐 Public Landing Page - -Spotlight your studio with a customizable marketing page at `/`: - -- Head to **Admin → CMS Pages** to enable the public landing page toggle. -- Edit the provided HTML template (rich sections, hero, testimonials) and optional CSS overrides. -- The preview renders in a sandboxed iframe so you can iterate safely before publishing. -- PicPeak sanitizes stored HTML and CSS server-side—scripts, iframes, and unsafe attributes are stripped automatically. -- Use **Reset to default** anytime to restore the bundled template. -- The backend caches the rendered landing page for 60 seconds by default; override with `PUBLIC_SITE_CACHE_TTL_MS` if you need a different TTL. -- When the landing page is disabled PicPeak continues to serve the admin SPA/login exactly as before. - -## 🎯 Use Cases - -Perfect for: -- 💒 **Wedding Photographers** - Share ceremony photos securely -- 🎂 **Event Photography** - Birthday parties, corporate events -- 📸 **Portrait Studios** - Client galleries with download limits -- 🏢 **Corporate Events** - Internal photo sharing with branding -- 🎓 **School Photography** - Secure parent access with expiration -- 📽️ **Live Events** - Put a [Live Slideshow](docs/live-slideshow.md) on the venue projector that updates as you shoot - -## 🏗️ Tech Stack - -- **Backend**: Node.js, Express, SQLite/PostgreSQL -- **Frontend**: React, Tailwind CSS, Framer Motion -- **Storage**: Local filesystem (default) or S3-compatible object store (AWS S3, MinIO, R2, B2, Wasabi, Spaces) — see [Storage Backends](#storage-backends) -- **Email**: SMTP with customizable templates -- **Analytics**: Privacy-focused with Umami integration - -## 💾 Storage Backends - -PicPeak supports two storage backends for photos, thumbnails, hero images, watermarks, and archive zips. Both are configured via environment variables; no code change is required to switch. - -| Capability | `STORAGE_BACKEND=local` (default) | `STORAGE_BACKEND=s3` | -|---|---|---| -| Photo / thumbnail / hero storage | Local filesystem under `STORAGE_PATH` | Bucket on any S3-compatible service | -| Admin UI upload | ✅ | ✅ | -| Filesystem auto-import (chokidar watcher) | ✅ | ❌ — disabled (use the upload API) | -| Watermarks, fingerprinting, fragmentation | ✅ | ✅ (materialized to a tmp file just-in-time) | -| Bulk download zips (cached + on-the-fly) | ✅ | ✅ | -| Backups | ✅ | ✅ | -| External media reference mode (`EXTERNAL_MEDIA_ROOT`) | ✅ (always local) | ✅ (still local — not migrated) | - -### Switching to an S3-compatible backend - -1. Provision a bucket and credentials. The minimum IAM policy is documented in `.env.example`. -2. Set `STORAGE_BACKEND=s3` plus `STORAGE_S3_BUCKET`, `STORAGE_S3_REGION`, `STORAGE_S3_ACCESS_KEY`, `STORAGE_S3_SECRET_KEY`. For non-AWS providers (MinIO, R2, B2, …) also set `STORAGE_S3_ENDPOINT`. -3. If you have existing local content, copy it first: `node backend/scripts/migrate-storage.js --dry-run` then `node backend/scripts/migrate-storage.js`. The script is idempotent and writes a failures CSV. -4. Restart the backend. The startup check pings the bucket and refuses to boot on misconfig. - -Note: presigned-URL serving (zero-bandwidth direct downloads from S3) is intentionally **not** in v1 — every request still streams through the backend so watermarks, devtools-detection, and access logging keep working. - -## 🔔 Webhooks - -PicPeak POSTs event/photo lifecycle notifications to URLs you configure under **Settings → Webhooks**. Each delivery is signed `HMAC-SHA256` with a per-webhook secret in the `X-PicPeak-Signature` header so receivers can verify the request really came from your PicPeak instance. - -### Event types - -| Event | Fires when | +| Topic | Link | |---|---| -| `event.created` | Gallery created (admin or API) | -| `event.published` | Draft becomes live (`is_draft: true → false`) — also fires when an event is created with `is_draft=false` | -| `event.archived` | Bulk-archive, manual archive, or auto-archive on expiry | -| `event.expired` | Expiration checker marks the gallery inactive (fires before `event.archived` in the cascade) | -| `photo.uploaded` | Admin upload, API upload, guest upload, or auto-import | -| `photo.deleted` | Single delete, bulk delete (NOT fired per-photo when an event is archived — receivers infer from `event.archived` to avoid flooding) | +| 🚀 Deployment (Docker, env, reverse proxy, SSL) | [docs.picpeak.app/deployment](https://docs.picpeak.app/deployment) | +| ⚙️ Admin settings reference | [docs.picpeak.app/guides/admin-settings](https://docs.picpeak.app/guides/admin-settings) | +| 🎯 Creating events | [docs.picpeak.app/guides/creating-events](https://docs.picpeak.app/guides/creating-events) | +| 📽️ Live Slideshow | [docs/live-slideshow.md](docs/live-slideshow.md) | +| 💾 Backup & Restore | [docs/backup-restore.md](docs/backup-restore.md) | +| 🔌 API reference | [docs.picpeak.app/api](https://docs.picpeak.app/api) | +| 🪝 Webhooks | [docs/_to-migrate/webhooks.md](docs/_to-migrate/webhooks.md) | +| 💾 Storage backends (local / S3) | [docs/_to-migrate/storage-backends.md](docs/_to-migrate/storage-backends.md) | +| 💻 System requirements & tuning | [docs/_to-migrate/system-requirements.md](docs/_to-migrate/system-requirements.md) | +| 🧾 CRM & Accounting | [docs.picpeak.app/features/crm](https://docs.picpeak.app/features/crm) · [disclaimers](docs/crm-disclaimers.md) | +| 🗺️ Roadmap | [docs/_to-migrate/roadmap.md](docs/_to-migrate/roadmap.md) | -### Payload shape - -```json -{ - "id": "delivery-uuid", - "type": "event.published", - "created_at": "2026-04-28T05:25:00.000Z", - "data": { - "event": { "id": 123, "slug": "wedding-smith", "share_url": "https://..." } - } -} -``` - -Also sent on every request: -- `X-PicPeak-Signature` — `HMAC-SHA256(secret, raw_body)` as hex -- `X-PicPeak-Event` — the event type (handy for routing without parsing the body) -- `X-PicPeak-Delivery` — UUID for idempotency on the receiver side -- `User-Agent: PicPeak-Webhooks/1.0` - -### Verifying signatures - -**Node.js** -```js -const crypto = require('crypto'); -function verify(secret, rawBody, signature) { - const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); - const a = Buffer.from(expected, 'hex'); - const b = Buffer.from(signature, 'hex'); - if (a.length !== b.length) return false; - return crypto.timingSafeEqual(a, b); -} -``` - -**Python** -```python -import hmac, hashlib -def verify(secret: str, raw_body: bytes, signature: str) -> bool: - expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() - return hmac.compare_digest(expected, signature) -``` - -**curl + openssl** (one-liner for a quick replay) -```sh -SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}') -[ "$SIG" = "$RECEIVED_SIG" ] && echo OK || echo MISMATCH -``` - -### Retries + observability - -- `2xx` → success, recorded with latency -- Non-`2xx` or network error → exponential backoff: `1m → 5m → 30m → 2h → 12h`, max 5 attempts -- After max attempts: status `failed`, surfaces in **Settings → Webhooks → Deliveries** with a "Replay" button -- Up to 5 deliveries in flight at once; one slow consumer can't block others (configurable via `WEBHOOK_DELIVERY_CONCURRENCY`) -- Response body truncated to 1KB before storage so chatty receivers don't bloat the audit log - -The deliveries page (`/admin/webhooks/:id/deliveries`) shows every attempt with timestamp, status, HTTP code, latency, payload sent, signature, and response. Click "Send test event" to fire a synthetic delivery for any event type. - -### SSRF protection - -Webhook URLs are validated against the same private-IP blocklist used elsewhere in the app — loopback, private RFC1918 ranges, link-local, `.local`/`.internal` hostnames, cloud metadata endpoints. The check runs both at create time and per-delivery (DNS-rebinding mitigation). - -For local development with a receiver on the same machine or docker network, set `WEBHOOK_ALLOW_PRIVATE_URLS=true`. Production deployments must leave this OFF. - -## 💻 System Requirements - -### Minimum Requirements -- **CPU**: 2 CPU cores -- **RAM**: **4 GB minimum** for a normal photo-upload workload — sharp/libvips - decodes the full uncompressed frame before resize, and the default two - worker loops at sharp-concurrency 2 can push peak RSS past 1.5 GB on a - batch of 20-MP+ photos. On a 2 GB VPS that's enough to OOM-kill the - backend mid-batch (surfaces as 503s on thumbnails — see [Low-memory - hosts](#low-memory-hosts) below for the recipe to run on 2 GB). -- **Storage**: 20GB minimum (plus photo storage needs) -- **OS**: Linux (Ubuntu 20.04+), macOS, or Windows with WSL2 -- **Node.js**: v18.0.0 or higher -- **Database**: SQLite (included) or PostgreSQL 12+ - -### Docker Requirements (Recommended) -- **Docker**: v20.10.0+ -- **Docker Compose**: v2.0.0+ - -### Low-memory hosts - -Running on 2 GB RAM (e.g. an entry-level VPS) is workable but requires -tuning the upload-processor concurrency down. The backend auto-detects -total RAM at startup via `os.totalmem()` — on a host that reports < 3 GB, -it defaults `UPLOAD_PROCESSOR_CONCURRENCY` to **1** instead of 2 and logs -a one-shot warning. You can pin the value explicitly in `.env`: - -```env -# Single worker loop — slower batch processing, lower peak RSS -UPLOAD_PROCESSOR_CONCURRENCY=1 -``` - -The trade-off is throughput: a single worker processes one photo at a -time, so a 100-photo batch takes ~2× as long but won't OOM. **Health-check -note**: if the backend dies under memory pressure, the gallery serves -`503 Service Unavailable` on thumbnails until Docker's -`restart: unless-stopped` brings the container back. Persistent 503s -during/after an upload batch on a low-memory host are almost always this. - -### Video Support Requirements -When enabling video uploads, consider these additional resources: - -| Resource | Recommendation | Notes | -|----------|----------------|-------| -| **RAM** | 4GB+ recommended | FFmpeg processing requires more memory | -| **Storage** | Plan for 10-100x more | Videos are significantly larger than images | -| **CPU** | Additional cores help | Video thumbnail extraction is CPU-intensive | -| **Bandwidth** | Higher throughput | Video streaming requires more bandwidth | - -**Technical Notes:** -- FFmpeg is bundled via npm (`@ffmpeg-installer/ffmpeg`) - no system installation required -- Maximum upload size: **10GB per video file** -- Chunked upload support for files >100MB (resumable uploads) -- Supported formats: MP4, WebM, MOV, AVI -- Video thumbnails are automatically generated from the first few seconds - -**For Nginx/Reverse Proxy:** -If using Nginx, increase the client max body size: -```nginx -client_max_body_size 10G; -proxy_read_timeout 3600; -proxy_send_timeout 3600; -``` - -## 🤝 Contributing - -We love contributions! PicPeak is built by photographers, for photographers. Whether you're fixing bugs, adding features, or improving documentation, your help is welcome. - -See our [Contributing Guide](CONTRIBUTING.md) for details. +**Project meta:** [Contributing](CONTRIBUTING.md) · [License](LICENSE) · [Security](SECURITY.md) · [Code of Conduct](CODE_OF_CONDUCT.md) ## 📊 Comparison with Alternatives @@ -410,168 +135,79 @@ See our [Contributing Guide](CONTRIBUTING.md) for details. | Quotes / Contracts / Invoices | 🧪 Beta | ❌ | ❌ | ✅ | | Incoming Invoices & Accounting | 🧪 Beta | ❌ | ❌ | ❌ | -*You still bring your own server (own hardware or a VPS) and, if you want one, a domain. -**Limited only by your server storage. -***Pixieset's "unlimited" is photos only; video is capped by plan (roughly 0–10 h depending on tier). -🧪 Beta = built but feature-flagged off by default (see [Beta Features](#-beta-features-use-at-your-own-risk)). +*You bring your own server and, optionally, a domain. **Limited only by your server storage. ***Pixieset's "unlimited" is photos only; video is capped by plan. 🧪 Beta = built but feature-flagged off by default. -## 🛡️ Security +## 🏗️ Tech Stack -PicPeak takes security seriously: -- 🔐 Password hashing with bcrypt -- 🎫 JWT-based authentication -- 🚦 Rate limiting on all endpoints -- 🛡️ CORS protection -- 📝 Activity logging -- 🔒 Secure file access - -Found a security issue? Please open a [security issue](https://github.com/PicPeak/picpeak/issues/new?labels=security) on GitHub +- **Backend**: Node.js, Express, SQLite/PostgreSQL +- **Frontend**: React, Tailwind CSS, Framer Motion +- **Storage**: Local filesystem (default) or S3-compatible object store (AWS S3, MinIO, R2, B2, Wasabi, Spaces) — see [Storage Backends](docs/_to-migrate/storage-backends.md) +- **Email**: SMTP with customizable templates +- **Analytics**: Privacy-focused with Umami integration +- **External media**: point PicPeak at `EXTERNAL_MEDIA_ROOT` to reference existing originals read-only, index quickly, and generate thumbnails on demand ## 📸 Screenshots -### 🎛️ **Admin Dashboard** -Get a complete overview of your photo galleries, analytics, and system status. +
+Click to see the admin dashboard, analytics, and event management +### 🎛️ Admin Dashboard PicPeak Admin Dashboard -### 📊 **Analytics & Insights** -Track gallery performance, view statistics, and monitor user engagement. - +### 📊 Analytics & Insights PicPeak Analytics Dashboard -### 📁 **Event Management** -Organize and manage your photo galleries with intuitive event management tools. - +### 📁 Event Management PicPeak Events Management -### ✨ **Key Interface Highlights** - -
-👆 Click to see more interface details - -#### What makes PicPeak's interface special: - -- **🎨 Clean Design**: Modern, photographer-friendly interface -- **📱 Responsive**: Perfect on desktop, tablet, and mobile -- **⚡ Fast Loading**: Optimized for quick photo browsing -- **🔒 Secure Access**: Password-protected galleries with expiration -- **📤 Easy Uploads**: Drag & drop functionality for effortless photo management -- **🎯 Client-Focused**: Intuitive gallery experience for your clients -
-## 🗺️ Roadmap +## 🤝 Contributing -We're constantly improving PicPeak and welcome contributions from our community! If you have ideas for new features or want to help implement existing ones, please open an issue or submit a pull request. Your contributions help make PicPeak better for everyone. +We love contributions! PicPeak is built by photographers, for photographers — whether you're fixing bugs, adding features, or improving docs. See the [Contributing Guide](CONTRIBUTING.md) to get started. -### 🚧 Beta Features (Use at your own risk) - -These features are currently in beta testing and may have limited functionality or stability: - -| Feature | Description | Status | -|---------|-------------|--------| -| **CRM & Accounting Module** | Quotes, contracts, invoices (+ Storno), hours logging, calendar, and tax report — plus inbound supplier-invoice capture, internal expenses, and a Treuhänder/Banana (Swiss/LI) accountant-journal export. Feature-flagged off by default. Seeded contract blocks, payment terms, IBAN / QR-bill and tax defaults are **examples only** and need legal / financial / **tax** review before customer-facing use. See [docs.picpeak.app/features/crm](https://docs.picpeak.app/features/crm). | 🧪 Beta | -| **Simple Deployment Script** | One-click deployment script for quick server setup with automated configuration and dependency installation | 🧪 Beta | - -### 📋 Future Enhancements - -| Feature | Description | Priority | Status | -|---------|-------------|----------|---------| -| **Backup & Restore** | Comprehensive backup system with S3/MinIO support, automated scheduling, and safe restore functionality | High | ✅ Implemented | -| **External Media Library (Reference Mode)** | Use an external folder library as a read‑only source with import and on‑demand thumbnail generation | High | ✅ Implemented | -| **Download Protection** | Advanced image protection system with canvas rendering, invisible watermarking, right-click prevention, and DevTools detection to protect photos from unauthorized downloads | High | ✅ Implemented | -| **Gallery Templates** | Multiple gallery layouts (grid, masonry, carousel, timeline, hero, mosaic) with custom CSS styling support. Includes starter templates like Apple Liquid Glass for complete visual customization | Medium | ✅ Implemented | -| **Face Recognition** | AI-powered face detection to help guests find their photos and create automatic person-based albums | Low | 🔄 Open | -| **Gallery Feedback** | Allow guests to like, rate, and comment on photos with admin notifications and moderation | Medium | ✅ Implemented | -| **Video Support** | Upload and display videos alongside photos in galleries with streaming support | Low | ✅ Implemented | -| **Multiple Administrators** | Support for multiple admin accounts with role-based permissions and activity tracking | Low | ✅ Implemented | -| **Filtering & Export Options** | Filter photos by likes, ratings, comments, or favorites. Search by filename. Sort by date, name, size, or rating. Export filtered selections as ZIP or generate Capture One/Lightroom-compatible file lists for professional workflows | Medium | ✅ Implemented | - -**Status Legend:** ✅ Implemented | 🚧 In Progress | 🔄 Open | 📋 Planned +Found a security issue? Please open a [security issue](https://github.com/PicPeak/picpeak/issues/new?labels=security). See [SECURITY.md](SECURITY.md) for the policy. ## ☕ Support the Project -PicPeak is free, open source, and self-hostable forever. If it saves you time or replaces a paid subscription, consider buying me a coffee — it directly funds the time spent on new features, bug fixes, and keeping the demo + docs running. - -

- - Buy Me A Coffee - -

- -Other ways to support without spending anything: ⭐ star the repo, share it with photographer friends, file good bug reports, or open a PR. +PicPeak is free, open source, and self-hostable forever. If it saves you time or replaces a paid subscription, consider [buying me a coffee](https://buymeacoffee.com/theluap) — it directly funds new features, bug fixes, and keeping the demo + docs running. You can also ⭐ star the repo, share it, file good bug reports, or open a PR. ## 🙏 Acknowledgments -PicPeak is inspired by the best features of commercial platforms while remaining completely open source. Special thanks to all contributors who make this project possible. +PicPeak is inspired by the best features of commercial platforms while remaining completely open source. It's developed with AI assistance, but human-tested end-to-end, security-audited, and human-reviewed for quality. ### 👥 Contributors A huge thank you to the people whose code, reports, and feedback have shaped PicPeak: -- [**@the-luap**](https://github.com/the-luap) — creator and lead maintainer. Started the project and built PicPeak's foundation and the entire gallery experience (events, galleries, uploads, sharing, download protection, templates), plus backup & restore, analytics, system health, branding/theming, and WhatsApp notifications — and the architecture every later feature builds on. -- [**@Luca-Timo**](https://github.com/Luca-Timo) — native Apple Silicon multi-arch images, external-URL toggle for legal CMS pages, the lazy-loaded folder tree picker, the admin-email picker on event creation, the data-driven self-hosted webfont system, the gallery header/banner decoupling, several typed-API refactors, and the CRM + accounting suite (quotes/contracts/invoices, hours logging, calendar, tax report, inbound supplier-invoice capture, expenses, and the Treuhänder/Banana export). Consistently raises the bar with thoughtful PRs. -- [**@Rekoo-PS**](https://github.com/Rekoo-PS) — sharp-eyed bug reporter and product feedback. Filed the issues that drove the login-loop fix, the gallery-loading skeleton work, the redirection cleanup, the mobile-lightbox overhaul, the admin-events search-counter fix, the photo-count column, and the bulk-delete workflow. Also a [BuyMeACoffee](https://buymeacoffee.com/theluap) supporter — the kind of feedback loop that keeps the project useful for real deployments. +**[@the-luap](https://github.com/the-luap)** — creator and lead maintainer +- Gallery foundation (events, uploads, sharing, download protection, templates) +- Backup & restore, analytics, branding/theming +- The architecture every later feature builds on + +**[@Luca-Timo](https://github.com/Luca-Timo)** +- Native Apple Silicon multi-arch images +- CRM & accounting suite (quotes/contracts/invoices) +- Hours logging & Treuhänder/Banana tax export +- Gallery header/banner decoupling + +**[@Rekoo-PS](https://github.com/Rekoo-PS)** — bug reports & product feedback +- Login-loop fix, mobile-lightbox overhaul, bulk-delete workflow +- Also a [BuyMeACoffee](https://buymeacoffee.com/theluap) supporter If you've contributed and aren't listed here, please open a PR — this list is meant to grow. -### 🤖 AI-Assisted Development - -This project was generated with the assistance of AI technology, but has been: -- ✅ **Fully tested end-to-end** by human developers -- 🔒 **Security audited** with comprehensive security checks -- 👨‍💻 **Human-reviewed** for code quality and best practices -- 🧪 **Production-tested** in real-world scenarios - -We believe in transparent development practices and the responsible use of AI as a tool to accelerate development while maintaining high standards of quality and security. - -## ⚠️ CRM & Accounting disclaimers — examples only, verify locally - -The CRM & accounting modules (contracts, invoices, QR-bills, the tax -report and the accountant exports) ship seeded content and computed -figures that are intended as a **starting point only**: - -- **Contract blocks** (image rights, NDA, model release, cancellation, - jurisdiction, …) are written by the maintainer, **not by a lawyer**. - Every operator must have their lawyer review and adapt them before - sending any contract to a customer. -- **QR-bills and SEPA EPC payloads** are rendered from the data you - typed. Picpeak is open source — please scan a test invoice with your - bank's app to check the QR actually works. We are not responsible for - any mistakes that come from sending an invoice with bad data on it. -- **Tax, VAT & accounting figures** (the tax report, VAT-payable, the - per-rate breakdown, the Treuhänder / Banana export, etc.) are computed - from the data you enter and the defaults you configure. They are - **guidance only and jurisdiction-specific** — tax rules, VAT rates, - deduction schemes (e.g. the Liechtenstein 20 % Gewinnungskosten flat - rate) and filing duties differ by country and change over time. **Every - operator must check their own tax / VAT regulations and verify the - numbers with their accountant / Treuhänder / tax authority before - relying on any figure or export.** Picpeak makes no warranty that the - output is correct for your jurisdiction or situation. - -Read [`docs/crm-disclaimers.md`](docs/crm-disclaimers.md) before -enabling the Contracts, Invoices or Accounting features. - ## 📄 License PicPeak is released under the [MIT License](LICENSE). Use it freely for personal or commercial projects. -## 🚀 Ready to Get Started? - -1. ⭐ **Star this repository** to show your support -2. 📖 Read the [docs at docs.picpeak.app](https://docs.picpeak.app) -3. 🐛 Report issues or request features -4. 🤝 Join our community and contribute! - ---

Made with ❤️ by photographers, for photographers
- Homepage • - Live Demo • - GitHub • - Documentation • + Homepage · + Live Demo · + Documentation · Support

diff --git a/docs/_to-migrate/README.md b/docs/_to-migrate/README.md new file mode 100644 index 00000000..d63301d4 --- /dev/null +++ b/docs/_to-migrate/README.md @@ -0,0 +1,22 @@ +# `_to-migrate` — staging area, **not canonical** + +> [!WARNING] +> These pages were cut out of the root `README.md` to slim it down. They are +> **being ported to the PicPeak docs site** (its own repo → docs.picpeak.app). +> +> - **Do not edit** these files expecting the change to be authoritative — edit +> the docs-site copy once it exists. +> - **Do not add new links** to this folder from code or other docs. +> - This whole folder is **deleted** once the docs-site pages are live (see the +> 3-phase plan in PicPeak/picpeak#1000). + +The root README links here only as a temporary bridge so no link 404s while the +docs-site pages are being written. + +| File | Ported to docs.picpeak.app path | +|---|---| +| `webhooks.md` | `/features/webhooks` | +| `storage-backends.md` | `/deployment` (storage section) | +| `first-run-setup.md` | `/deployment` (first run / permissions) | +| `system-requirements.md` | `/deployment` (system requirements) | +| `roadmap.md` | project roadmap / GitHub Projects (TBD) | diff --git a/docs/_to-migrate/first-run-setup.md b/docs/_to-migrate/first-run-setup.md new file mode 100644 index 00000000..14fd38ac --- /dev/null +++ b/docs/_to-migrate/first-run-setup.md @@ -0,0 +1,26 @@ +# First run — create your admin account + +On first start with no `ADMIN_PASSWORD` set, PicPeak has **no admin account yet** and greets you with an in-browser setup screen — no credentials in `.env`: + +1. Open **http://localhost:3000/admin** — you'll be redirected to `/setup`. +2. Read the **one-time setup token** from the 0600 file the backend writes it to + (it is deliberately *not* printed to the logs — that would leave a live + bootstrap credential in `docker logs`): + ```bash + docker compose exec backend cat /app/data/SETUP_TOKEN + ``` + It is bind-mounted, so `sudo cat data/SETUP_TOKEN` on the host works too. Only + if that file could not be written does the backend fall back to logging the + token (`docker compose logs backend | grep -i "setup token"`). +3. Paste the token, set your admin **email + password**, and you're in. The token is single-use, and the setup screen closes permanently once an admin exists. + +> Prefer the old behaviour? Set `ADMIN_PASSWORD` in `.env` and PicPeak auto-creates the admin on first boot instead (credentials written to `data/ADMIN_CREDENTIALS.txt`). + +## Docker file permissions + +- The backend container starts as root, chowns bind-mounted host directories (`./storage`, `./data`, `./logs`) to UID 1001 (`nodejs`), then drops privileges via `su-exec` before running the app. No host-side setup needed for fresh installs. +- If you pin `user:` in a compose override (e.g. to map a specific host UID), the self-chown is skipped and you must pre-chown the host directories to that UID — see [docs.picpeak.app/deployment/docker#permissions](https://docs.picpeak.app/deployment/docker#permissions). + +## ARM64 (aarch64) systems + +Pre-built images include native `linux/arm64`, no platform flags or emulation needed. If you're on an older image tag that's still amd64-only, see [docker-compose.amd64.override.yml](../../docker-compose.amd64.override.yml) for a transitional fallback. diff --git a/docs/_to-migrate/roadmap.md b/docs/_to-migrate/roadmap.md new file mode 100644 index 00000000..6002839b --- /dev/null +++ b/docs/_to-migrate/roadmap.md @@ -0,0 +1,28 @@ +# Roadmap + +We're constantly improving PicPeak and welcome contributions from our community! If you have ideas for new features or want to help implement existing ones, please open an issue or submit a pull request. Your contributions help make PicPeak better for everyone. + +## 🚧 Beta Features (Use at your own risk) + +These features are currently in beta testing and may have limited functionality or stability: + +| Feature | Description | Status | +|---------|-------------|--------| +| **CRM & Accounting Module** | Quotes, contracts, invoices (+ Storno), hours logging, calendar, and tax report — plus inbound supplier-invoice capture, internal expenses, and a Treuhänder/Banana (Swiss/LI) accountant-journal export. Feature-flagged off by default. Seeded contract blocks, payment terms, IBAN / QR-bill and tax defaults are **examples only** and need legal / financial / **tax** review before customer-facing use. See [docs.picpeak.app/features/crm](https://docs.picpeak.app/features/crm). | 🧪 Beta | +| **Simple Deployment Script** | One-click deployment script for quick server setup with automated configuration and dependency installation | 🧪 Beta | + +## 📋 Future Enhancements + +| Feature | Description | Priority | Status | +|---------|-------------|----------|---------| +| **Backup & Restore** | Comprehensive backup system with S3/MinIO support, automated scheduling, and safe restore functionality | High | ✅ Implemented | +| **External Media Library (Reference Mode)** | Use an external folder library as a read‑only source with import and on‑demand thumbnail generation | High | ✅ Implemented | +| **Download Protection** | Advanced image protection system with canvas rendering, invisible watermarking, right-click prevention, and DevTools detection to protect photos from unauthorized downloads | High | ✅ Implemented | +| **Gallery Templates** | Multiple gallery layouts (grid, masonry, carousel, timeline, hero, mosaic) with custom CSS styling support. Includes starter templates like Apple Liquid Glass for complete visual customization | Medium | ✅ Implemented | +| **Face Recognition** | AI-powered face detection to help guests find their photos and create automatic person-based albums | Low | 🔄 Open | +| **Gallery Feedback** | Allow guests to like, rate, and comment on photos with admin notifications and moderation | Medium | ✅ Implemented | +| **Video Support** | Upload and display videos alongside photos in galleries with streaming support | Low | ✅ Implemented | +| **Multiple Administrators** | Support for multiple admin accounts with role-based permissions and activity tracking | Low | ✅ Implemented | +| **Filtering & Export Options** | Filter photos by likes, ratings, comments, or favorites. Search by filename. Sort by date, name, size, or rating. Export filtered selections as ZIP or generate Capture One/Lightroom-compatible file lists for professional workflows | Medium | ✅ Implemented | + +**Status Legend:** ✅ Implemented | 🚧 In Progress | 🔄 Open | 📋 Planned diff --git a/docs/_to-migrate/storage-backends.md b/docs/_to-migrate/storage-backends.md new file mode 100644 index 00000000..d6afc191 --- /dev/null +++ b/docs/_to-migrate/storage-backends.md @@ -0,0 +1,22 @@ +# Storage Backends + +PicPeak supports two storage backends for photos, thumbnails, hero images, watermarks, and archive zips. Both are configured via environment variables; no code change is required to switch. + +| Capability | `STORAGE_BACKEND=local` (default) | `STORAGE_BACKEND=s3` | +|---|---|---| +| Photo / thumbnail / hero storage | Local filesystem under `STORAGE_PATH` | Bucket on any S3-compatible service | +| Admin UI upload | ✅ | ✅ | +| Filesystem auto-import (chokidar watcher) | ✅ | ❌ — disabled (use the upload API) | +| Watermarks, fingerprinting, fragmentation | ✅ | ✅ (materialized to a tmp file just-in-time) | +| Bulk download zips (cached + on-the-fly) | ✅ | ✅ | +| Backups | ✅ | ✅ | +| External media reference mode (`EXTERNAL_MEDIA_ROOT`) | ✅ (always local) | ✅ (still local — not migrated) | + +## Switching to an S3-compatible backend + +1. Provision a bucket and credentials. The minimum IAM policy is documented in `.env.example`. +2. Set `STORAGE_BACKEND=s3` plus `STORAGE_S3_BUCKET`, `STORAGE_S3_REGION`, `STORAGE_S3_ACCESS_KEY`, `STORAGE_S3_SECRET_KEY`. For non-AWS providers (MinIO, R2, B2, …) also set `STORAGE_S3_ENDPOINT`. +3. If you have existing local content, copy it first: `node backend/scripts/migrate-storage.js --dry-run` then `node backend/scripts/migrate-storage.js`. The script is idempotent and writes a failures CSV. +4. Restart the backend. The startup check pings the bucket and refuses to boot on misconfig. + +Note: presigned-URL serving (zero-bandwidth direct downloads from S3) is intentionally **not** in v1 — every request still streams through the backend so watermarks, devtools-detection, and access logging keep working. diff --git a/docs/_to-migrate/system-requirements.md b/docs/_to-migrate/system-requirements.md new file mode 100644 index 00000000..33984cfa --- /dev/null +++ b/docs/_to-migrate/system-requirements.md @@ -0,0 +1,63 @@ +# System Requirements + +## Minimum Requirements +- **CPU**: 2 CPU cores +- **RAM**: **4 GB minimum** for a normal photo-upload workload — sharp/libvips + decodes the full uncompressed frame before resize, and the default two + worker loops at sharp-concurrency 2 can push peak RSS past 1.5 GB on a + batch of 20-MP+ photos. On a 2 GB VPS that's enough to OOM-kill the + backend mid-batch (surfaces as 503s on thumbnails — see [Low-memory + hosts](#low-memory-hosts) below for the recipe to run on 2 GB). +- **Storage**: 20GB minimum (plus photo storage needs) +- **OS**: Linux (Ubuntu 20.04+), macOS, or Windows with WSL2 +- **Node.js**: v18.0.0 or higher +- **Database**: SQLite (included) or PostgreSQL 12+ + +## Docker Requirements (Recommended) +- **Docker**: v20.10.0+ +- **Docker Compose**: v2.0.0+ + +## Low-memory hosts + +Running on 2 GB RAM (e.g. an entry-level VPS) is workable but requires +tuning the upload-processor concurrency down. The backend auto-detects +total RAM at startup via `os.totalmem()` — on a host that reports < 3 GB, +it defaults `UPLOAD_PROCESSOR_CONCURRENCY` to **1** instead of 2 and logs +a one-shot warning. You can pin the value explicitly in `.env`: + +```env +# Single worker loop — slower batch processing, lower peak RSS +UPLOAD_PROCESSOR_CONCURRENCY=1 +``` + +The trade-off is throughput: a single worker processes one photo at a +time, so a 100-photo batch takes ~2× as long but won't OOM. **Health-check +note**: if the backend dies under memory pressure, the gallery serves +`503 Service Unavailable` on thumbnails until Docker's +`restart: unless-stopped` brings the container back. Persistent 503s +during/after an upload batch on a low-memory host are almost always this. + +## Video Support Requirements +When enabling video uploads, consider these additional resources: + +| Resource | Recommendation | Notes | +|----------|----------------|-------| +| **RAM** | 4GB+ recommended | FFmpeg processing requires more memory | +| **Storage** | Plan for 10-100x more | Videos are significantly larger than images | +| **CPU** | Additional cores help | Video thumbnail extraction is CPU-intensive | +| **Bandwidth** | Higher throughput | Video streaming requires more bandwidth | + +**Technical Notes:** +- FFmpeg is bundled via npm (`@ffmpeg-installer/ffmpeg`) - no system installation required +- Maximum upload size: **10GB per video file** +- Chunked upload support for files >100MB (resumable uploads) +- Supported formats: MP4, WebM, MOV, AVI +- Video thumbnails are automatically generated from the first few seconds + +**For Nginx/Reverse Proxy:** +If using Nginx, increase the client max body size: +```nginx +client_max_body_size 10G; +proxy_read_timeout 3600; +proxy_send_timeout 3600; +``` diff --git a/docs/_to-migrate/webhooks.md b/docs/_to-migrate/webhooks.md new file mode 100644 index 00000000..bb497018 --- /dev/null +++ b/docs/_to-migrate/webhooks.md @@ -0,0 +1,77 @@ +# Webhooks + +PicPeak POSTs event/photo lifecycle notifications to URLs you configure under **Settings → Webhooks**. Each delivery is signed `HMAC-SHA256` with a per-webhook secret in the `X-PicPeak-Signature` header so receivers can verify the request really came from your PicPeak instance. + +## Event types + +| Event | Fires when | +|---|---| +| `event.created` | Gallery created (admin or API) | +| `event.published` | Draft becomes live (`is_draft: true → false`) — also fires when an event is created with `is_draft=false` | +| `event.archived` | Bulk-archive, manual archive, or auto-archive on expiry | +| `event.expired` | Expiration checker marks the gallery inactive (fires before `event.archived` in the cascade) | +| `photo.uploaded` | Admin upload, API upload, guest upload, or auto-import | +| `photo.deleted` | Single delete, bulk delete (NOT fired per-photo when an event is archived — receivers infer from `event.archived` to avoid flooding) | + +## Payload shape + +```json +{ + "id": "delivery-uuid", + "type": "event.published", + "created_at": "2026-04-28T05:25:00.000Z", + "data": { + "event": { "id": 123, "slug": "wedding-smith", "share_url": "https://..." } + } +} +``` + +Also sent on every request: +- `X-PicPeak-Signature` — `HMAC-SHA256(secret, raw_body)` as hex +- `X-PicPeak-Event` — the event type (handy for routing without parsing the body) +- `X-PicPeak-Delivery` — UUID for idempotency on the receiver side +- `User-Agent: PicPeak-Webhooks/1.0` + +## Verifying signatures + +**Node.js** +```js +const crypto = require('crypto'); +function verify(secret, rawBody, signature) { + const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); + const a = Buffer.from(expected, 'hex'); + const b = Buffer.from(signature, 'hex'); + if (a.length !== b.length) return false; + return crypto.timingSafeEqual(a, b); +} +``` + +**Python** +```python +import hmac, hashlib +def verify(secret: str, raw_body: bytes, signature: str) -> bool: + expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() + return hmac.compare_digest(expected, signature) +``` + +**curl + openssl** (one-liner for a quick replay) +```sh +SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}') +[ "$SIG" = "$RECEIVED_SIG" ] && echo OK || echo MISMATCH +``` + +## Retries + observability + +- `2xx` → success, recorded with latency +- Non-`2xx` or network error → exponential backoff: `1m → 5m → 30m → 2h → 12h`, max 5 attempts +- After max attempts: status `failed`, surfaces in **Settings → Webhooks → Deliveries** with a "Replay" button +- Up to 5 deliveries in flight at once; one slow consumer can't block others (configurable via `WEBHOOK_DELIVERY_CONCURRENCY`) +- Response body truncated to 1KB before storage so chatty receivers don't bloat the audit log + +The deliveries page (`/admin/webhooks/:id/deliveries`) shows every attempt with timestamp, status, HTTP code, latency, payload sent, signature, and response. Click "Send test event" to fire a synthetic delivery for any event type. + +## SSRF protection + +Webhook URLs are validated against the same private-IP blocklist used elsewhere in the app — loopback, private RFC1918 ranges, link-local, `.local`/`.internal` hostnames, cloud metadata endpoints. The check runs both at create time and per-delivery (DNS-rebinding mitigation). + +For local development with a receiver on the same machine or docker network, set `WEBHOOK_ALLOW_PRIVATE_URLS=true`. Production deployments must leave this OFF.