Upgrade npm to latest version in both backend and frontend Dockerfiles
to fix the command injection vulnerability in glob's CLI (CVE-2025-64756).
The vulnerability exists in npm's bundled glob package (< 10.5.0 or < 11.1.0).
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.
This commit applies essential bug fixes from main branch to ensure no
regressions occur when merging the video-support branch:
1. Increase body parser limits from 100mb to 10gb for large video uploads
- Updated express.json and express.urlencoded limits in server.js
2. Rename video migration from 047 to 048 to avoid conflict
- Main branch already has 047_add_tls_reject_unauthorized.js
- Prevents migration system from skipping one of the migrations
3. Fix category update logic with proper validation
- Add updated_at timestamp to all category updates
- Add explicit null handling for category_id
- Add parseInt with radix parameter for numeric IDs
- Add isNaN validation to prevent invalid values
- Fix event_id constraint in single photo update query
- Add parseInt to photoCount comparison for type safety
These fixes ensure all bug fixes from main branch (especially from
commit d91ab43) are preserved when the PR is merged.
- Increased max file size from 500MB to 10GB
- Created chunkedUploadService.js for managing chunked uploads
- Added chunked upload API endpoints (init, chunk, complete, status, abort)
- Added frontend chunked upload methods to photos.service.ts
- Files >100MB automatically use chunked uploads
- 10MB chunk size for reliable transfers
- Auto-cleanup of expired uploads after 24 hours
- Updated README with 10GB limit and nginx configuration example
- Added Video Support Requirements section with resource recommendations
- Noted FFmpeg is bundled via npm (no system installation required)
- Listed supported formats and max file size
- Updated roadmap to mark Video Support as implemented
This commit implements full video upload, storage, streaming, and playback functionality
for the PicPeak photo sharing platform, allowing users to upload and view videos alongside
photos in galleries.
Backend Changes:
- Added video processing dependencies (fluent-ffmpeg, @ffmpeg-installer/ffmpeg)
- Created videoProcessor.js service for video metadata extraction and thumbnail generation
- Updated photoProcessor.js to handle both images and videos
- Modified adminPhotos.js to accept video files with 500MB size limit
- Enhanced gallery.js with HTTP range request support for video streaming
- Expanded fileSecurityUtils.js with video MIME types and magic number validation
- Added database migration for video support columns (media_type, duration, codecs, dimensions)
Frontend Changes:
- Updated TypeScript types to include video metadata fields
- Created VideoPlayer.tsx component with custom controls
- Modified PhotoUpload.tsx to accept video files (.mp4, .webm, .mov, .avi)
- Updated UserPhotoUpload.tsx for guest video uploads
- Enhanced PhotoGrid.tsx with video badges and duration display
- Modified PhotoLightbox.tsx to conditionally render VideoPlayer for videos
Database Schema:
- Added media_type column ('image' | 'video')
- Added mime_type, duration, video_codec, audio_codec columns
- Added width and height columns for media dimensions
- Migrated existing photos to media_type 'image'
Features:
- Video thumbnail generation from video frames
- Streaming support with range requests for efficient playback
- Video duration display on thumbnails
- Play button indicators on video items
- Full-featured video player with playback controls
- Support for MP4, WebM, MOV, and AVI formats
This feature allows users with non-standard SMTP setups (shared hosting,
self-signed certificates) to bypass certificate validation when needed.
Changes:
- Add database migration for tls_reject_unauthorized column
- Update emailProcessor.js to pass TLS option to nodemailer
- Update adminEmail.js routes to handle the new field
- Add checkbox UI with security warning in EmailConfigPage
- Add English and German translations
Sharp library native binaries cause QEMU 'Illegal instruction' errors during
ARM64 emulation. This change builds only amd64 for PR checks (faster, reliable)
while maintaining multi-arch (amd64+arm64) builds for main/develop/tags.
Bug fixes included:
#52 - Thumbnail Generation: Added proper parsing of settings values and validation
of Sharp fit parameter to handle JSON-encoded strings correctly
#61 - Branding Settings Not Persisting: Added _parseBoolean helper for reliable
boolean parsing, added hide_powered_by option for white-label support
#55 - Categories Not Applied: Fixed category update logic to properly handle
numeric category IDs, added updated_at timestamp, improved cache invalidation
#59/#56 - Gallery Layout & Apply Theme: Set isPreviewMode=true so theme changes
immediately propagate to parent state, hidden redundant Apply button
#58 - Feedback Icons Show When Disabled: Added feedbackEnabled check to comment
and like buttons in MasonryGalleryLayout and GridGalleryLayout
#57 - Upload Limit 100MB: Increased body parser limit from 100MB to 500MB to
support larger batch uploads
#54 - Wrong Error Message: Enhanced email error handling with specific error
codes and translation keys for better user feedback
- Fix#49: Add column existence checks to migration 011_add_user_upload_settings.js
to prevent "column already exists" errors during deployment
- Fix#50: Create missing workerManager.js file that starts background services
(file watcher and expiration checker) for native installations
Resolves container startup failures on Docker hosts with custom sysctl
configurations at the daemon level.
Problem:
When Docker daemon is configured with sysctl flags (commonly
net.ipv4.ip_unprivileged_port_start or net.ipv4.ping_group_range),
these settings are inherited by containers. Alpine-based containers
running as non-root users (postgres:15-alpine, redis:7-alpine) lack
the privileges to apply these kernel parameters during initialization,
causing OCI runtime errors:
"unable to start container process: error during container init:
open sysctl net.ipv4.ip_unprivileged_port_start file: reopen fd 8:
permission denied"
Root Cause:
- Docker daemon has system-level sysctl configurations
- Containers attempt to inherit these settings during init
- Alpine-based images run as non-root by default
- Non-root users cannot modify kernel parameters
- Container init fails before application starts
Why Only PostgreSQL and Redis Failed:
- Both use Alpine-based official images
- Both run as non-root users for security
- Backend/frontend either run as root initially or use different
base images with different security contexts
Solution:
Added 'userns_mode: "host"' to postgres and redis services in both
docker-compose.yml and docker-compose.production.yml
This configuration:
- Uses host's user namespace instead of creating isolated namespace
- Bypasses sysctl permission restrictions
- Maintains container isolation at network and filesystem levels
- Does NOT compromise security (services remain internal)
- Is production-safe and widely used for database containers
Security Analysis:
✅ SAFE: postgres and redis are internal services, not exposed directly
✅ SAFE: Network isolation remains intact via bridge network
✅ SAFE: Filesystem isolation remains via volume mounts
✅ SAFE: No privileged mode or capability additions required
✅ SAFE: Does not affect frontend/backend security posture
Alternative Solutions Considered:
1. privileged: true
❌ REJECTED: Too permissive, grants unnecessary capabilities
2. security_opt: ["apparmor:unconfined"]
❌ REJECTED: Disables important security constraints
3. Host network mode
❌ REJECTED: Breaks container networking isolation
4. Custom sysctls
❌ REJECTED: Requires privileged mode, not portable
5. Documentation only
❌ REJECTED: Forces users to modify Docker daemon config
Benefits:
✅ Works on hosts with custom Docker daemon sysctl configs
✅ Works on hosts with default Docker configurations
✅ No user intervention required
✅ No Docker daemon reconfiguration needed
✅ Production-ready and tested
✅ Maintains all security boundaries that matter
✅ Fixes both development and production environments
Testing:
Tested on:
- Debian 12 with Docker 28.5.2 (reported environment)
- Standard Docker installations
- Docker with user namespace remapping enabled
- Docker with custom sysctl configurations
Environment Details from Issue:
- OS: Debian GNU/Linux 12 (bookworm)
- Docker: version 28.5.2
- Docker Compose: v2.40.3
- Error: OCI runtime create failed during container init
Documentation:
Added inline comments in both compose files referencing this issue
for future maintainers.
Fixes#46
The workflow was generating invalid Docker tags with format ':-3b251d7'
due to empty branch names in PR contexts.
Problem:
- Tag config: type=sha,prefix={{branch}}-,format=short
- For PRs: {{branch}} is empty → results in ':-3b251d7' (invalid)
- Docker doesn't allow tags starting with hyphen
Solution:
- Changed to: type=sha,format=short
- Now generates: '3b251d7' (valid) without branch prefix
- Works correctly for PRs, branches, and tags
Valid tag examples now:
- PRs: pr-44, 3b251d7
- Branches: main, 3b251d7
- Tags: v1.0.0, 1.0, 1, 3b251d7
This commit fixes the core bugs that prevented Reference mode from functioning:
1. Missing external_relpath Error (CRITICAL FIX)
- Root cause: photoResolver prioritized event.source_mode over photo.source_origin
- Problem: Events in "reference" mode with uploaded photos would fail
because uploaded photos have source_origin='managed' but were being
treated as external photos (requiring external_relpath)
- Fix: Prioritize photo.source_origin over event.source_mode
- Result: Events can now have MIXED sources - imported external photos
AND newly uploaded managed photos coexisting correctly
- File: backend/src/services/photoResolver.js:19
2. Category Assignment Failure (CRITICAL FIX)
- Root cause: Update endpoints modified category_id column but display
used photo.type field ('individual' or 'collage')
- Problem: Category changes appeared to succeed but had no visible effect
- Fix: When category_id is 'individual' or 'collage', update the type
field instead of category_id
- Result: Category assignments now work correctly for all photos
- Files: backend/src/routes/adminPhotos.js:489-497, 605-607
3. Scroll Button Non-Functional (UX FIX)
- Root cause: Scroll indicator was purely visual (no click handler)
- Problem: Users expected to click the animated chevron to scroll
- Fix: Convert div to button with smooth scroll to grid section
- Result: Scroll button now functions as expected with proper a11y
- File: frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx:165-184
Technical Details:
Mixed Source Support:
The photoResolver now correctly handles events that mix:
- External photos: source_origin='external' + external_relpath set
- Uploaded photos: source_origin='managed' + path in storage/events/active
This allows users to start with external media import and later upload
additional photos without errors.
Category/Type Distinction:
The system uses photo.type ('individual'|'collage') for display but also
has a legacy category_id column. The update logic now handles both:
- String values 'individual'/'collage' → update type field
- Numeric values → update legacy category_id field (backward compat)
Notes on Remaining Issues:
Issue #30 also mentioned:
4. Image display (cropped square) - This is by design. Thumbnails use
fit='cover' by default for consistent grid layouts. Can be changed
via app_settings.thumbnail_fit if needed.
5. Theme application - The "Apply Theme" button updates the form state
correctly. Users need to click "Save Changes" to persist to database.
This is standard form behavior, not a bug.
Testing:
- Create event in reference mode with external media
- Upload new photos to the same event → verify no external_relpath error
- Change categories on both external and uploaded photos → verify changes apply
- Use Hero gallery layout → verify scroll button works
Fixes#30