claude-code-termux/docs/03-ssh-mdns-tailscale.md

100 lines
4.4 KiB
Markdown
Raw Normal View History

# 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 `<host>.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. `<host>.local` for fast LAN access
2. `<host>.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.