The all-in-one image could not be installed from a GUI at all — the deployment it
exists for. validateEnv treats a missing JWT_SECRET as critical and exits, and the
documented run command supplies it with `openssl rand`, a shell command a Synology
Container Manager or QNAP Container Station form cannot run.
wait-for-db.sh now generates one on first start and persists it next to the database,
extending the existing /run/secrets hydration rather than adding a second mechanism.
Explicit env still wins, then /run/secrets, then the generated file. The write is
load-bearing: JWT_SECRET is exported only when the file actually persisted, because an
unpersisted secret would mint a new one every restart and sign every session out.
Creation writes to a private temp file and hard-links it into place — atomic, fails with
EEXIST when another container won, and the loser adopts the winner's value. Non-regular
paths are rejected before the link, since POSIX ln links INTO a directory rather than
failing, which would make a mistyped -v target unrecoverable.
Also repairs the onboarding paths a new install actually walks: the installer no longer
rotates the secrets of a running install on re-run, deprecates the dead scripts/install.sh
in place, corrects the CONTRIBUTING dev loop, and fixes the vite proxy target that had
been pointing at a stray local port since 0da45e69.
Reviewed over three rounds. Co-authored by @Luca-Timo.
* fix(security): bump backend deps to close all open Trivy code-scanning alerts
- axios 1.16.0 -> 1.18.1 (GHSA-gcfj-64vw-6mp9 high + 10 medium advisories)
- sharp 0.34.3 -> 0.35.3 (GHSA-f88m-g3jw-g9cj, inherited libvips CVEs)
- mailparser 3.9.9 -> 3.9.14 (pulls linkify-it 5.0.2, CVE-2026-59887)
- brace-expansion override >=5.0.6 -> >=5.0.7 (CVE-2026-13149)
- body-parser 1.20.4 -> 1.20.6 via lockfile refresh (CVE-2026-12590)
* fix(images): migrate removed sharp failOnError option and enforce Node >=20.9
sharp 0.35 drops the deprecated failOnError constructor option, so
recoverably corrupt images would start failing upload validation and
thumbnail generation; use the failOn: 'none' equivalent instead.
sharp 0.35 also requires Node >=20.9: declare it in engines and make
picpeak-setup.sh compare the full version instead of only the major,
so native installs on Node 20.3-20.8 upgrade instead of breaking.
* fix(setup): align the Node floor with the whole dependency tree and gate native updates
html-to-text@10 needs Node >=20.19 and the glob/minimatch family excludes
Node 21, so declare engines as ^20.19.0 || >=22 and enforce the same range
in picpeak-setup.sh. Also run install_nodejs at the start of
update_native_installation so existing native installs on an old Node get
upgraded before the service is stopped, instead of restarting broken.
* fix(setup): make the update-path Node gate actually work
--update dispatches before detect_os, so install_nodejs saw an empty
PACKAGE_MANAGER, matched no install branch, and reported success on the
old runtime. Detect the OS on demand and re-verify the installed version
afterwards, failing loudly (before the service is stopped) when the
runtime still misses the engines range, e.g. a Node 21 that package
managers refuse to downgrade.
Restructure picpeak-setup.sh around two clear modes:
- Interactive wizard (run_wizard): asks method → install dir → channel →
domain → HTTPS handling → admin email → SMTP, then shows a review and
confirms before installing. Each value already passed as a flag is
respected and its question skipped.
- Unattended (--unattended + flags): validate_unattended fills defaults and
fails fast on impossible combos (e.g. --enable-ssl without --domain).
New flags: --admin-password, --install-dir, --channel.
Align the Docker path with the rest of the project:
- Use the committed docker-compose.production.yml (prebuilt GHCR images) via
COMPOSE_FILE in .env instead of hand-generating a divergent compose file.
- Drop the broken setup_ssl_docker call (was referenced but never defined).
- Update path pulls images instead of building.
Admin bootstrap follows the browser-first model (#714): by default no
password is written; the one-time /setup token is surfaced (from
data/SETUP_TOKEN or the logs) with browser instructions. --admin-password
keeps the legacy seeded-admin + ADMIN_CREDENTIALS.txt flow for headless runs.
Depends on #714 (setup-token backend + secrets-init in production compose)
for the browser-first + zero-secret behavior at runtime.
Blockers:
- SetupPage now mirrors the server password rule (>=8 with upper/lower/digit) so
a green client isn't bounced by the server; server errors carry a `field`
(routes/setup.js) that the client maps to a translated key instead of
rendering raw English. New i18n: setup.invalidToken, setup.passwordRequirements.
- picpeak-setup.sh: the ADMIN_CREDENTIALS.txt block no longer dead-ends on the
wizard path — when no legacy admin was seeded it prints the one-time setup
token (from data/SETUP_TOKEN / docker compose logs) and points at /setup.
Concern:
- createInitialAdmin creates the admin + burns the token in ONE transaction,
atomically claiming the token (null-if-present, expect 1 row) so a
double-submit can't create two super_admins. Cross-DB (whereNotNull, trx-only
writes). Added a concurrency test.
Nits:
- SetupPage redirects to /login when /setup/status errors (no form flash on a
configured instance).
- Dropped the unused DATABASE_URL from docker-compose.yml.
- Documented why secrets are chmod 644 (three different reader users).
Repo transferred from the-luap/picpeak → PicPeak/picpeak. Docker images
publish to ghcr.io/picpeak/picpeak/{backend,frontend} (lowercase, per the
GHCR canonical form computed by docker-build.yml's `${GITHUB_REPOSITORY,,}`).
Sweep covers:
- docker-compose.production.yml + Dockerfiles → new image registry path
- README, CONTRIBUTING, SECURITY, SIMPLE_SETUP, scripts/picpeak-setup.sh
→ new GitHub URLs
- Update-check / release-notes services (updateCheckService,
environmentService, updateNotificationService, adminSystem,
UpdateNotification, githubReleaseUrl) → GitHub API + tag URLs use the
canonical PicPeak/picpeak path
- Issue templates + README-DOCKER + workflow README → updated package URLs
- One commit-context comment in migrations/090 + customerAccountsService
CHANGELOG.md is intentionally untouched (historical release entries are
immutable; GitHub auto-redirects the old URLs indefinitely).
CLAUDE.md keeps the bare `(the-luap)` reference — that's the maintainer's
personal handle, not a repo URL.
22 files, 48/48 line swaps (every change is a 1:1 URL replacement).
Two follow-up fixes inside the same install-experience surface as
the previous commit:
1. **Removed `docker compose exec -T backend npm run migrate`** in
both install_docker and update_docker_installation. The backend
container's wait-for-db.sh already runs `npm run migrate:safe`
on startup; the script was racing it with a separate (and
non-safe) `npm run migrate`. That race is the most likely
actual mechanism behind #484's "relation 'photos' does not
exist" error on the second install attempt — partial schema
visible to one of the two parallel migrators. Replaced with a
bounded wait for the backend container to become healthy
(Docker healthcheck reports green only after wait-for-db.sh
finishes its migration pass).
2. **Added the missing frontend container** to the script-generated
compose. The script previously generated a postgres + redis +
backend stack with no frontend at all (backend on host port
3001), while the documented production install
(docker-compose.production.yml) ships postgres + redis +
backend + frontend (nginx /api proxy on host port 3000). That
shape divergence is half of issue B in #484 — script-installed
admins had no frontend container and were left wondering where
the UI lived. Aligning both compose files on the same shape
eliminates the divergence; the frontend uses curl in its
healthcheck (frontend/Dockerfile explicitly `apk add curl`)
unlike the backend.
The remaining piece of issue B — picking ONE canonical install
path (build-from-source script vs. prebuilt-image production
compose) and deprecating the other — is a deployment-strategy
call that deserves its own design pass. Both paths now produce
architecturally-equivalent stacks.
Three install-experience bugs that compounded into MrGabri's "fresh
install fails" report:
1. **postgres healthcheck noise.** `pg_isready -U <user>` without
-d defaults to probing a database whose name matches the user.
Since DB_NAME defaults to picpeak_prod (not picpeak), every
healthcheck interval logged
FATAL: database "picpeak" does not exist
into postgres logs even though the install was working
correctly. Reporter saw the FATAL, assumed broken, restarted
with DB_NAME=picpeak, hit a tainted-state migration error on
the second try, filed a bug. Fixed in both
docker-compose.production.yml and the inline compose generated
by scripts/picpeak-setup.sh — pin -d to ${DB_NAME} so the
probe hits the real database.
2. **backend container shows perpetually `unhealthy`.** Both
compose files used `curl -f` for the backend healthcheck, but
backend/Dockerfile only installs dumb-init + postgresql-client +
ffmpeg — no curl. Switch to wget --no-verbose --tries=1 --spider
to match what backend/Dockerfile's own HEALTHCHECK already
does. Now docker ps, docker compose ps, and the backend image's
built-in healthcheck all agree.
3. **stale separate `workers` container.** scripts/picpeak-setup.sh
still generated a second container running `npm run workers`
alongside the backend, but workers (fileWatcher,
expirationChecker, emailQueueProcessor, backgroundProcessor,
webhookWorker) have been started by server.js in-process for
a while — see the comment at line ~895 of the same script for
the systemd-side cleanup. The duplicate container caused two
file watchers and two expiration checkers to compete for the
same DB rows. Removed from the generated compose; install +
upgrade paths now stop and rm any pre-existing picpeak-workers
container.
Doesn't address issue B's bigger architecture mismatch (the script
generates a build-from-source compose with no frontend container,
while docker-compose.production.yml uses prebuilt images with a
separate frontend container). That deserves its own design pass
to pick a canonical install path and align — out of scope here.
Two intertwined bugs reported in #427 by @iSchumi6210:
1. Login silently fails over HTTP. Backend defaulted COOKIE_SECURE to true
when NODE_ENV=production. Over plain HTTP the browser drops the Secure
cookie → next /auth/session request returns 401 → redirect back to
/admin/login → no error shown. picpeak-setup.sh writes
NODE_ENV=production but never writes COOKIE_SECURE, so every first-time
install without a reverse proxy hits this.
2. Admin password is generated but admins can't find it. The 001_init.js
migration writes the generated password to data/ADMIN_CREDENTIALS.txt
inside the backend container, but picpeak-setup.sh only copies it out
when --reset-admin-password is passed. Default-path users never see it
and resort to manual bcrypt updates in psql.
Changes:
- tokenUtils.js: production default goes from `true` to `'auto'`. On real
HTTPS req.secure is true → Secure flag is still emitted (no security
regression for reverse-proxy deployments). On plain HTTP req.secure is
false → Secure flag omitted → login works. Users who explicitly want
the strict HTTPS-only behaviour can still set COOKIE_SECURE=true.
- .env.example: rewrite the COOKIE_SECURE block to make the new default
obvious and explain when to override (set =true for strict, =false to
skip the per-request check, leave unset for the auto behaviour).
- picpeak-setup.sh (both Docker and native paths):
- Write COOKIE_SECURE=auto explicitly to the generated .env (defense in
depth so the right behaviour is preserved even if the backend default
flips again later)
- After migrations, ALWAYS copy ADMIN_CREDENTIALS.txt out of the
backend container/data dir to the host data dir, chmod 600, and print
the email + password to the install output. The credentials file
remains as a backup record that the operator should delete after
noting the password.
Verified locally with all 4 permutations of NODE_ENV × COOKIE_SECURE:
production, unset → HTTPS: secure=true ✓ HTTP: secure=false ✓ (was both true)
production, =true → both: secure=true (strict opt-in preserved)
production, =auto → HTTPS: secure=true HTTP: secure=false (already-correct)
development, unset → both: secure=false (dev unchanged)
Adds a long-lived bearer-token mechanism + scoped REST surface designed
for n8n-style automation: create a gallery, upload photos, fetch the
share URL — all via documented HTTPS endpoints instead of poking at the
admin UI's internal routes.
API
- Migration 081 adds `api_tokens` (hashed_token, scopes, owner FK,
last_used/expires/revoked timestamps).
- New apiTokenAuth middleware: parses `Authorization: Bearer pp_live_…`,
resolves to the owner admin user, attaches `req.admin` so existing
permission decorators (events.create etc.) still work. Token-level
scope check (read/write/admin) layers on top as defence in depth —
a leaked read-only token cannot mutate even if its owner is super_admin.
- adminApiTokens route exposes list/create/revoke for admins (cookie-
authed). Plaintext token is returned exactly once on creation.
- v1 surface mounted at /api/v1: POST/GET /events, GET /events/:id,
POST /events/:id/photos (multipart, single file), GET
/events/:id/share-link. Each endpoint annotated with @openapi JSDoc.
Documentation
- swagger-jsdoc + swagger-ui-express produce a live spec at
/api/openapi.json and a Swagger UI at /api/docs (admin-gated).
- backend/scripts/generate-openapi.js writes docs/openapi.{json,yaml}
to the repo so the spec is versioned.
- scripts/sync-api-docs.sh runs in pre-push: regenerates the spec and
copies it into the picpeak-docs Nextra site at app/api/. Writes only,
never commits or pushes the docs repo (PUSH_SKIP_DOCS=1 to bypass).
Frontend
- New Settings → API Tokens tab: generate, list, revoke. Plaintext
tokens are shown once with a copy-to-clipboard control.
- Add migration to backfill width/height for existing photos without dimensions
- Replace justified masonry mode with quilted layout (mixed sizes based on aspect ratio)
- Rewrite mosaic layout to use proper CSS Grid with span rules
- Fix theme not being applied after gallery login
- Improve columns mode distribution using shortest-column algorithm
- Apply gallery theme regardless of authentication status
Issue #66: Remove redundant picpeak-workers.service creation from setup script.
Workers (fileWatcher, expirationChecker, emailProcessor) are now started
automatically by server.js, so a separate systemd service is not needed.
The legacy service cleanup code is retained for migration purposes.
Issue #67: Ensure storage directories exist at container startup in
wait-for-db.sh. When host directories are bind-mounted in Docker, the
container's built-in directories are overridden. This fix creates the
required directory structure (events/active, events/archived, thumbnails)
before the application starts, preventing EACCES permission errors.
- Created unified SIMPLE_SETUP.md combining Docker and native installation guides
- Created universal scripts/setup.sh supporting both Docker and native installations
- Removed redundant setup files (simple-setup.md, simple-setup.sh, scripts/simple-setup.sh)
- Added intelligent installation method selection based on system resources
- Implemented update and uninstall functionality in unified script
- Enhanced with command-line options for unattended installations
- Improved cross-platform support (Ubuntu, Debian, RHEL/CentOS, Fedora, Raspberry Pi OS)
Fixes#7🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Original: feat: enhance security logging and ensure rate limit blocks are properly tracked
- Add comprehensive logging for rate limit blocks with full request details
- IP address (with proper proxy detection), user agent, headers, timestamps
- Rate limit info (current count, limit, remaining, reset time)
- Separate tracking for auth vs general endpoints
- Enhance authentication failure logging
- JWT validation failures with detailed error info
- Admin auth attempts without token
- Failed token validation with user context
- All events include IP, path, method, user agent
- Improve Winston logger configuration for production
- Add automatic log rotation (10MB errors, 50MB combined)
- Create separate security.log for auth/rate limit events
- Ensure logs directory exists automatically
- Add structured JSON format for log aggregation
- Support container logging with LOG_TO_CONSOLE env var
- Create comprehensive documentation
- Security logging guide with examples
- Monitoring recommendations
- Configuration reference
- Add test script to verify logging functionality
All rate limit settings remain configurable via admin panel:
- Window duration, max requests, auth limits
- Skip authenticated requests option
- Public endpoints only option
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>