Files
fotospiel-app/docs/photobooth_ftp

Photobooth FTP Ingestion

This guide explains how to operate the Photobooth FTP workflow endtoend: provisioning FTP users for tenants, running the ingest pipeline, and exposing photobooth photos inside the Guest PWA.

Architecture Overview

  1. vsftpd container (port 2121) accepts uploads into a shared volume (default /var/www/storage/app/photobooth). Each event receives isolated credentials and a dedicated directory.
  2. Control Service (REST) provisions FTP accounts. Laravel calls it during enable/rotate/disable actions.
  3. Photobooth settings (Filament SuperAdmin) define global port, rate limit, expiry grace, and Control Service connection.
  4. Ingest command copies uploaded files into the events storage disk, generates thumbnails, records photos.ingest_source = photobooth, and respects package quotas.
  5. Guest PWA filter consumes /api/v1/events/{token}/photos?filter=photobooth to render the “Fotobox” tab.
Photobooth -> FTP (vsftpd) -> photobooth disk
       photobooth:ingest (queue/scheduler)
            -> Event media storage (public disk/S3)
            -> packages_usage, thumbnails, security scan

Environment Variables

Add the following to .env (already scaffolded in .env.example):

PHOTOBOOTH_CONTROL_BASE_URL=https://control.internal/api
PHOTOBOOTH_CONTROL_TOKEN=your-control-token
PHOTOBOOTH_CONTROL_TIMEOUT=5

PHOTOBOOTH_FTP_HOST=ftp.internal
PHOTOBOOTH_FTP_PORT=2121

PHOTOBOOTH_USERNAME_PREFIX=pb
PHOTOBOOTH_USERNAME_LENGTH=8
PHOTOBOOTH_PASSWORD_LENGTH=8

PHOTOBOOTH_RATE_LIMIT_PER_MINUTE=20
PHOTOBOOTH_EXPIRY_GRACE_DAYS=1

PHOTOBOOTH_IMPORT_DISK=photobooth
PHOTOBOOTH_IMPORT_ROOT=/var/www/storage/app/photobooth
PHOTOBOOTH_IMPORT_MAX_FILES=50
PHOTOBOOTH_ALLOWED_EXTENSIONS=jpg,jpeg,png,webp

Filesystem Disk

config/filesystems.php registers a photobooth disk that must point to the shared volume where vsftpd writes files. Mount the same directory inside both the FTP container and the Laravel app container.

Control Service Contract

Laravel expects the Control Service to expose:

POST   /users                  { username, password, path, rate_limit_per_minute, expires_at, ftp_port }
POST   /users/{username}/rotate { password, rate_limit_per_minute, expires_at }
DELETE /users/{username}
POST   /config                 { ftp_port, rate_limit_per_minute, expiry_grace_days }

Authentication is provided via PHOTOBOOTH_CONTROL_TOKEN (Bearer token).

Scheduler & Commands

Command Purpose Default schedule
photobooth:ingest [--event=ID] [--max-files=N] Pulls files from the Photobooth disk and imports them into the event storage. every 5 minutes
photobooth:cleanup-expired De-provisions FTP accounts after their expiry. hourly

You can run the ingest job manually for a specific event:

php artisan photobooth:ingest --event=123 --max-files=20

Tenant Admin UX

Inside the Event Admin PWA, go to Event → Fotobox-Uploads to:

  1. Enable/disable the Photobooth link.
  2. Rotate credentials (max 10-char usernames, 8-char passwords).
  3. View rate limit + expiry info and copy the ftp:// link.

Guest PWA Filter

The Guest gallery now exposes a “Fotobox” tab (both preview card and full gallery). API usage:

GET /api/v1/events/{token}/photos?filter=photobooth
Headers: X-Device-Id (optional)

Response items contain ingest_source, allowing the frontend to toggle photobooth-only views.

Operational Checklist

  1. Set env vars from above and restart the app.
  2. Ensure vsftpd + Control Service are deployed; verify port 2121 and REST endpoint connectivity.
  3. Mount shared volume to /var/www/storage/app/photobooth (or update PHOTOBOOTH_IMPORT_ROOT + filesystems.disks.photobooth.root).
  4. Run migrations (php artisan migrate) to create settings/event columns.
  5. Seed default storage target (e.g., MediaStorageTarget::create([... 'key' => 'public', ...])) in non-test environments if not present.
  6. Verify scheduler (Horizon or cron) is running commands photobooth:ingest and photobooth:cleanup-expired.
  7. Test end-to-end: enable Photobooth on a staging event, upload a file via FTP, wait for ingest, and confirm it appears under the Fotobox filter in the PWA.