GhostRoll is a “drop the SD card in and it just works” ingest pipeline:
SD card → local session → share-friendly JPEGs + thumbnails + gallery → private S3 → automatic enhancement (optional) → one share link
It's designed to be:
- Low-friction (watch mode + one URL)
- Fast (parallel processing + uploading; 30-50% faster on Raspberry Pi)
- Incremental (dedupe so re-inserting the same card is fast)
- Privacy-friendly (derived images strip metadata; S3 stays private)
flowchart LR
A[📷 Insert SD Card] --> B[⏳ Automatic Processing]
B --> C[🔗 Get Share Link]
C --> D[📤 Share with Others]
style A fill:#e1f5ff,color:#000
style B fill:#fff4e1,color:#000
style C fill:#e8f5e9,color:#000
style D fill:#f3e5f5,color:#000
flowchart TD
A[Insert SD Card] --> B[Watch detects mount]
B --> C[Scan DCIM directory]
C --> D{New files?}
D -->|No| E[Skip - already processed]
D -->|Yes| F[Copy originals to session]
F --> G[Generate share images & thumbnails]
G -->|As processed| I[Upload to private S3 bucket<br/>in parallel]
I --> H[Build HTML gallery]
H --> J[S3 EventBridge triggers Lambda]
J --> K[Lambda enhances images automatically]
K --> L[Enhanced images stored in enhanced/]
L --> M[Generate presigned URLs]
M --> N[Create share link + QR code]
N --> O[Share URL with others]
O --> P[Gallery with toggle: Original/Enhanced]
E --> Q[Wait for next card]
P --> Q
style G fill:#fff4e1,color:#000
style I fill:#e3f2fd,color:#000
style J fill:#e3f2fd,color:#000
style K fill:#fff3e0,color:#000
style L fill:#f3e5f5,color:#000
style P fill:#e8f5e9,color:#000
./setup.sh
source .venv/bin/activate
ghostroll setupThe setup script will:
- Create a virtual environment
- Install GhostRoll and dependencies
- Guide you through configuration checks
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -e .GhostRoll uses the AWS CLI (aws s3 cp + aws s3 presign), so make sure this works:
aws configure
aws sts get-caller-identityIf you prefer to edit files directly, use the templates in:
docs/aws/credentials.exampledocs/aws/config.exampledocs/aws/iam-policy-ghostroll-s3.json(starter least-privilege policy)
Run the interactive setup guide to check everything is configured correctly:
ghostroll setupThis will check AWS credentials, disk space, mount points, and provide guidance for any issues.
Set the SD card volume label to:
auto-import
macOS sometimes mounts as auto-import 1, etc — GhostRoll handles that.
ghostroll watchWhen it finishes, you'll get a session directory under ~/ghostroll/ with a share.txt presigned URL.
For a guided setup experience with helpful error messages:
ghostroll setupIf something feels misconfigured (AWS creds, bucket perms, mount roots, disk space), run:
ghostroll doctorThis repo includes:
ghostroll doctorfor quick environment checks- a small CI smoke test (
tests/test_pipeline_smoke.py) that runs on every push via GitHub Actions
This project is licensed under the PolyForm Noncommercial License 1.0.0 (non‑commercial use only). See LICENSE.
Default location:
~/ghostroll/<SESSION_ID>/
Contents:
originals/(copied from the card; preserves structure)derived/share/(max ~2660px long edge, quality ~90, auto-oriented, metadata stripped)derived/thumbs/(max 512px long edge, quality ~85, auto-oriented, metadata stripped)index.html(local gallery)share.txt(the link you share)share-qr.png(QR for the share link)ghostroll.log(session log)
The uploaded index.html is generated to work with a private bucket: it embeds presigned URLs for the images.
The gallery includes a responsive grid and a lightbox (click to open, Esc to close, ←/→ to navigate).
It also includes a Download all link for the session's share images.
GhostRoll can automatically enhance images with automatic lighting adjustments (similar to Lightroom's auto-settings). When enabled:
- Images are automatically enhanced after upload via AWS Lambda
- Enhanced versions are stored in
enhanced/prefix - Gallery includes a toggle button to switch between original and enhanced views
- User preference is saved in browser localStorage
See aws-lambda/README.md for deployment instructions.
GhostRoll keeps a persistent SQLite DB keyed by SHA-256 of file bytes (default ~/.ghostroll/ghostroll.db).
- Re-insert same card with no new photos: it will quickly report "No new files detected".
- Add new photos: only the new files are copied/processed/uploaded.
GhostRoll automatically filters out:
- macOS metadata files (
._*): Resource fork files created by macOS when copying files. These are not real images and are skipped during processing. - Corrupted/truncated images: Files that cannot be processed are skipped with a warning in the log.
You can configure via env vars (CLI flags override env):
GHOSTROLL_SD_LABEL(defaultauto-import)GHOSTROLL_BASE_DIR(default~/ghostroll)GHOSTROLL_DB_PATH(default~/.ghostroll/ghostroll.db)GHOSTROLL_S3_BUCKET(defaultphoto-ingest-project)GHOSTROLL_S3_PREFIX_ROOT(defaultsessions/)GHOSTROLL_PRESIGN_EXPIRY_SECONDS(default604800)GHOSTROLL_MOUNT_ROOTS(default/Volumes,/media,/run/media,/mnt)GHOSTROLL_STATUS_PATH(default~/ghostroll/status.json)GHOSTROLL_STATUS_IMAGE_PATH(default~/ghostroll/status.png)GHOSTROLL_STATUS_IMAGE_SIZE(default800x480)GHOSTROLL_PROCESS_WORKERS(default: CPU-count clamped to 1–6)GHOSTROLL_UPLOAD_WORKERS(default4)GHOSTROLL_PRESIGN_WORKERS(default8)
Web interface settings (enabled by default):
GHOSTROLL_WEB_ENABLED(defaulttrue) — enable/disable web interfaceGHOSTROLL_WEB_HOST(default127.0.0.1) — bind address (use0.0.0.0for network access)GHOSTROLL_WEB_PORT(default8080,8081on Pi if WiFi portal enabled) — port numberGHOSTROLL_UPLOAD_RAW_FILES(defaulttrue) — upload RAW files as compressed ZIP to S3 (see RAW Storage Options)
Image settings:
GHOSTROLL_SHARE_MAX_LONG_EDGE(default2660)GHOSTROLL_SHARE_QUALITY(default90)GHOSTROLL_THUMB_MAX_LONG_EDGE(default512)GHOSTROLL_THUMB_QUALITY(default85)
GhostRoll includes a lightweight web interface that provides easy access to status and session galleries without impacting performance. The web interface is enabled by default.
To disable:
export GHOSTROLL_WEB_ENABLED=false
ghostroll watchOr use command-line flags:
ghostroll watch --web-host 0.0.0.0 --web-port 8080Note: On Raspberry Pi, if the WiFi portal is enabled (uses port 8080), the web interface defaults to port 8081 to avoid conflicts.
Features:
- View current status (
/status.json,/status.png) - List all sessions (
/sessions) - View local session galleries (
/sessions/<session-id>/) - Access QR codes and session files
Performance Impact:
The web server runs in a separate thread and only serves existing files — it has virtually no impact on the main pipeline performance. It uses Python's built-in http.server, requires minimal memory (~5–10 MB), and doesn't interfere with image processing or uploads.
Default URL: http://localhost:8080 (or http://<pi-ip>:8081/ on Raspberry Pi, http://<pi-ip>:8080/ if bound to 0.0.0.0 on macOS/Linux)
Troubleshooting: If the web interface isn't starting, check:
- Service logs:
sudo journalctl -u ghostroll-watch.service -n 50 | grep -i web - Configuration:
cat /etc/ghostroll.env | grep GHOSTROLL_WEB - Port availability:
sudo ss -tuln | grep 8081(or 8080) - Run diagnostic:
./pi/scripts/check-web-interface.sh
GhostRoll runs on Linux and writes status outputs that work well for e‑ink:
status.json(machine-readable)status.png(monochrome "now doing X" image)
Components:
- Waveshare 2.13inch E-Ink Display HAT V4 Version Compatible with Raspberry Pi
- Raspberry Pi 4B
- SD card reader
- PiSugar battery
Raspberry Pi running this software and uploading images with a gallery link QR code on the e-ink screen.
If you want a "flash once, boot, and it runs" image with systemd + boot-partition config, follow:
pi/README.md
Note: on Raspberry Pi OS Bookworm, system Python is “externally managed” (PEP 668), so manual installs typically use a virtualenv. See pi/README.md for the tested manual setup steps.
The original prototype is still in ingest.sh (rsync + ImageMagick + rclone + aws presign).


