diff --git a/README.md b/README.md index abbc356..090bbe5 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ bash install.sh --dry-run | `mdns-publish.py` | `~/.local/bin/` | Publish this device as `movil.local` (needs `zeroconf`) | | `ssh-mdns-proxy` | `~/.local/bin/` | SSH ProxyCommand: mDNS → Tailscale | | `ssh-fallback` | `~/.local/bin/` | SSH ProxyCommand: Tailscale → LAN fallback | +| `resolve` | `~/.local/bin/` | Generic host resolver: mDNS → Tailscale → cache (for any command) | | `settings.json` | `~/.claude/` | Disables auto-updater | ## Usage diff --git a/bin/resolve b/bin/resolve new file mode 100755 index 0000000..a6c22e6 --- /dev/null +++ b/bin/resolve @@ -0,0 +1,47 @@ +#!/data/data/com.termux/files/usr/bin/bash +# resolve: hostname -> IP con fallback mDNS (LAN) -> Tailscale DNS -> cache +# Uso: resolve [.local|.tailnet] +# Ej: resolve lenovo # -> 100.69.236.16 +# ping $(resolve hp62a) +# curl "http://$(resolve dell):8080" +set -e +HOST="${1:-}" +[[ -z "$HOST" ]] && { echo "Uso: resolve " >&2; exit 1; } + +CACHE="$HOME/.ssh/resolve-cache" +TAILSCALE_DOMAIN="${TAILSCALE_DOMAIN:-tailb0bb74.ts.net}" +MDNS_TIMEOUT="${MDNS_TIMEOUT:-1}" + +QUERY="${HOST%.local}" +QUERY="${QUERY%.${TAILSCALE_DOMAIN}}" + +declare -A ALIASES=( + [dell]="dell-latitude3400" + [lenovo]="lenovo-ideapad" +) +[[ -n "${ALIASES[$QUERY]:-}" ]] && QUERY="${ALIASES[$QUERY]}" + +# 1. mDNS multicast (LAN) +IP=$("$HOME/.local/bin/mdns-resolve" "${QUERY}.local" "$MDNS_TIMEOUT" 2>/dev/null || true) + +# 2. Tailscale MagicDNS (remoto) +if [[ -z "$IP" ]]; then + IP=$(python -c "import socket; print(socket.gethostbyname('${QUERY}.${TAILSCALE_DOMAIN}'))" 2>/dev/null || true) +fi + +# 3. Cache +if [[ -z "$IP" && -f "$CACHE" ]]; then + IP=$(grep "^${QUERY} " "$CACHE" 2>/dev/null | awk '{print $2}') +fi + +if [[ -z "$IP" ]]; then + echo "Error: no se resuelve '${HOST}'" >&2 + exit 1 +fi + +mkdir -p "$(dirname "$CACHE")" +grep -v "^${QUERY} " "$CACHE" > "${CACHE}.tmp" 2>/dev/null || true +echo "${QUERY} ${IP}" >> "${CACHE}.tmp" +mv "${CACHE}.tmp" "$CACHE" + +echo "$IP" diff --git a/bin/ssh-mdns-proxy b/bin/ssh-mdns-proxy index f2f945c..b694309 100755 --- a/bin/ssh-mdns-proxy +++ b/bin/ssh-mdns-proxy @@ -24,9 +24,13 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # 1. mDNS resolution IP=$("$SCRIPT_DIR/mdns-resolve" "${QUERY}.local" 2 2>/dev/null) -# 2. Fallback: Tailscale DNS +# 2. Fallback: Tailscale DNS (Termux carece de getent, usar python si esta disponible) if [[ -z "$IP" ]]; then - IP=$(getent hosts "${QUERY}.${TAILSCALE_DOMAIN}" 2>/dev/null | awk '{print $1}') + if command -v getent >/dev/null 2>&1; then + IP=$(getent hosts "${QUERY}.${TAILSCALE_DOMAIN}" 2>/dev/null | awk '{print $1}') + else + IP=$(python -c "import socket; print(socket.gethostbyname('${QUERY}.${TAILSCALE_DOMAIN}'))" 2>/dev/null) + fi fi # 3. Fallback: cached IP diff --git a/config/bashrc.snippet b/config/bashrc.snippet index 52e29b2..8b660b4 100644 --- a/config/bashrc.snippet +++ b/config/bashrc.snippet @@ -27,6 +27,21 @@ alias mdns-status='pgrep -af mdns-publish || echo "mDNS publisher is not running alias mdns-restart='pkill -f mdns-publish; sleep 1; nohup python ~/.local/bin/mdns-publish.py >>~/.cache/mdns.log 2>&1 & disown && echo "mDNS restarted"' alias mdns-stop='pkill -f mdns-publish && echo "mDNS stopped"' +# === Generic host resolver (resolve: mDNS -> Tailscale -> cache) === +# Works in any command: ping $(resolve lenovo) / curl "http://$(resolve hp62a)" +alias r='resolve' +pingr() { ping -c 3 "$(resolve "$1")"; } +curlr() { local h="$1"; shift; curl "$@" "http://$(resolve "$h")"; } +# HostKeyAlias reuses the known_hosts entry for the Tailscale FQDN +# so SSH-by-IP does not fail host-key verification. +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")" "$@" +} + # === Optional: auto-start samba + mDNS on shell login === # Uncomment the block below if you want services up automatically. # Failures are logged (NOT silenced) — silent failures after a tar restore are the worst class of bug. diff --git a/docs/03-ssh-mdns-tailscale.md b/docs/03-ssh-mdns-tailscale.md index 778018c..784666b 100644 --- a/docs/03-ssh-mdns-tailscale.md +++ b/docs/03-ssh-mdns-tailscale.md @@ -10,6 +10,7 @@ Termux's Bionic libc does **not** resolve `.local` names. This repo ships small | `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 @@ -58,3 +59,41 @@ When you list hosts in `~/.ssh/config`, prefer: 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. diff --git a/install.sh b/install.sh index f386751..d002f24 100755 --- a/install.sh +++ b/install.sh @@ -59,7 +59,7 @@ fi # 4. Install scripts step 4 "Installing scripts to $BIN_DIR..." run mkdir -p "$BIN_DIR" -for script in clauded claude-check-env claude-update mdns-resolve mdns-publish.py ssh-mdns-proxy ssh-fallback; do +for script in clauded claude-check-env claude-update mdns-resolve mdns-publish.py ssh-mdns-proxy ssh-fallback resolve; do run cp "$SCRIPT_DIR/bin/$script" "$BIN_DIR/$script" run chmod +x "$BIN_DIR/$script" done