- Go 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| cmd | ||
| docs/adr | ||
| internal | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| .releaserc.yml | ||
| AGENTS.md | ||
| config.example.yaml | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| main.go | ||
| README.md | ||
immich-wallpaper-sync
Disclaimer: this entire tool was developed with opencode, using DeepSeek v4 flash and GLM 5.3 flash as planner. It was built for a personal setup and shared as-is — review the code before trusting it with your library, and report issues/PRs if you find bugs.
A small Go CLI that pulls images from a self-hosted Immich server and syncs them into a directory to be used as desktop wallpapers. Inspired by SONDLecT/immich-wallpaper (Android companion), reimplemented for the desktop.
It only downloads — applying the wallpaper to your desktop is your environment's job (see Applying the wallpapers).
Features
- Pick from the whole library, favorites, an album, a person, or a CLIP smart
search phrase — or combine them (
search:phrases are OR'd). - One-shot
fetch(cron/systemd friendly) or a long-runningdaemonthat re-syncs every interval. - No-repeat rotation: recently shown assets and shooting dates are tracked in a state file, so a batch spans different places/sessions rather than one photo burst.
- Orientation filtering (landscape / portrait / any).
- Downloads originals for web-friendly formats (JPEG/PNG/WebP/GIF) and falls back to Immich's JPEG preview for everything else (HEIC, etc.).
- Requires camera EXIF by default, filtering out memes, screenshots and downloaded internet images.
- Read-only against Immich: only search + download endpoints are used.
Install
go install git.chaops.de/rwxd/immich-wallpaper-sync@latest
Or build from source:
git clone <this repo>
cd immich-wallpaper-sync
go build -o immich-wallpaper-sync .
Configuration
Connection info can come from CLI flags, environment variables, or a config file. Precedence: flag > env var > config file.
Environment variables use the IMMICH_ prefix (IMMICH_URL, IMMICH_API_KEY,
IMMICH_SOURCE, ...). The config file lives at
~/.config/immich-wallpaper-sync/config.yaml (YAML or TOML), or wherever
--config points. See config.example.yaml.
Create an API key in Immich under Account Settings → API Keys with the
asset.view and asset.download (read) permissions.
Usage
Download a single random landscape photo:
immich-wallpaper-sync fetch \
--url https://photos.example.com \
--api-key "$IMMICH_API_KEY" \
--source library \
--orientation landscape
Re-sync every 30 minutes in the background:
immich-wallpaper-sync daemon --interval 30m --source "album:e6f24d17-..."
Downloads land in --out (default ~/.cache/immich-wallpaper-sync), named
wallpaper-<assetid>.<ext>. The directory is pruned to --keep files (default
20).
Applying the wallpapers
The tool never sets a wallpaper; that's the user's job. Point your own
wallpaper daemon or rotation loop at the output directory. For example, on
Hyprland with awww/swww:
awww img "$(ls ~/.cache/immich-wallpaper-sync | shuf -n1)"
Sources
--source is a comma-separated list of terms:
| Term | Meaning |
|---|---|
library |
the entire library (default) |
favorites |
starred assets only |
album:<id> |
assets in an album |
person:<id> |
assets featuring a person |
search:<phrase> |
CLIP smart search, e.g. search:beach |
Example:
--source "favorites"
--source "person:389fa157-ee56-4356-a3a7-ab516660d5b3"
--source "favorites,album:e6f24d17-..."
Repeating search: ORs the results across phrases — each phrase is its own
CLIP query and the candidate pools are unioned before a random pick. This
fixes compound phrases like "beautiful nature and big architecture", which
CLIP would otherwise mangle into one query:
# "beautiful nature" OR "big architecture"
--source "search:beautiful nature,search:big architecture"
RAW files are skipped, and --require-camera (default true) drops anything
with no camera EXIF (memes, screenshots). If smart search matches nothing, the
tool falls back to the library. --threshold (0..1) tightens CLIP relevance;
higher numbers return only close matches.
Within a batch, picks are spread across distinct shooting dates and exact
duplicates are deduped by checksum, so a run doesn't pull a whole photo
burst/session from one place. Recently used dates and assets are suppressed
via the state file (see --history).
Find album/person IDs via the Immich web UI URL, or:
curl -H "x-api-key: $IMMICH_API_KEY" https://photos.example.com/api/albums
curl -H "x-api-key: $IMMICH_API_KEY" https://photos.example.com/api/people
Scheduling with systemd (user timer)
~/.config/systemd/user/immich-wallpaper-sync.service:
[Unit]
Description=Sync an Immich wallpaper
[Service]
Type=oneshot
ExecStart=%h/go/bin/immich-wallpaper-sync fetch
~/.config/systemd/user/immich-wallpaper-sync.timer:
[Unit]
Description=Sync an Immich wallpaper hourly
[Timer]
OnBootSec=5min
OnUnitActiveSec=1h
[Install]
WantedBy=timers.target
Then systemctl --user enable --now immich-wallpaper-sync.timer. For a
continuously running process, prefer immich-wallpaper-sync daemon as a
long-running service.
How it works
fetch/daemon → parse source → query /api/search/random (or
/api/search/smart for phrases) → filter orientation, camera EXIF and skip
recently shown IDs/dates → download to the output directory → prune to --keep
files. The state file (~/.cache/immich-wallpaper-sync/state.json) remembers
recent assets and dates so the rotation does not repeat.
Flags
--url Immich server URL
--api-key Immich API key
--source photo source (see table above)
--orientation any|landscape|portrait
--size original|preview
--threshold smart search relevance 0..1 (0 = server default)
--out download directory
--interval daemon re-sync interval (e.g. 30m, 1h)
--batch images to download per run
--keep number of wallpapers to keep in the output directory
--history distinct assets/dates to remember (suppress repeats)
--require-camera only pick photos with camera EXIF (default true)
--config config file path
License
MIT