From ec4be12133d1a57c4787243a232c63a44dbfc3e3 Mon Sep 17 00:00:00 2001 From: jairomelo Date: Fri, 29 May 2026 15:38:26 -0700 Subject: [PATCH 01/16] feat: add documentation service and Docker setup for rendering --- docker-compose.yml | 7 +++++++ docs/.dockerignore | 5 +++++ docs/Dockerfile | 8 ++++++++ docs/_quarto.yml | 4 ++++ 4 files changed, 24 insertions(+) create mode 100644 docs/.dockerignore create mode 100644 docs/Dockerfile diff --git a/docker-compose.yml b/docker-compose.yml index e9eada5..0958c07 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -85,5 +85,12 @@ services: # No depends_on db — the frontend is a Node.js API proxy and never connects # to Postgres directly. Removing this lets db and frontend start in parallel. + docs: + build: + context: ./docs + dockerfile: Dockerfile + ports: + - "8080:80" + volumes: postgres_data: diff --git a/docs/.dockerignore b/docs/.dockerignore new file mode 100644 index 0000000..612210f --- /dev/null +++ b/docs/.dockerignore @@ -0,0 +1,5 @@ +# Exclude pre-rendered output and cache from build context. +# The Dockerfile re-renders docs fresh with `quarto render`. +site/ +.quarto/ +**/*.quarto_ipynb diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 0000000..aa7d9b5 --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,8 @@ +FROM ghcr.io/quarto-dev/quarto:1.9.37 AS build +COPY . /site +WORKDIR /site +RUN quarto render --output-dir /output + +FROM nginx:alpine +COPY --from=build /output /usr/share/nginx/html +EXPOSE 80 \ No newline at end of file diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 6f285e1..8468963 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -35,6 +35,10 @@ website: text: "Device Setup: Raspberry Pi CM4 with Cameras" - href: developers/device_setup_pi5_imx519.qmd text: "Device Setup: Raspberry Pi 5 with IMX519 Camera" + - section: "Software Setup Guides" + contents: + - href: developers/sd_card_distribution.qmd + text: "SD Card Distribution" page-footer: center: "Documentation site built: {{< meta date >}}" From 89f301f5c17a6faf9cebb60e339427b6cffb3673 Mon Sep 17 00:00:00 2001 From: jairomelo Date: Fri, 29 May 2026 15:54:04 -0700 Subject: [PATCH 02/16] fix: update Dockerfile and .dockerignore to correct output directory for rendering --- docs/.dockerignore | 1 - docs/Dockerfile | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/.dockerignore b/docs/.dockerignore index 612210f..2bb29dd 100644 --- a/docs/.dockerignore +++ b/docs/.dockerignore @@ -1,5 +1,4 @@ # Exclude pre-rendered output and cache from build context. # The Dockerfile re-renders docs fresh with `quarto render`. -site/ .quarto/ **/*.quarto_ipynb diff --git a/docs/Dockerfile b/docs/Dockerfile index aa7d9b5..0047828 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,8 +1,8 @@ FROM ghcr.io/quarto-dev/quarto:1.9.37 AS build COPY . /site WORKDIR /site -RUN quarto render --output-dir /output +RUN quarto render --output-dir /site FROM nginx:alpine -COPY --from=build /output /usr/share/nginx/html +COPY site/ /usr/share/nginx/html EXPOSE 80 \ No newline at end of file From d92015464ecd5c762e139fc9eddc52c2bbc917bf Mon Sep 17 00:00:00 2001 From: jairomelo Date: Fri, 29 May 2026 16:03:58 -0700 Subject: [PATCH 03/16] Update documentation for SD Card Distribution and adjust publication dates - Changed publication date from May 19, 2026 to May 29, 2026 in index.html and related sections. - Added new "SD Card Distribution" guide to listings.json and search.json. - Updated search text to reflect the new publication date and added details for the SD Card Distribution guide. - Enhanced the sidebar navigation in index.html to include the new Software Setup Guides section with the SD Card Distribution link. --- docs/developers/sd_card_distribution.qmd | 156 ++++ .../site/developers/sd_card_distribution.html | 837 ++++++++++++++++++ docs/site/index.html | 37 +- docs/site/listings.json | 3 +- docs/site/search.json | 74 +- 5 files changed, 1100 insertions(+), 7 deletions(-) create mode 100644 docs/developers/sd_card_distribution.qmd create mode 100644 docs/site/developers/sd_card_distribution.html diff --git a/docs/developers/sd_card_distribution.qmd b/docs/developers/sd_card_distribution.qmd new file mode 100644 index 0000000..8e5ec6c --- /dev/null +++ b/docs/developers/sd_card_distribution.qmd @@ -0,0 +1,156 @@ +--- +title: "SD Card Distribution" +date-modified: last-modified +format: html +--- + +*** + +This guide explains how to prepare and ship a Raspberry Pi SD card image with the Digitization Toolkit pre-installed, so the recipient can plug it in and access the application from any device on the same network — without any manual IP configuration. + +--- + +## How It Works + +The Raspberry Pi broadcasts its hostname over the local network using **mDNS** (via the Avahi daemon, pre-installed on Raspberry Pi OS). This means any device on the same network can reach the Pi at `http://.local` without knowing its IP address. + +The application is configured to use this hostname instead of a hardcoded IP, so the image works on any network regardless of what IP the router assigns. + +--- + +## Preparing the Master Image + +### 1. Set a meaningful hostname + +Choose a hostname that identifies the device. The current convention is `digitool`: + +```bash +sudo hostnamectl set-hostname digitool +``` + +Verify: + +```bash +hostname +# digitool +``` + +The Pi will be reachable at `http://digitool.local` on the destination network. + +### 2. Confirm Avahi is running + +Avahi (mDNS) is pre-installed on Raspberry Pi OS. Confirm it is active: + +```bash +systemctl is-enabled avahi-daemon +systemctl is-active avahi-daemon +``` + +Both should return `enabled` / `active`. If not: + +```bash +sudo systemctl enable --now avahi-daemon +``` + +### 3. Update `.env` to use the hostname + +In `/home/pi/dtk/.env`, set `HOST_IP` to the `.local` hostname: + +```bash +HOST_IP=digitool.local +CORS_ORIGINS=["http://digitool.local:5173","http://digitool.local:3000","http://localhost:5173","http://localhost:3000"] +``` + +`PUBLIC_API_BASE` resolves automatically from `HOST_IP`: + +```bash +PUBLIC_API_BASE=http://${HOST_IP}:8000 +``` + +### 4. Rebuild and verify the frontend container + +The `PUBLIC_API_BASE` variable is baked into the compiled frontend at build time. After editing `.env`, rebuild: + +```bash +cd /home/pi/dtk +docker compose -f docker-compose.yml -f docker-compose.pi.yml up -d --build --force-recreate +``` + +Open `http://digitool.local:3000` from another device on the same network to confirm it loads. + +### 5. Image the SD card + +Once the system is working correctly, shut down the Pi cleanly and image the SD card from another machine: + +**macOS / Linux:** +```bash +# Identify the SD card device (e.g. /dev/disk4 or /dev/sdb) +diskutil list # macOS +lsblk # Linux + +# Create compressed image +sudo dd if=/dev/sdX bs=4M status=progress | gzip > dtk-$(date +%Y%m%d).img.gz +``` + +**Windows:** Use [Raspberry Pi Imager](https://www.raspberrypi.com/software/) or [Win32DiskImager](https://win32diskimager.org/) to read the card to a `.img` file. + +::: {.callout-tip} +## Shrink before imaging +Tools like [PiShrink](https://github.com/Drewsif/PiShrink) can reduce the image size significantly before compression by trimming unused filesystem space. +::: + +--- + +## Flashing and Delivering the Image + +Flash the image onto a new SD card: + +```bash +# Linux / macOS +gzip -dc dtk-YYYYMMDD.img.gz | sudo dd of=/dev/sdX bs=4M status=progress +``` + +Or use Raspberry Pi Imager → *Use custom image* → select the `.img.gz` file. + +--- + +## What the Recipient Needs to Do + +Nothing, in most cases. They: + +1. Insert the SD card and power on the Pi. +2. Connect the Pi to their local network (Ethernet or Wi-Fi configured before imaging). +3. Open `http://digitool.local:3000` from any browser on the same network. + +::: {.callout-note} +## Wi-Fi pre-configuration +If the destination uses Wi-Fi (not Ethernet), configure the network credentials on the SD card **before** shipping. The easiest way is via Raspberry Pi Imager's *Advanced options* when flashing, or by placing a `wpa_supplicant.conf` in the `/boot` partition after flashing. +::: + +--- + +## Troubleshooting + +**`digitool.local` doesn't resolve** + +- Windows requires [Bonjour](https://support.apple.com/kb/DL999) (installed automatically with iTunes or iCloud). Windows 10 (1803+) supports mDNS natively. +- Try pinging: `ping digitool.local` +- As a fallback, find the IP via the router's DHCP client list and use it directly. + +**CORS errors in the browser** + +The frontend origin must match `CORS_ORIGINS` in `.env`. If you changed the hostname or port, update that list and restart the backend: + +```bash +cd /home/pi/dtk/backend && pixi run dev +``` + +**Two Pis on the same network** + +mDNS hostnames must be unique per network. Change the hostname on one of them: + +```bash +sudo hostnamectl set-hostname digitool-2 +``` + +And update `HOST_IP` and `CORS_ORIGINS` in `.env` accordingly, then rebuild the frontend container. diff --git a/docs/site/developers/sd_card_distribution.html b/docs/site/developers/sd_card_distribution.html new file mode 100644 index 0000000..0b46218 --- /dev/null +++ b/docs/site/developers/sd_card_distribution.html @@ -0,0 +1,837 @@ + + + + + + + + + + +SD Card Distribution – Digitization Toolkit Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

SD Card Distribution

+
+ + + +
+ + +
+
Published
+
+

May 29, 2026

+
+
+ +
+
Modified
+
+

May 29, 2026

+
+
+ +
+ + + +
+ + +
+

This guide explains how to prepare and ship a Raspberry Pi SD card image with the Digitization Toolkit pre-installed, so the recipient can plug it in and access the application from any device on the same network — without any manual IP configuration.

+
+
+

How It Works

+

The Raspberry Pi broadcasts its hostname over the local network using mDNS (via the Avahi daemon, pre-installed on Raspberry Pi OS). This means any device on the same network can reach the Pi at http://<hostname>.local without knowing its IP address.

+

The application is configured to use this hostname instead of a hardcoded IP, so the image works on any network regardless of what IP the router assigns.

+
+
+
+

Preparing the Master Image

+
+

1. Set a meaningful hostname

+

Choose a hostname that identifies the device. The current convention is digitool:

+
sudo hostnamectl set-hostname digitool
+

Verify:

+
hostname
+# digitool
+

The Pi will be reachable at http://digitool.local on the destination network.

+
+
+

2. Confirm Avahi is running

+

Avahi (mDNS) is pre-installed on Raspberry Pi OS. Confirm it is active:

+
systemctl is-enabled avahi-daemon
+systemctl is-active avahi-daemon
+

Both should return enabled / active. If not:

+
sudo systemctl enable --now avahi-daemon
+
+
+

3. Update .env to use the hostname

+

In /home/pi/dtk/.env, set HOST_IP to the .local hostname:

+
HOST_IP=digitool.local
+CORS_ORIGINS=["http://digitool.local:5173","http://digitool.local:3000","http://localhost:5173","http://localhost:3000"]
+

PUBLIC_API_BASE resolves automatically from HOST_IP:

+
PUBLIC_API_BASE=http://${HOST_IP}:8000
+
+
+

4. Rebuild and verify the frontend container

+

The PUBLIC_API_BASE variable is baked into the compiled frontend at build time. After editing .env, rebuild:

+
cd /home/pi/dtk
+docker compose -f docker-compose.yml -f docker-compose.pi.yml up -d --build --force-recreate
+

Open http://digitool.local:3000 from another device on the same network to confirm it loads.

+
+
+

5. Image the SD card

+

Once the system is working correctly, shut down the Pi cleanly and image the SD card from another machine:

+

macOS / Linux:

+
# Identify the SD card device (e.g. /dev/disk4 or /dev/sdb)
+diskutil list           # macOS
+lsblk                   # Linux
+
+# Create compressed image
+sudo dd if=/dev/sdX bs=4M status=progress | gzip > dtk-$(date +%Y%m%d).img.gz
+

Windows: Use Raspberry Pi Imager or Win32DiskImager to read the card to a .img file.

+
+
+
+ +
+
+TipShrink before imaging +
+
+
+

Tools like PiShrink can reduce the image size significantly before compression by trimming unused filesystem space.

+
+
+
+
+
+
+

Flashing and Delivering the Image

+

Flash the image onto a new SD card:

+
# Linux / macOS
+gzip -dc dtk-YYYYMMDD.img.gz | sudo dd of=/dev/sdX bs=4M status=progress
+

Or use Raspberry Pi Imager → Use custom image → select the .img.gz file.

+
+
+
+

What the Recipient Needs to Do

+

Nothing, in most cases. They:

+
    +
  1. Insert the SD card and power on the Pi.
  2. +
  3. Connect the Pi to their local network (Ethernet or Wi-Fi configured before imaging).
  4. +
  5. Open http://digitool.local:3000 from any browser on the same network.
  6. +
+
+
+
+ +
+
+NoteWi-Fi pre-configuration +
+
+
+

If the destination uses Wi-Fi (not Ethernet), configure the network credentials on the SD card before shipping. The easiest way is via Raspberry Pi Imager’s Advanced options when flashing, or by placing a wpa_supplicant.conf in the /boot partition after flashing.

+
+
+
+
+
+

Troubleshooting

+

digitool.local doesn’t resolve

+
    +
  • Windows requires Bonjour (installed automatically with iTunes or iCloud). Windows 10 (1803+) supports mDNS natively.
  • +
  • Try pinging: ping digitool.local
  • +
  • As a fallback, find the IP via the router’s DHCP client list and use it directly.
  • +
+

CORS errors in the browser

+

The frontend origin must match CORS_ORIGINS in .env. If you changed the hostname or port, update that list and restart the backend:

+
cd /home/pi/dtk/backend && pixi run dev
+

Two Pis on the same network

+

mDNS hostnames must be unique per network. Change the hostname on one of them:

+
sudo hostnamectl set-hostname digitool-2
+

And update HOST_IP and CORS_ORIGINS in .env accordingly, then rebuild the frontend container.

+ + +
+ +
+ +
+
+ +
+ + + + + \ No newline at end of file diff --git a/docs/site/index.html b/docs/site/index.html index d30e68c..66c7c86 100644 --- a/docs/site/index.html +++ b/docs/site/index.html @@ -6,7 +6,7 @@ - + Digitization Toolkit Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Setting Up gPhoto2-Compatible Cameras

+
+ + + +
+ + +
+
Published
+
+

May 30, 2026

+
+
+ +
+
Modified
+
+

May 30, 2026

+
+
+ +
+ + + +
+ + +
+ +
+
+

For a comprehensive list of gPhoto2-supported cameras, visit the official support page.

+

To be fully compatible with the Digitization Toolkit software, cameras should support all of the following capabilities: Image Capture, Trigger Capture, Live View, and Configuration.

+
+
+
+
+

Connect the cameras to the Raspberry Pi

+
    +
  • Requirements: +
      +
    • 2 x USB 2.0 Type-A to Mini-B (5-pin) cables
    • +
  • +
  • Connections: +
      +
    • Camera side: Plug into the middle port (marked with the USB trident icon).
    • +
    • Raspberry Pi side: Plug into any USB-A port (preferably a blue USB 3.0 port).
    • +
  • +
+
+
+

USB connection from the camera side

+
USB connection from the camera side
+
+
+
+
+

USB connection to the Raspberry Pi - dual camera setup in ports USB 3.0

+
USB connection to the Raspberry Pi - dual camera setup in ports USB 3.0
+
+
+
+
+

Canon EOS Rebel T7

+
    +
  • Set the focus mode to “Manual Focus”: On the lens switch, set the focus mode to MF (Manual Focus).
  • +
+
+
+

Canon EF-S 18-55mm lens attached to a DSLR camera body

+
Canon EF-S 18-55mm lens attached to a DSLR camera body
+
+
+
    +
  • Set the mode to Manual: rotate the mode dial to M (Manual).
  • +
+
+
+

Canon EOS camera mode dial in manual mode

+
Canon EOS camera mode dial in manual mode
+
+
+
    +
  • Retract the built-in flash: If the flash pops up after turning on the camera, push the flash head down into the closed position.
  • +
+
+
+

Canon EOS camera built-in flash in closed position

+
Canon EOS camera built-in flash in closed position
+
+
+
    +
  • Set Auto power off: From the camera menu, find the Auto power off setting and set it to 15 minutes.
  • +
+
+
+

Canon EOS camera menu screen auto power off

+
Canon EOS camera menu screen auto power off
+
+
+
+
+
+ +
+
+

Disabling Auto power off can result in lens damage. The software will try to power off the camera after 15 minutes of inactivity to prevent this. However, if the Raspberry Pi crashes or is inadvertently powered off while Auto power off is disabled, the camera could remain on indefinitely.

+
+
+
+ + +
+ +
+ +
+
+ +
+ + + + + + \ No newline at end of file diff --git a/docs/users/setting-up-gphoto2-cams.qmd b/docs/users/setting-up-gphoto2-cams.qmd new file mode 100644 index 0000000..51799bd --- /dev/null +++ b/docs/users/setting-up-gphoto2-cams.qmd @@ -0,0 +1,50 @@ +--- +title: "Setting Up gPhoto2-Compatible Cameras" +date-modified: last-modified +format: html +lightbox: true +--- + +::: {.callout-note appearance="simple" collapse="true"} +## List of gPhoto2 compatible cameras + +For a comprehensive list of gPhoto2-supported cameras, visit the [official support page](http://gphoto.org/proj/libgphoto2/support.php). + +To be fully compatible with the Digitization Toolkit software, cameras should support all of the following capabilities: Image Capture, Trigger Capture, Live View, and Configuration. +::: + +## Connect the cameras to the Raspberry Pi + +- **Requirements**: + - 2 x USB 2.0 Type-A to Mini-B (5-pin) cables + +- **Connections**: + - Camera side: Plug into the middle port (marked with the USB trident icon). + - Raspberry Pi side: Plug into any USB-A port (preferably a blue USB 3.0 port). + +![USB connection from the camera side](../../_static/imgs/cannon-EOS-RebelT7-usb4cam.JPG){width=400} + +![USB connection to the Raspberry Pi - dual camera setup in ports USB 3.0](../../_static/imgs/cannon_EOS-RebelT7-usb2RPi.JPG){width=400} + +## Canon EOS Rebel T7 + +- Set the focus mode to "Manual Focus": On the lens switch, set the focus mode to MF (Manual Focus). + +![Canon EF-S 18-55mm lens attached to a DSLR camera body](../../_static/imgs/cannon-EOS-RebelT7-lensMF.jpg){width=400} + +- Set the mode to Manual: rotate the mode dial to M (Manual). + +![Canon EOS camera mode dial in manual mode](../../_static/imgs/cannon-EOS-RebelT7-Mode.jpg){width=400} + +- Retract the built-in flash: If the flash pops up after turning on the camera, push the flash head down into the closed position. + +![Canon EOS camera built-in flash in closed position](../../_static/imgs/cannon-EOS-RebelT7-flash.jpg){width=400} + +- Set Auto power off: From the camera menu, find the Auto power off setting and set it to 15 minutes. + +![Canon EOS camera menu screen auto power off](../../_static/imgs/cannon-EOS-RebelT7-config-menu.jpg){width=400} + +::: {.callout-caution appearance="simple"} +Disabling Auto power off can result in lens damage. The software will try to power off the camera after 15 minutes of inactivity to prevent this. However, if the Raspberry Pi crashes or is inadvertently powered off while Auto power off is disabled, the camera could remain on indefinitely. +::: + From fe953f4b1207d3961d44c435232c15337149b142 Mon Sep 17 00:00:00 2001 From: jairomelo Date: Sat, 30 May 2026 14:32:56 -0700 Subject: [PATCH 09/16] feat: add gPhoto2 installation step in setup script --- scripts/setup.sh | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/setup.sh b/scripts/setup.sh index 08cad4e..5111087 100644 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -112,7 +112,20 @@ else fi # --------------------------------------------------------------------------- -# 6. Database initialisation & migrations +# 6. Installing packages and binaries for cameras +# --------------------------------------------------------------------------- + +# gPhoto2 +if sudo apt install -y gphoto2 + echo "gPhoto2 package installed" + echo "" +else + echo "gPhoto2 package not available in apt package manager." + echo "" +fi + +# --------------------------------------------------------------------------- +# 7. Database initialisation & migrations # --------------------------------------------------------------------------- cd "$PROJECT_ROOT" echo "→ Starting database for migration..." From 42972e3da1926a119f9e7af05c1c20f0fba4a479 Mon Sep 17 00:00:00 2001 From: jairomelo Date: Sat, 30 May 2026 14:33:45 -0700 Subject: [PATCH 10/16] fix: update .gitignore to ignore all text files in docs directory --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4133a5b..32040b5 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,7 @@ _logs/ temp/ # Working files -docs/RP-008156-DS-2-picamera2-manual.txt +docs/*.txt # External tools fisqua/ From 941793ca990d5906d9dda4e77fff7d95331d6538 Mon Sep 17 00:00:00 2001 From: jairomelo Date: Sat, 30 May 2026 23:09:06 -0700 Subject: [PATCH 11/16] feat: configure Nginx as a reverse proxy for API and frontend traffic --- .env.example | 12 +++++------ docker-compose.pi.yml | 24 ++++++++++++++++++++++ docker-compose.yml | 2 ++ nginx.conf | 47 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 nginx.conf diff --git a/.env.example b/.env.example index 8f6cd8e..0d5ffff 100644 --- a/.env.example +++ b/.env.example @@ -20,10 +20,10 @@ LOG_LEVEL=info CAMERA_BACKEND=picamera2 # Frontend Configuration -# Set HOST_IP to the Pi's local IP for network access (e.g. 10.0.0.133). -# Leave unset or use localhost for local dev — docker-compose.yml defaults to localhost. +# Set HOST_IP to the Pi's hostname or IP for network access (e.g. digitool.local or 10.0.0.133). +# Leave unset or use localhost for local dev. HOST_IP=localhost -PUBLIC_API_BASE=http://${HOST_IP}:8000 -# Add the Pi's IP here so the backend accepts requests from network devices. -# For local dev, the defaults (localhost:3000 and :5173) are used automatically. -CORS_ORIGINS=["http://localhost:5173","http://localhost:3000"] +# Browser-side API calls are routed through Nginx (/api → backend:8000) +PUBLIC_API_BASE=http://${HOST_IP}/api +# Add origins for both the port-80 Nginx URL and the direct port-3000 fallback. +CORS_ORIGINS=["http://localhost","http://localhost:5173","http://localhost:3000"] diff --git a/docker-compose.pi.yml b/docker-compose.pi.yml index b90277f..0c4dc9e 100644 --- a/docker-compose.pi.yml +++ b/docker-compose.pi.yml @@ -9,3 +9,27 @@ services: # Override portable folders with appliance paths - /var/lib/dtk:/var/lib/dtk - /var/log/dtk:/var/log/dtk + + frontend: + environment: + # Route browser-side API calls through Nginx (same-origin, fixes Chrome PNA policy) + # http://digitool.local/api/* → Nginx strips /api → backend:8000/* + - PUBLIC_API_BASE=http://${HOST_IP:-localhost}/api + # ORIGIN must match the URL users open (port 80 via Nginx, no explicit port) + - ORIGIN=http://${HOST_IP:-localhost} + + # ── Nginx reverse proxy ──────────────────────────────────────────────────── + # Puts frontend (:3000) and backend (:8000) behind a single origin (port 80). + # This eliminates the cross-port request that Chrome's Private Network Access + # (PNA) policy blocks when the client is on a .local HTTP address. + # Access the app at: http://digitool.local (instead of :3000) + nginx: + image: nginx:1.25-alpine + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + extra_hosts: + - "host.docker.internal:host-gateway" + depends_on: + - frontend diff --git a/docker-compose.yml b/docker-compose.yml index 0958c07..9ab9c9d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -84,6 +84,8 @@ services: - NODE_ENV=production # No depends_on db — the frontend is a Node.js API proxy and never connects # to Postgres directly. Removing this lets db and frontend start in parallel. + # No depends_on db — the frontend is a Node.js API proxy and never connects + # to Postgres directly. Removing this lets db and frontend start in parallel. docs: build: diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..b518666 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,47 @@ +events { + worker_connections 1024; +} + +http { + server { + listen 80; + server_name _; + + # ── API — strip /api prefix, proxy to native backend on the host ── + # http://digitool.local/api/cameras → http://backend:8000/cameras + location /api/ { + proxy_pass http://host.docker.internal:8000/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # FastAPI auto-redirects /foo → /foo/ using the Host header, producing + # e.g. Location: http://digitool.local/collections/ + # That would fall through to the frontend (404). Rewrite it so the + # path is prefixed with /api/ again before the browser follows it. + proxy_redirect ~^https?://[^/]+(/.*)$ http://$host/api$1; + + # Disable buffering so live-preview images stream through promptly + proxy_buffering off; + proxy_read_timeout 120s; + + # Allow large uploads (captured documents / RAW files) + client_max_body_size 100m; + } + + # ── Frontend — all other traffic goes to the SvelteKit Node server ── + location / { + proxy_pass http://frontend:3000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + # WebSocket support (used by Vite HMR in dev mode) + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + } +} From f947ee686b3cdb9592c2ec5400cf806bee80a00e Mon Sep 17 00:00:00 2001 From: jairomelo Date: Sat, 30 May 2026 23:12:39 -0700 Subject: [PATCH 12/16] =?UTF-8?q?gphoto2=20=E2=86=94=20picamera2=20Full=20?= =?UTF-8?q?Integration=20Scaffolding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend | 2 +- frontend | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend b/backend index 77e3a86..49cba84 160000 --- a/backend +++ b/backend @@ -1 +1 @@ -Subproject commit 77e3a86eba3e8c4c22290cbc580c51f44aa1fbb2 +Subproject commit 49cba84bd1dc971496401c7ded5c17ef7be5cdf7 diff --git a/frontend b/frontend index e4b9723..bbc06d1 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit e4b9723c7d294f75cd748e7dc085849ca6480a17 +Subproject commit bbc06d1615f1735adcdca7ca653c1b61d3a30b28 From c65e13dc43504bca9fd34e3053aa9028cf5c1e37 Mon Sep 17 00:00:00 2001 From: jairomelo Date: Sat, 30 May 2026 23:35:02 -0700 Subject: [PATCH 13/16] gphoto cameras working :) --- backend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend b/backend index 49cba84..1711395 160000 --- a/backend +++ b/backend @@ -1 +1 @@ -Subproject commit 49cba84bd1dc971496401c7ded5c17ef7be5cdf7 +Subproject commit 17113954eea936aeac8d9330003fd55d41989e43 From 3a5083950dcae8e9a313c9118d448c94d3ad023c Mon Sep 17 00:00:00 2001 From: jairomelo Date: Sun, 31 May 2026 00:26:41 -0700 Subject: [PATCH 14/16] chore: update subproject commits for backend and frontend --- backend | 2 +- frontend | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend b/backend index 1711395..b567e84 160000 --- a/backend +++ b/backend @@ -1 +1 @@ -Subproject commit 17113954eea936aeac8d9330003fd55d41989e43 +Subproject commit b567e842b1c7666849872fc20c8eacf55bad2b89 diff --git a/frontend b/frontend index bbc06d1..f53fc4f 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit bbc06d1615f1735adcdca7ca653c1b61d3a30b28 +Subproject commit f53fc4fef6c8eb697678379e0030ab9ea9f3a92b From a4592d95815ae461b670633a25b31588f3aca7dc Mon Sep 17 00:00:00 2001 From: jairomelo Date: Sun, 31 May 2026 00:57:18 -0700 Subject: [PATCH 15/16] gphoto2 implementation --- backend | 2 +- frontend | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend b/backend index b567e84..7e25bb6 160000 --- a/backend +++ b/backend @@ -1 +1 @@ -Subproject commit b567e842b1c7666849872fc20c8eacf55bad2b89 +Subproject commit 7e25bb6f212e0e2425987ad416488eb34ffb4941 diff --git a/frontend b/frontend index f53fc4f..d8d867a 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit f53fc4fef6c8eb697678379e0030ab9ea9f3a92b +Subproject commit d8d867a66e7ad8dad1c013d53ccfd7601a425508 From 79ab3355574f582e93e0811882a49f8b6339113a Mon Sep 17 00:00:00 2001 From: jairomelo Date: Sun, 31 May 2026 01:17:10 -0700 Subject: [PATCH 16/16] docs: add changelog and release history section to README --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++++++++++ README.md | 4 ++++ 2 files changed, 46 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..268c3a9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,42 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on Keep a Changelog, and this project uses pre-1.0 semantic versioning. + +## [0.0.0-pre.2] - 2026-05-31 + +### Added + +- **DSLR camera support via gPhoto2** — Canon EOS and compatible USB cameras now work alongside picamera2. Backend auto-selects via `CAMERA_BACKEND=gphoto2` env var. +- **RAW (CR2) capture** — Selecting RAW or RAW+JPEG format now correctly saves `.cr2` files. Previously the camera silently fell back to JPEG on every capture. +- **Orientation / rotation control** — Per-camera post-capture rotation (0°, 90°, 180°, 270° CW) for scanning manuscripts in portrait orientation. JPEG captures are pixel-rotated via Pillow; CR2 files receive an EXIF Orientation tag via piexif so RAW converters auto-rotate. +- **Live preview rotation** — The camera feed in the live-preview viewport reflects the selected rotation in real time so operators can frame shots correctly before capturing. +- **Floating rotation buttons on each camera feed** — Quick-access ↺ / ↻ buttons float over each camera feed (bottom-centre). Clicking them updates both the live preview rotation and the capture rotation without opening the sidebar. State stays in sync with the sidebar orientation control. +- **Nginx reverse proxy** — All traffic now routes through Nginx on port 80 (`/api/*` → backend, `/*` → frontend). Eliminates hard-coded port references in the browser. +- **Network discoverability** — Raspberry Pi is accessible on the local network by hostname (`digitool.local`) and by IP. `HOST_IP` and CORS origins are configured dynamically at startup. +- **Documentation service** — Quarto-based docs rendered and served via Docker; includes device-setup guides for CM4 and Pi 5 + IMX519. +- **SD card distribution guide** — New developer documentation for distributing pre-imaged SD cards. +- **gPhoto2 device-setup guide** — Step-by-step instructions for connecting Canon DSLR cameras. + +### Fixed + +- **CR2 thumbnail in record viewer** — The modal image viewer (`img-viewer-frame`) now shows a renderable JPEG instead of sending the `.cr2` file directly to the browser (which browsers cannot display). The `_preview.jpg` sidecar is served when available. +- **`image_format` reverse mapping** — Camera configuration now correctly returns human-readable values (`JPEG`, `RAW`, `RAW+JPEG`) instead of internal PTP strings. +- **Capture path mismatch** — `GPhoto2Backend.capture_image()` previously returned the originally requested `.jpg` path even when the camera had saved a `.cr2` file. It now returns the actual saved path. +- **imageformat reset on every capture** — `apply_dslr_config()` was overwriting the camera's format setting to JPEG on every shot when no explicit format was configured. It now skips the PTP write when `image_format` is `None`, leaving the camera's own setting intact. +- **Record `format` field hardcoded to `"jpg"`** — The format stored in the database now reflects the actual file extension (e.g. `"cr2"`). + +### Notes + +- Project remains pre-alpha pending replication and validation on additional machines. +- Default capture orientation is 90° (portrait) to match typical book/manuscript scanning setups. +- piexif must be present in the pixi environment (`pixi run python -c "import piexif"`) for CR2 EXIF rotation to apply. + +## [0.0.0-pre.1] + +### Added + +- Initial pre-alpha release baseline. + +[0.0.0-pre.1]: https://github.com/UCSB-AMPLab/digitization-toolkit/releases/tag/pre-alpha diff --git a/README.md b/README.md index 1b0676f..740945c 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,10 @@ An open-source, modular digitization toolkit designed for low-cost, high-quality > Project currently in development. Kick-off: September 2025 > Alpha prototype planned for deployment at SBMAL in June 2026. +## Release History + +See [CHANGELOG.md](CHANGELOG.md) for version history and release notes. + *** ## Quick Start