Skip to content

Offload Router setup

Offload Router turns an OpenWrt travel router into an upload cache. The phone sends photos to the router over the local network. Once the router has a complete copy on its USB drive, the phone counts the file as done and can disconnect. The router then checks the copy and uploads it to Immich or SFTP itself, retrying for as long as the internet is down.

What you need

An OpenWrt router

A GL.iNet AR300M (the 128 MB NAND model) or a similar OpenWrt router. The program is a single 12 MB file with no dependencies.

A USB stick or SSD

This goes in the router's USB port and holds the cache. ext4 is the recommended format.

You'll also need a computer with the Offload source code and Go installed, to build and copy the program to the router over SSH.

Set up the USB drive

On the router, over SSH:

opkg update
opkg install kmod-usb-storage kmod-fs-ext4 block-mount e2fsprogs
mkfs.ext4 -L offload /dev/sda1          # erases the drive
block detect | uci import fstab
uci set fstab.@mount[-1].enabled=1 && uci commit fstab
block mount && df -h /mnt/sda1
On GL.iNet firmware, USB drives are usually mounted for you under /mnt/, named after the partition label or device. Check with df -h. You can use a drive that already has a writable partition with free space as it is, without formatting it.

Install

From the router/ folder of the Offload source, on your computer:

$ make deploy                       # or: make deploy ROUTER=root@<router address>

This installs /usr/bin/offload-router and the /etc/init.d/offload-router service, starts it (which opens the 10-minute pairing window), and adds /etc/offload-router/ to the files kept across firmware upgrades.

The default router address is GL.iNet's 192.168.8.1. To stop being asked for the router's password on every command, run make authorize-key once to install your SSH public key on it.

Pair the app

  1. Connect the phone to the router's network.
  2. On a computer or a second phone, open http://<router address>:8080/ in a browser (or https://<router address>:8443/, accepting the self-signed certificate warning).
  3. For 10 minutes after the service starts, the page shows a pairing QR code. In the app, open Settings → Offload Router, tap Scan QR code and scan it. This fills in the router's address, token and certificate fingerprint.
  4. Choose where the router should upload to, and tap Save & test connection. The app sends that destination's settings to the router, and the router tests its own connection to the server.
  5. Upload to Offload Router from the Camera tab as usual.
The QR code contains the router's token, so it only appears for those 10 minutes. To show it again, restart the router (unplug it and plug it back in) or run offload-router pair over SSH, then reload the page. At other times the page shows only counts and progress, never file names, settings or the token. make token prints the same details if you'd rather type them in.

Settings

Settings are in /etc/offload-router/config.json, which is created the first time the service starts.

SettingDefaultMeaning
listen:8443HTTPS address. The LAN firewall zone allows it. Don't open it on the WAN.
plainListen:8080Plain-HTTP address, which the app sends files to. Anyone on the router's network can read the photos and the token on it. "" turns it off, and the app uses HTTPS instead.
cacheDirchosen automaticallyWhere files are kept until they're uploaded. If empty, the first start picks offload-cache on the mounted USB drive with the most free space (at least 1 GB) and saves the choice here.
cacheMaxBytes0Maximum amount of cached data. 0 means it's limited only by free space.
reserveBytes256 MBSpace always left free on the drive.
requireMountedDrivetrueIf cacheDir would be on the router's own flash storage, for example before the drive is mounted at boot, wait (logging once) instead of starting.
parallelUploads1How many uploads run at once. The app sends its own setting here, up to a maximum of 4.
keepDoneDays30How long finished and failed uploads are remembered.

The app sends the destination settings (immich, sftp). Only edit the file by hand for things the app can't send, and then run /etc/init.d/offload-router restart.

Why plain HTTP? On an AR300M, TLS decrypts at about 2 MB/s and SHA-1 manages about 5 MB/s. Either one on the receiving path would be slower than the Wi-Fi. So files arrive over plain HTTP and are written straight to the drive, and they're checksummed afterwards. The only network they cross is the router's own.

Immich with a client certificate (mTLS)

The phone can't share its client certificate, because Android keeps the private key locked in the device. Instead, copy a PEM certificate and key to the router, and add them to the immich section:

"clientCertFile": "/etc/offload-router/client.crt",
"clientKeyFile": "/etc/offload-router/client.key"

To convert a .p12 file:

openssl pkcs12 -in cert.p12 -clcerts -nokeys -out client.crt
openssl pkcs12 -in cert.p12 -nocerts -nodes -out client.key

How it behaves

Resumable transfers

Interrupted transfers from the phone resume where they stopped.

Checksums afterwards

A file is checksummed once it's on the drive and the phone has gone quiet, and only when needed: to check what the phone sent, or because Immich detects duplicates by checksum. A copy that doesn't match is discarded, and the file shows as failed in the app so you can send it again.

Status page

http://<router>:8080/ shows live progress for files arriving from the phone, copies being checksummed, and uploads to the server. It needs no token, so it shows sizes and speeds but no file names.

Retries

Failures that can fix themselves, such as no internet or server errors, are retried with growing gaps, up to every 10 minutes, for as long as it takes. Failures that can't, such as a wrong password or a changed host key, stop and show in the app, where you can retry them.

Duplicates and file paths

Duplicates are detected the same way the app detects them. SFTP paths use the same patterns and the phone's time zone, so files end up where the phone would have put them.

Restarts

A restart or power cut loses nothing. Interrupted uploads start again, and files already received stay queued.

Development

$ make test          # unit tests, including in-process SFTP and Immich fakes
$ make run-local     # run on this PC; the emulator reaches it at http://10.0.2.2:8080
$ make logs          # follow the router's log