From c6d54ea5c98c8cea73a29ff50deb8d782715f945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Eduardo=20Garc=C3=ADa=20M=C3=A1rquez?= Date: Sun, 19 Jul 2026 20:56:51 -0500 Subject: [PATCH] refactor: extraer mDNS/SSH host-resolution al repo mdns-termux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Los helpers mDNS ya no viven aquí. Ahora se encuentran en el repo hermano: https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux Cambios: - bin/: eliminados mdns-resolve, mdns-publish.py, ssh-mdns-proxy, ssh-fallback y resolve. - config/: eliminado ssh_config.example. bashrc.snippet queda solo con entorno Claude + aliases Samba (referencia al repo mDNS al final). - docs/03-ssh-mdns-tailscale.md eliminado. Docs renumerados: 04-samba.md -> 03-samba.md, 05-claude-code.md -> 04-claude-code.md. - README, TROUBLESHOOTING, 02-base-packages y 03-samba: enlaces al repo mdns-termux donde antes había docs internas. - install.sh: no copia los scripts mDNS ni instala zeroconf, y avisa del repo hermano al finalizar. TOTAL 9 -> 8. Motivación: los helpers de resolución son útiles sin Claude Code (pueden usarse por cualquier usuario Termux), y desacoplarlos evita que la instalación de Claude arrastre setup de red innecesario. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 27 ++--- bin/mdns-publish.py | 73 ------------- bin/mdns-resolve | 103 ------------------ bin/resolve | 47 -------- bin/ssh-fallback | 23 ---- bin/ssh-mdns-proxy | 52 --------- config/bashrc.snippet | 33 ++---- config/ssh_config.example | 29 ----- docs/02-base-packages.md | 19 +++- docs/{04-samba.md => 03-samba.md} | 2 +- docs/03-ssh-mdns-tailscale.md | 99 ----------------- docs/{05-claude-code.md => 04-claude-code.md} | 0 docs/TROUBLESHOOTING.md | 11 +- install.sh | 17 ++- 14 files changed, 44 insertions(+), 491 deletions(-) delete mode 100755 bin/mdns-publish.py delete mode 100755 bin/mdns-resolve delete mode 100755 bin/resolve delete mode 100755 bin/ssh-fallback delete mode 100755 bin/ssh-mdns-proxy delete mode 100644 config/ssh_config.example rename docs/{04-samba.md => 03-samba.md} (89%) delete mode 100644 docs/03-ssh-mdns-tailscale.md rename docs/{05-claude-code.md => 04-claude-code.md} (100%) diff --git a/README.md b/README.md index 090bbe5..4b9422a 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,12 @@ Claude Code v2.1.113+ switched to native binaries, breaking Termux/Android compa - **v2.1.112** (last pure Node.js release) with auto-update protection - **termux-chroot wrapper** that fixes the `/tmp` permission issue -- **mDNS resolver** for SSH without hardcoded IPs - **Safe updater** that respects the version ceiling +For SSH/mDNS/Tailscale hostname resolution on Termux (used to reach dev +machines from the phone) see the companion repo: +[**mdns-termux**](https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux). + ## Requirements - [Termux](https://f-droid.org/en/packages/com.termux/) (F-Droid recommended) @@ -39,11 +42,6 @@ bash install.sh --dry-run | `clauded` | `~/.local/bin/` | Wrapper with termux-chroot for /tmp | | `claude-check-env` | `~/.local/bin/` | Environment validator | | `claude-update` | `~/.local/bin/` | Safe updater (ceiling-aware) | -| `mdns-resolve` | `~/.local/bin/` | Pure Python mDNS resolver | -| `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 @@ -59,14 +57,11 @@ claude-check-env claude-update ``` -## SSH with Dynamic Resolution +## SSH with dynamic host resolution -The included `ssh-mdns-proxy` resolves hostnames via mDNS first, then Tailscale DNS, then cache. See `config/ssh_config.example` for setup. - -```bash -# Set your Tailscale domain (optional, has a default) -export TAILSCALE_DOMAIN="your-tailnet.ts.net" -``` +Moved out to a dedicated project so it can be reused without Claude Code: +[**mdns-termux**](https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux) +— mDNS + Tailscale resolver, `resolve` CLI, SSH `ProxyCommand`. ## Version Ceiling @@ -82,10 +77,10 @@ Step-by-step guides for each piece of the stack: |---|-----|--------| | 01 | [docs/01-termux-fresh-install.md](docs/01-termux-fresh-install.md) | Clean Termux install (F-Droid / GitHub Releases, not Play) | | 02 | [docs/02-base-packages.md](docs/02-base-packages.md) | Required `pkg` / `pip` / `npm` packages | -| 03 | [docs/03-ssh-mdns-tailscale.md](docs/03-ssh-mdns-tailscale.md) | SSH, `.local` resolution, Tailscale | -| 04 | [docs/04-samba.md](docs/04-samba.md) | Samba server on port 4450 + `msg.lock` gotcha | -| 05 | [docs/05-claude-code.md](docs/05-claude-code.md) | Claude Code internals (version ceiling, `clauded`, settings) | +| 03 | [docs/03-samba.md](docs/03-samba.md) | Samba server on port 4450 + `msg.lock` gotcha | +| 04 | [docs/04-claude-code.md](docs/04-claude-code.md) | Claude Code internals (version ceiling, `clauded`, settings) | | — | [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | Common errors and fixes | +| — | [mdns-termux](https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux) | Companion repo: SSH `.local` / Tailscale hostname resolution | ## Troubleshooting diff --git a/bin/mdns-publish.py b/bin/mdns-publish.py deleted file mode 100755 index 189c946..0000000 --- a/bin/mdns-publish.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/data/data/com.termux/files/usr/bin/python -"""Publica servicios via mDNS/Zeroconf: SSH + Samba""" -import socket -import signal -import sys -from zeroconf import ServiceInfo, Zeroconf, IPVersion - -HOSTNAME = 'movil' - -SERVICES = [ - { - 'type': '_ssh._tcp.local.', - 'port': 8022, - 'props': {'description': 'Termux SSH'}, - }, - { - 'type': '_smb._tcp.local.', - 'port': 4450, - 'props': {'description': 'Termux Samba', 'path': '/'}, - }, -] - - -def get_local_ip(): - try: - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.connect(('8.8.8.8', 80)) - ip = s.getsockname()[0] - s.close() - return ip - except Exception: - return '127.0.0.1' - - -def main(): - ip = get_local_ip() - addr = socket.inet_aton(ip) - zc = Zeroconf(ip_version=IPVersion.V4Only) - infos = [] - - for svc in SERVICES: - info = ServiceInfo( - svc['type'], - f'{HOSTNAME}.{svc["type"]}', - addresses=[addr], - port=svc['port'], - properties=svc['props'], - server=f'{HOSTNAME}.local.', - ) - zc.register_service(info) - infos.append(info) - print(f'Publicando {HOSTNAME}.local {svc["type"]} -> {ip}:{svc["port"]}') - - def cleanup(sig, frame): - print('Deteniendo mDNS...') - for info in infos: - zc.unregister_service(info) - zc.close() - sys.exit(0) - - signal.signal(signal.SIGINT, cleanup) - signal.signal(signal.SIGTERM, cleanup) - - try: - signal.pause() - except AttributeError: - import time - while True: - time.sleep(3600) - - -if __name__ == '__main__': - main() diff --git a/bin/mdns-resolve b/bin/mdns-resolve deleted file mode 100755 index 81d954f..0000000 --- a/bin/mdns-resolve +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -""" -Resolve hostname.local via mDNS multicast (pure Python, no dependencies). -Usage: mdns-resolve [timeout_seconds] -Returns: IP address on stdout, or exit 1 on failure. -""" -import sys -import socket -import struct -import select -import time - - -def build_mdns_query(hostname: str) -> bytes: - header = struct.pack('>HHHHHH', 0, 0, 1, 0, 0, 0) - question = b'' - for part in hostname.rstrip('.').split('.'): - question += bytes([len(part)]) + part.encode() - question += b'\x00' - question += struct.pack('>HH', 1, 1) # Type A, Class IN - return header + question - - -def parse_mdns_response(data: bytes) -> str | None: - if len(data) < 12: - return None - qdcount = struct.unpack('>H', data[4:6])[0] - ancount = struct.unpack('>H', data[6:8])[0] - if ancount == 0: - return None - - offset = 12 - for _ in range(qdcount): - while offset < len(data) and data[offset] != 0: - if data[offset] & 0xc0 == 0xc0: - offset += 2 - break - offset += data[offset] + 1 - else: - offset += 1 - offset += 4 - - for _ in range(ancount): - while offset < len(data): - if data[offset] & 0xc0 == 0xc0: - offset += 2 - break - elif data[offset] == 0: - offset += 1 - break - else: - offset += data[offset] + 1 - if offset + 10 > len(data): - break - rtype, rclass, ttl, rdlength = struct.unpack('>HHIH', data[offset:offset+10]) - offset += 10 - if rtype == 1 and rdlength == 4 and offset + 4 <= len(data): - return socket.inet_ntoa(data[offset:offset+4]) - offset += rdlength - return None - - -def resolve_mdns(hostname: str, timeout: float = 2.0) -> str | None: - if not hostname.endswith('.local'): - hostname += '.local' - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.setblocking(False) - sock.sendto(build_mdns_query(hostname), ('224.0.0.251', 5353)) - end_time = time.time() + timeout - while time.time() < end_time: - ready, _, _ = select.select([sock], [], [], 0.1) - if ready: - try: - data, _ = sock.recvfrom(4096) - ip = parse_mdns_response(data) - if ip: - sock.close() - return ip - except Exception: - pass - sock.close() - except Exception: - pass - return None - - -def main(): - if len(sys.argv) < 2: - print("Usage: mdns-resolve [timeout]", file=sys.stderr) - sys.exit(1) - hostname = sys.argv[1] - timeout = float(sys.argv[2]) if len(sys.argv) > 2 else 2.0 - ip = resolve_mdns(hostname, timeout) - if ip: - print(ip) - else: - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/bin/resolve b/bin/resolve deleted file mode 100755 index a6c22e6..0000000 --- a/bin/resolve +++ /dev/null @@ -1,47 +0,0 @@ -#!/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-fallback b/bin/ssh-fallback deleted file mode 100755 index bcd6ac1..0000000 --- a/bin/ssh-fallback +++ /dev/null @@ -1,23 +0,0 @@ -#!/data/data/com.termux/files/usr/bin/bash -# -# ssh-fallback: Proxy para SSH con fallback automático -# Usado como ProxyCommand en ~/.ssh/config -# -# Uso: ssh-fallback - -TAILSCALE_HOST="$1" -LAN_IP="$2" -PORT="${3:-22}" - -# Intentar Tailscale primero (1 segundo timeout) -if ping -c 1 -W 1 "$TAILSCALE_HOST" &>/dev/null 2>&1; then - exec nc -w 5 "$TAILSCALE_HOST" "$PORT" -fi - -# Fallback a IP LAN -if ping -c 1 -W 1 "$LAN_IP" &>/dev/null 2>&1; then - exec nc -w 5 "$LAN_IP" "$PORT" -fi - -# Último intento: Tailscale sin verificar -exec nc -w 10 "$TAILSCALE_HOST" "$PORT" diff --git a/bin/ssh-mdns-proxy b/bin/ssh-mdns-proxy deleted file mode 100755 index b694309..0000000 --- a/bin/ssh-mdns-proxy +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash -# ssh-mdns-proxy: Resolve .local via mDNS with fallbacks, then connect -# -# Resolution order: mDNS → Tailscale DNS → cached IP -# Usage: ssh-mdns-proxy [port] -# -# Configure your Tailscale domain: -TAILSCALE_DOMAIN="${TAILSCALE_DOMAIN:-tailb0bb74.ts.net}" - -HOST="$1" -PORT="${2:-22}" -CACHE="$HOME/.ssh/mdns-cache" -QUERY="${HOST%.local}" - -# Alias mapping (short name -> mDNS hostname) -declare -A ALIASES=( - [dell]="dell-latitude3400" - [lenovo]="lenovo-ideapad" -) -[[ -n "${ALIASES[$QUERY]}" ]] && QUERY="${ALIASES[$QUERY]}" - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - -# 1. mDNS resolution -IP=$("$SCRIPT_DIR/mdns-resolve" "${QUERY}.local" 2 2>/dev/null) - -# 2. Fallback: Tailscale DNS (Termux carece de getent, usar python si esta disponible) -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}') - else - IP=$(python -c "import socket; print(socket.gethostbyname('${QUERY}.${TAILSCALE_DOMAIN}'))" 2>/dev/null) - fi -fi - -# 3. Fallback: cached IP -if [[ -z "$IP" && -f "$CACHE" ]]; then - IP=$(grep "^${QUERY} " "$CACHE" 2>/dev/null | awk '{print $2}') -fi - -if [[ -z "$IP" ]]; then - echo "Error: cannot resolve ${HOST}" >&2 - exit 1 -fi - -# Save to cache -mkdir -p "$(dirname "$CACHE")" -grep -v "^${QUERY} " "$CACHE" > "${CACHE}.tmp" 2>/dev/null || true -echo "${QUERY} ${IP}" >> "${CACHE}.tmp" -mv "${CACHE}.tmp" "$CACHE" - -exec nc "$IP" "$PORT" diff --git a/config/bashrc.snippet b/config/bashrc.snippet index 8b660b4..a67919d 100644 --- a/config/bashrc.snippet +++ b/config/bashrc.snippet @@ -14,8 +14,8 @@ export PATH="$HOME/.local/bin:$PATH" # Prevent Claude Code auto-updater (enforced at shell level too) export DISABLE_AUTOUPDATER=1 -# === Optional: helper aliases for samba + mDNS services === -# Only useful if you set up smbd and mdns-publish (see docs/04-samba.md, docs/03-ssh-mdns-tailscale.md). +# === Optional: Samba service helpers === +# Only useful if you set up smbd (see docs/03-samba.md). alias smb-start='smbd -D -s ~/.config/samba/smb.conf 2>>~/.config/samba/samba-startup.log && echo "Samba started on port 4450"' alias smb-stop='pkill smbd && echo "Samba stopped"' @@ -23,29 +23,12 @@ alias smb-restart='pkill smbd 2>/dev/null; sleep 1; smbd -D -s ~/.config/samba/s alias smb-status='pgrep -a smbd && echo "Port: 4450" || echo "Samba is not running"' alias smb-log='tail -50 ~/.config/samba/samba.log' -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. +# === Optional: auto-start Samba on shell login === +# Uncomment if you want smbd up automatically. # Failures are logged (NOT silenced) — silent failures after a tar restore are the worst class of bug. # -# mkdir -p ~/.cache # pgrep -x smbd >/dev/null 2>&1 || smbd -D -s ~/.config/samba/smb.conf 2>>~/.config/samba/samba-startup.log -# pgrep -f "mdns-publish" >/dev/null 2>&1 || nohup python ~/.local/bin/mdns-publish.py >>~/.cache/mdns.log 2>&1 & disown + +# === Companion repo: mdns-termux === +# For host resolution (.local + Tailscale) install: +# https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux diff --git a/config/ssh_config.example b/config/ssh_config.example deleted file mode 100644 index 08e6d6f..0000000 --- a/config/ssh_config.example +++ /dev/null @@ -1,29 +0,0 @@ -# SSH Config for Termux with mDNS/Tailscale resolution -# Copy relevant sections to ~/.ssh/config - -Host * - IdentityFile ~/.ssh/id_ed25519 - IdentitiesOnly yes - ServerAliveInterval 60 - ServerAliveCountMax 3 - ConnectTimeout 10 - -# === mDNS (.local) - Dynamic resolution === -Host *.local - User - ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p - -# === Short hostnames -> mDNS first, then Tailscale === -Host - User - ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p - -Host - User - ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p - -# === ProxyJump (hosts without Tailscale, via jump host) === -# Host -# HostName .local -# User -# ProxyJump diff --git a/docs/02-base-packages.md b/docs/02-base-packages.md index 0c94dbd..f0d3293 100644 --- a/docs/02-base-packages.md +++ b/docs/02-base-packages.md @@ -16,23 +16,25 @@ pkg install -y \ | Package | Why | |---------|-----| | `nodejs` | Claude Code runs on Node.js | -| `python` | `mdns-resolve`, `mdns-publish.py` | +| `python` | Handy in general; also required by the mDNS companion repo | | `git` | Cloning this repo, version control inside Claude | | `proot` | Provides `termux-chroot` used by `clauded` (fixes `/tmp`) | | `ripgrep` | Faster search inside Claude (the bundled one may not run on aarch64) | -## Recommended (SSH + mDNS) +## Recommended (SSH) ```bash pkg install -y openssh termux-api -pip install zeroconf ``` | Package | Why | |---------|-----| | `openssh` | SSH server (`sshd`) and client | | `termux-api` | Backs the `termux-*` Android-bridge commands | -| `zeroconf` (pip) | Required by `mdns-publish.py`; **does not survive a `tar` backup** of `$HOME`, reinstall after restore | + +For `.local` / Tailscale hostname resolution install the companion repo +[**mdns-termux**](https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux) +(it handles its own `pip install zeroconf`). ## Optional (file sharing) @@ -40,7 +42,7 @@ pip install zeroconf pkg install -y samba ``` -See `04-samba.md` for the rest of the setup (custom port 4450, share definitions). +See `03-samba.md` for the rest of the setup (custom port 4450, share definitions). ## Optional (image support in Claude) @@ -63,4 +65,9 @@ If any of these fail, re-run the relevant `pkg install`. ## What's next -`03-ssh-mdns-tailscale.md` — set up dynamic hostname resolution so you never hardcode IPs. +`03-samba.md` — expose Termux storage as an SMB share (optional). + +`04-claude-code.md` — Claude Code specifics on Termux. + +For SSH/mDNS host resolution follow the standalone repo +[**mdns-termux**](https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux). diff --git a/docs/04-samba.md b/docs/03-samba.md similarity index 89% rename from docs/04-samba.md rename to docs/03-samba.md index 4161e5f..e31decb 100644 --- a/docs/04-samba.md +++ b/docs/03-samba.md @@ -68,7 +68,7 @@ mount -t cifs ///Home /mnt -o port=4450,username=YOUR_USER ## Discovery via mDNS -If you also run `mdns-publish.py` (see `03-ssh-mdns-tailscale.md`), the device shows up as `movil.local` and SMB clients can use that name directly. +If you also install the companion repo [**mdns-termux**](https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux) and run its `mdns-publish.py`, the device shows up as `movil.local` and SMB clients can use that name directly. ## Don't bind to `0.0.0.0` if you don't need to diff --git a/docs/03-ssh-mdns-tailscale.md b/docs/03-ssh-mdns-tailscale.md deleted file mode 100644 index 784666b..0000000 --- a/docs/03-ssh-mdns-tailscale.md +++ /dev/null @@ -1,99 +0,0 @@ -# 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. diff --git a/docs/05-claude-code.md b/docs/04-claude-code.md similarity index 100% rename from docs/05-claude-code.md rename to docs/04-claude-code.md diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index b09611d..1a360c1 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -69,13 +69,10 @@ claude-update **Cause:** mDNS (.local) does NOT work natively in Termux (Android's Bionic libc doesn't support it). -**Fix:** Use the included `ssh-mdns-proxy` as ProxyCommand in `~/.ssh/config`: -``` -Host *.local - ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p -``` - -This uses pure Python multicast UDP to resolve mDNS, with Tailscale DNS fallback. +**Fix:** Install the companion repo +[**mdns-termux**](https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux) +which provides `ssh-mdns-proxy` (mDNS → Tailscale → cache) and the generic +`resolve` command usable in any tool (ping, curl, kubectl…). --- diff --git a/install.sh b/install.sh index d002f24..df914fd 100755 --- a/install.sh +++ b/install.sh @@ -36,7 +36,7 @@ if [[ -z "$PREFIX" || ! -d "$PREFIX" ]]; then exit 1 fi -TOTAL=9 +TOTAL=8 echo -e "${GREEN}=== Claude Code for Termux - Installer ===${NC}" echo "Version ceiling: v$CLAUDE_VERSION" $DRY_RUN && echo -e "${YELLOW}(dry-run mode - no changes will be made)${NC}" @@ -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 resolve; do +for script in clauded claude-check-env claude-update; do run cp "$SCRIPT_DIR/bin/$script" "$BIN_DIR/$script" run chmod +x "$BIN_DIR/$script" done @@ -91,14 +91,8 @@ run npm install -g @img/sharp-wasm32 sharp --force 2>/dev/null || { echo -e " ${YELLOW}Sharp install failed (optional, images won't work)${NC}" } -# 8. Install zeroconf for mDNS publisher (needed by mdns-publish.py) -step 8 "Installing zeroconf (pip) for mDNS publishing..." -run pip install --quiet zeroconf 2>/dev/null || { - echo -e " ${YELLOW}zeroconf install failed (optional, mdns-publish won't work)${NC}" -} - -# 9. Done -step 9 "Verifying installation..." +# 8. Done +step 8 "Verifying installation..." if ! $DRY_RUN; then echo "" "$BIN_DIR/claude-check-env" @@ -113,3 +107,6 @@ echo " 2. Authenticate: claude auth" echo " 3. Start Claude: clauded" echo "" echo "See docs/TROUBLESHOOTING.md for common issues." +echo "" +echo "For SSH/mDNS host resolution on Termux, see the companion repo:" +echo " https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux"