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
/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
- Connect the phone to the router's network.
- On a computer or a second phone, open
http://<router address>:8080/in a browser (orhttps://<router address>:8443/, accepting the self-signed certificate warning). - 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.
- 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.
- Upload to Offload Router from the Camera tab as usual.
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.
| Setting | Default | Meaning |
|---|---|---|
listen | :8443 | HTTPS address. The LAN firewall zone allows it. Don't open it on the WAN. |
plainListen | :8080 | Plain-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. |
cacheDir | chosen automatically | Where 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. |
cacheMaxBytes | 0 | Maximum amount of cached data. 0 means it's limited only by free space. |
reserveBytes | 256 MB | Space always left free on the drive. |
requireMountedDrive | true | If 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. |
parallelUploads | 1 | How many uploads run at once. The app sends its own setting here, up to a maximum of 4. |
keepDoneDays | 30 | How 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.
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