# 03 — SSH, mDNS and Tailscale on Termux Termux's Bionic libc does **not** resolve `.local` names. This repo ships small Python helpers that fix this without needing root or a custom resolver. ## Scripts involved | Script | Role | |--------|------| | `mdns-resolve` | Resolve `.local` via raw multicast UDP. Returns the IP or non-zero. | | `mdns-publish.py` | Publish this device on the LAN as `movil.local` (or whatever you choose) using `zeroconf`. | | `ssh-mdns-proxy` | SSH `ProxyCommand` that tries mDNS first, then Tailscale DNS. Used from `~/.ssh/config`. | | `ssh-fallback` | Lower-level fallback: tries Tailscale, then LAN. Useful when Tailscale is flaky. | | `resolve` | Generic resolver for **any** command (ping, curl, kubectl…). Same order: mDNS → Tailscale → cache. | ## SSH config Drop the contents of `config/ssh_config.example` into `~/.ssh/config` (or merge it). The key entries: ```ssh-config Host *.local ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p Host lenovo-ideapad ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p Host *.YOUR-TAILNET.ts.net User YOUR_USER ``` Adjust the username and the tailnet domain. The proxy command will try mDNS first, then fall through to your Tailscale magic-DNS name (env `TAILSCALE_DOMAIN`). ## Publishing this device as `movil.local` If you want the device to be reachable as `movil.local` from your LAN: ```bash # One-shot foreground (to see errors): python ~/.local/bin/mdns-publish.py # Background (typical use, from ~/.bashrc): nohup python ~/.local/bin/mdns-publish.py >>~/.cache/mdns.log 2>&1 & disown ``` **Gotcha:** `zeroconf` (the pip package) is **not** part of a `pkg`-managed install. If you restore Termux from a `tar` backup, `zeroconf` will be missing from `$PREFIX/lib/python*/site-packages` and the script will silently fail with `ModuleNotFoundError`. Re-run `pip install zeroconf` after every restore. ## Logging, not silencing The `.bashrc` auto-start lines in this repo (`config/bashrc.snippet`) deliberately log to a file instead of `/dev/null`. Silent failures are the single most painful class of bug after a restore — keep the logs. ## Tailscale Tailscale on Termux runs **as the Android app**, not as a CLI inside Termux. You can't `tailscale up` from a shell. The app provides magic DNS for `*.YOUR-TAILNET.ts.net`, which Bionic-libc resolves fine (it's just a regular DNS query). When you list hosts in `~/.ssh/config`, prefer: 1. `.local` for fast LAN access 2. `.YOUR-TAILNET.ts.net` for off-LAN Never hardcode IPs — they rotate. ## Generic resolver (`resolve`) for any command `ssh-mdns-proxy` only helps SSH. For everything else — `ping`, `curl`, `kubectl`, `nc` — use `resolve`. It applies the same lookup order and updates the cache: ```bash resolve lenovo # -> 100.69.236.16 ping $(resolve hp62a) curl "http://$(resolve dell):8080" ``` Environment knobs: - `TAILSCALE_DOMAIN` — defaults to `tailb0bb74.ts.net`. Set to your own tailnet. - `MDNS_TIMEOUT` — seconds for multicast (`1` by default, low so remote calls stay snappy). Short aliases and helper functions to drop into `~/.bashrc` (see `config/bashrc.snippet`): ```bash alias r='resolve' pingr() { ping -c 3 "$(resolve "$1")"; } sshr() { local h="$1"; shift local canonical="$h" case "$h" in dell) canonical="dell-latitude3400";; lenovo) canonical="lenovo-ideapad";; esac ssh -o "HostKeyAlias=${canonical}.${TAILSCALE_DOMAIN:-tailb0bb74.ts.net}" \ "YOUR_USER@$(resolve "$h")" "$@" } curlr() { local h="$1"; shift; curl "$@" "http://$(resolve "$h")"; } ``` `HostKeyAlias` in `sshr` is important: since we connect by IP, SSH would fail host-key verification otherwise. Aliasing back to the canonical Tailscale FQDN reuses the `known_hosts` entry you already trust. ## Gotchas found the hard way - **`getent` doesn't exist on Termux.** Any script that shells out to it for DNS will silently fail. `resolve` and `ssh-mdns-proxy` fall back to `python -c "import socket; socket.gethostbyname(...)"` — that's the portable way on Bionic. - **Shebang matters.** `#!/data/data/com.termux/files/usr/bin/bash` is required for `declare -A` (associative arrays). A stray backslash (`#\!`) makes the kernel fall through to `sh`, which silently rejects bash-only syntax with `Syntax error: "(" unexpected`. - **mDNS off-LAN is a no-op.** Multicast (224.0.0.251) does not cross networks. Over mobile data the Tailscale fallback is the only path that works.