Photobooth FTP Ingestion
This guide explains how to operate the Photobooth FTP workflow end‑to‑end: provisioning FTP users for tenants, running the ingest pipeline, and exposing photobooth photos inside the Guest PWA.
Architecture Overview
- 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. - Control Service (REST) provisions FTP accounts. Laravel calls it during enable/rotate/disable actions.
- Photobooth settings (Filament SuperAdmin) define global port, rate limit, expiry grace, and Control Service connection.
- Ingest command copies uploaded files into the event’s storage disk, generates thumbnails, records
photos.ingest_source = photobooth, and respects package quotas. - Guest PWA filter consumes
/api/v1/events/{token}/photos?filter=photoboothto 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:
- Enable/disable the Photobooth link.
- Rotate credentials (max 10-char usernames, 8-char passwords).
- 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
- Set env vars from above and restart the app.
- Ensure vsftpd + Control Service are deployed; verify port 2121 and REST endpoint connectivity.
- Mount shared volume to
/var/www/storage/app/photobooth(or updatePHOTOBOOTH_IMPORT_ROOT+filesystems.disks.photobooth.root). - Run migrations (
php artisan migrate) to create settings/event columns. - Seed default storage target (e.g.,
MediaStorageTarget::create([... 'key' => 'public', ...])) in non-test environments if not present. - Verify scheduler (Horizon or cron) is running commands
photobooth:ingestandphotobooth:cleanup-expired. - 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.