Gateway pairing helpers for remote clients.
The QR payload is deliberately tiny and URL-shaped so native apps can scan it without an HTTP round trip:
vis://gateway?url=http%3A%2F%2F100.64.0.10%3A7890&token=...
Tailscale fits naturally: if a 100.64.0.0/10 interface is present we prefer it over LAN addresses, otherwise we fall back to site-local IPv4 addresses.
Gateway pairing helpers for remote clients. The QR payload is deliberately tiny and URL-shaped so native apps can scan it without an HTTP round trip: vis://gateway?url=http%3A%2F%2F100.64.0.10%3A7890&token=... Tailscale fits naturally: if a 100.64.0.0/10 interface is present we prefer it over LAN addresses, otherwise we fall back to site-local IPv4 addresses.
(candidate-hosts bind-host)Reachable hostnames/IPs worth showing in a pairing QR, in preference order. Tailscale addresses come first because they keep working off-LAN; then LAN; then the server's bind host when concrete.
Reachable hostnames/IPs worth showing in a pairing QR, in preference order. Tailscale addresses come first because they keep working off-LAN; then LAN; then the server's bind host when concrete.
(loopback-bind? host)True when host is an address only this machine can reach. A phone can never
open such a URL, so pairing against it is meaningless no matter how good the
QR is.
True when `host` is an address only this machine can reach. A phone can never open such a URL, so pairing against it is meaningless no matter how good the QR is.
(pair-bind-host)Bind address to use when --pair is requested WITHOUT an explicit --host.
Pairing on the loopback default is a contradiction: the QR would encode an address no socket is listening on and the phone would time out minutes later. Asking to pair IS asking for phone access, so it binds every interface.
Binding only the Tailscale IP was the earlier, narrower choice and it broke
the pairing link's own promise: pairing-url advertises the LAN addresses as
alt= fallbacks, but nothing listened there, so a phone whose Tailscale was
off or paused failed on EVERY candidate while sitting on the same Wi-Fi as
the gateway. 0.0.0.0 is non-loopback exactly like the Tailscale IP was, so
server/start! still forces the bearer token: this widens reach, never auth.
Bind address to use when `--pair` is requested WITHOUT an explicit `--host`. Pairing on the loopback default is a contradiction: the QR would encode an address no socket is listening on and the phone would time out minutes later. Asking to pair IS asking for phone access, so it binds every interface. Binding only the Tailscale IP was the earlier, narrower choice and it broke the pairing link's own promise: `pairing-url` advertises the LAN addresses as `alt=` fallbacks, but nothing listened there, so a phone whose Tailscale was off or paused failed on EVERY candidate while sitting on the same Wi-Fi as the gateway. `0.0.0.0` is non-loopback exactly like the Tailscale IP was, so `server/start!` still forces the bearer token: this widens reach, never auth.
(pairing-url {:keys [host port token]})The vis://gateway deep link. url= is the best guess (Tailscale first), and
alt= carries the remaining routable hosts so a phone that cannot reach the
first one (no Tailscale, different LAN) falls back instead of failing. IPv4
link-local (169.254/16) is dropped from the alternates: no phone can route it,
and every extra host makes the QR denser.
The `vis://gateway` deep link. `url=` is the best guess (Tailscale first), and `alt=` carries the remaining routable hosts so a phone that cannot reach the first one (no Tailscale, different LAN) falls back instead of failing. IPv4 link-local (169.254/16) is dropped from the alternates: no phone can route it, and every extra host makes the QR denser.
(print-pairing! {:keys [require-token? emit host] :or {emit println} :as opts})Emit the companion pairing block (title, reachable hosts, vis:// URL, and a
terminal QR). Each line goes through emit (default println); CLI callers
whose stdout is redirected to the log file pass a real-terminal writer so the
QR is actually visible. Returns the pairing URL payload.
Bound to loopback there is nothing to pair with: the interface scan still finds Tailscale/LAN addresses, but the listener is not on them, so a QR built from those would encode a URL that times out — the failure landing on the phone, minutes later, looking like a broken app. Refuse and print the restart command instead; returns nil.
Emit the companion pairing block (title, reachable hosts, `vis://` URL, and a terminal QR). Each line goes through `emit` (default `println`); CLI callers whose stdout is redirected to the log file pass a real-terminal writer so the QR is actually visible. Returns the pairing URL payload. Bound to loopback there is nothing to pair with: the interface scan still finds Tailscale/LAN addresses, but the listener is not on them, so a QR built from those would encode a URL that times out — the failure landing on the phone, minutes later, looking like a broken app. Refuse and print the restart command instead; returns nil.
(tailscale-hosts)Tailscale (100.64/10) IPv4 addresses currently bound to a live interface, in
discovery order. Empty when Tailscale is not up, so callers can fall back to
LAN / 0.0.0.0 guidance.
Tailscale (100.64/10) IPv4 addresses currently bound to a live interface, in discovery order. Empty when Tailscale is not up, so callers can fall back to LAN / `0.0.0.0` guidance.
(terminal-qr text)Render text as a terminal QR code using Unicode half-blocks. Returns a string
so tests and CLI callers can decide where to print it.
Two properties matter for a phone camera to actually decode this:
qrencode -t UTF8, so the
code reads correctly on the dark terminal themes everyone runs. Painting
dark modules instead produces a photo-negative that most scanners reject.Render `text` as a terminal QR code using Unicode half-blocks. Returns a string so tests and CLI callers can decide where to print it. Two properties matter for a phone camera to actually decode this: - a full 4-module quiet zone (the spec minimum; a 1-module margin scans only on a perfect white background), padded to an even module height so the bottom quiet zone survives the half-block row packing; - the block glyph paints the *light* modules, like `qrencode -t UTF8`, so the code reads correctly on the dark terminal themes everyone runs. Painting dark modules instead produces a photo-negative that most scanners reject.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |