feat(mdns): añadir resolver genérico + helpers de shell
- bin/resolve: hostname -> IP con orden mDNS (LAN) -> Tailscale -> caché. Reutilizable en cualquier comando (ping, curl, kubectl, nc). - bin/ssh-mdns-proxy: arreglar shebang (#\! -> #!) que hacía fallar declare -A al caer en sh; y reemplazar getent (inexistente en Termux) por python socket.gethostbyname, con detección para portabilidad. - config/bashrc.snippet: añadir aliases r/pingr/sshr/curlr. sshr usa HostKeyAlias para que SSH-por-IP no falle verificación de known_hosts. - docs/03-ssh-mdns-tailscale.md: sección nueva sobre resolve + gotchas (getent ausente, shebang bash-only, mDNS no cruza redes). - install.sh: incluir resolve en la lista de scripts a instalar. - README.md: nueva fila en la tabla de componentes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f7ad71bd3c
commit
5c4e41f6b0
|
|
@ -43,6 +43,7 @@ bash install.sh --dry-run
|
||||||
| `mdns-publish.py` | `~/.local/bin/` | Publish this device as `movil.local` (needs `zeroconf`) |
|
| `mdns-publish.py` | `~/.local/bin/` | Publish this device as `movil.local` (needs `zeroconf`) |
|
||||||
| `ssh-mdns-proxy` | `~/.local/bin/` | SSH ProxyCommand: mDNS → Tailscale |
|
| `ssh-mdns-proxy` | `~/.local/bin/` | SSH ProxyCommand: mDNS → Tailscale |
|
||||||
| `ssh-fallback` | `~/.local/bin/` | SSH ProxyCommand: Tailscale → LAN fallback |
|
| `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 |
|
| `settings.json` | `~/.claude/` | Disables auto-updater |
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
#!/data/data/com.termux/files/usr/bin/bash
|
||||||
|
# resolve: hostname -> IP con fallback mDNS (LAN) -> Tailscale DNS -> cache
|
||||||
|
# Uso: resolve <hostname>[.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 <hostname>" >&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"
|
||||||
|
|
@ -24,9 +24,13 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
# 1. mDNS resolution
|
# 1. mDNS resolution
|
||||||
IP=$("$SCRIPT_DIR/mdns-resolve" "${QUERY}.local" 2 2>/dev/null)
|
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
|
if [[ -z "$IP" ]]; then
|
||||||
|
if command -v getent >/dev/null 2>&1; then
|
||||||
IP=$(getent hosts "${QUERY}.${TAILSCALE_DOMAIN}" 2>/dev/null | awk '{print $1}')
|
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
|
fi
|
||||||
|
|
||||||
# 3. Fallback: cached IP
|
# 3. Fallback: cached IP
|
||||||
|
|
|
||||||
|
|
@ -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-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"'
|
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 ===
|
# === Optional: auto-start samba + mDNS on shell login ===
|
||||||
# Uncomment the block below if you want services up automatically.
|
# 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.
|
# Failures are logged (NOT silenced) — silent failures after a tar restore are the worst class of bug.
|
||||||
|
|
|
||||||
|
|
@ -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`. |
|
| `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-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. |
|
| `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
|
## SSH config
|
||||||
|
|
||||||
|
|
@ -58,3 +59,41 @@ When you list hosts in `~/.ssh/config`, prefer:
|
||||||
2. `<host>.YOUR-TAILNET.ts.net` for off-LAN
|
2. `<host>.YOUR-TAILNET.ts.net` for off-LAN
|
||||||
|
|
||||||
Never hardcode IPs — they rotate.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ fi
|
||||||
# 4. Install scripts
|
# 4. Install scripts
|
||||||
step 4 "Installing scripts to $BIN_DIR..."
|
step 4 "Installing scripts to $BIN_DIR..."
|
||||||
run mkdir -p "$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 cp "$SCRIPT_DIR/bin/$script" "$BIN_DIR/$script"
|
||||||
run chmod +x "$BIN_DIR/$script"
|
run chmod +x "$BIN_DIR/$script"
|
||||||
done
|
done
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue