refactor: extraer mDNS/SSH host-resolution al repo mdns-termux

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) <noreply@anthropic.com>
This commit is contained in:
Andrés Eduardo García Márquez 2026-07-19 20:56:51 -05:00
parent 5c4e41f6b0
commit c6d54ea5c9
14 changed files with 44 additions and 491 deletions

View File

@ -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 - **v2.1.112** (last pure Node.js release) with auto-update protection
- **termux-chroot wrapper** that fixes the `/tmp` permission issue - **termux-chroot wrapper** that fixes the `/tmp` permission issue
- **mDNS resolver** for SSH without hardcoded IPs
- **Safe updater** that respects the version ceiling - **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 ## Requirements
- [Termux](https://f-droid.org/en/packages/com.termux/) (F-Droid recommended) - [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 | | `clauded` | `~/.local/bin/` | Wrapper with termux-chroot for /tmp |
| `claude-check-env` | `~/.local/bin/` | Environment validator | | `claude-check-env` | `~/.local/bin/` | Environment validator |
| `claude-update` | `~/.local/bin/` | Safe updater (ceiling-aware) | | `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 | | `settings.json` | `~/.claude/` | Disables auto-updater |
## Usage ## Usage
@ -59,14 +57,11 @@ claude-check-env
claude-update 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. Moved out to a dedicated project so it can be reused without Claude Code:
[**mdns-termux**](https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux)
```bash — mDNS + Tailscale resolver, `resolve` CLI, SSH `ProxyCommand`.
# Set your Tailscale domain (optional, has a default)
export TAILSCALE_DOMAIN="your-tailnet.ts.net"
```
## Version Ceiling ## 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) | | 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 | | 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 | | 03 | [docs/03-samba.md](docs/03-samba.md) | Samba server on port 4450 + `msg.lock` gotcha |
| 04 | [docs/04-samba.md](docs/04-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) |
| 05 | [docs/05-claude-code.md](docs/05-claude-code.md) | Claude Code internals (version ceiling, `clauded`, settings) |
| — | [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | Common errors and fixes | | — | [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 ## Troubleshooting

View File

@ -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()

View File

@ -1,103 +0,0 @@
#!/usr/bin/env python3
"""
Resolve hostname.local via mDNS multicast (pure Python, no dependencies).
Usage: mdns-resolve <hostname.local> [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 <hostname.local> [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()

View File

@ -1,47 +0,0 @@
#!/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"

View File

@ -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> <lan_ip> <port>
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"

View File

@ -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 <hostname> [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"

View File

@ -14,8 +14,8 @@ export PATH="$HOME/.local/bin:$PATH"
# Prevent Claude Code auto-updater (enforced at shell level too) # Prevent Claude Code auto-updater (enforced at shell level too)
export DISABLE_AUTOUPDATER=1 export DISABLE_AUTOUPDATER=1
# === Optional: helper aliases for samba + mDNS services === # === Optional: Samba service helpers ===
# Only useful if you set up smbd and mdns-publish (see docs/04-samba.md, docs/03-ssh-mdns-tailscale.md). # 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-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"' 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-status='pgrep -a smbd && echo "Port: 4450" || echo "Samba is not running"'
alias smb-log='tail -50 ~/.config/samba/samba.log' alias smb-log='tail -50 ~/.config/samba/samba.log'
alias mdns-status='pgrep -af mdns-publish || echo "mDNS publisher is not running"' # === Optional: auto-start Samba on shell login ===
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"' # Uncomment if you want smbd up automatically.
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. # 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 -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

View File

@ -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 <your-user>
ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p
# === Short hostnames -> mDNS first, then Tailscale ===
Host <host-1>
User <your-user>
ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p
Host <host-2>
User <your-user>
ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p
# === ProxyJump (hosts without Tailscale, via jump host) ===
# Host <lan-only-host>
# HostName <lan-only-host>.local
# User <user>
# ProxyJump <jump-host>

View File

@ -16,23 +16,25 @@ pkg install -y \
| Package | Why | | Package | Why |
|---------|-----| |---------|-----|
| `nodejs` | Claude Code runs on Node.js | | `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 | | `git` | Cloning this repo, version control inside Claude |
| `proot` | Provides `termux-chroot` used by `clauded` (fixes `/tmp`) | | `proot` | Provides `termux-chroot` used by `clauded` (fixes `/tmp`) |
| `ripgrep` | Faster search inside Claude (the bundled one may not run on aarch64) | | `ripgrep` | Faster search inside Claude (the bundled one may not run on aarch64) |
## Recommended (SSH + mDNS) ## Recommended (SSH)
```bash ```bash
pkg install -y openssh termux-api pkg install -y openssh termux-api
pip install zeroconf
``` ```
| Package | Why | | Package | Why |
|---------|-----| |---------|-----|
| `openssh` | SSH server (`sshd`) and client | | `openssh` | SSH server (`sshd`) and client |
| `termux-api` | Backs the `termux-*` Android-bridge commands | | `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) ## Optional (file sharing)
@ -40,7 +42,7 @@ pip install zeroconf
pkg install -y samba 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) ## Optional (image support in Claude)
@ -63,4 +65,9 @@ If any of these fail, re-run the relevant `pkg install`.
## What's next ## 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).

View File

@ -68,7 +68,7 @@ mount -t cifs //<device-ip>/Home /mnt -o port=4450,username=YOUR_USER
## Discovery via mDNS ## 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 ## Don't bind to `0.0.0.0` if you don't need to

View File

@ -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 `<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.

View File

@ -69,13 +69,10 @@ claude-update
**Cause:** mDNS (.local) does NOT work natively in Termux (Android's Bionic libc doesn't support it). **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`: **Fix:** Install the companion repo
``` [**mdns-termux**](https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux)
Host *.local which provides `ssh-mdns-proxy` (mDNS → Tailscale → cache) and the generic
ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p `resolve` command usable in any tool (ping, curl, kubectl…).
```
This uses pure Python multicast UDP to resolve mDNS, with Tailscale DNS fallback.
--- ---

View File

@ -36,7 +36,7 @@ if [[ -z "$PREFIX" || ! -d "$PREFIX" ]]; then
exit 1 exit 1
fi fi
TOTAL=9 TOTAL=8
echo -e "${GREEN}=== Claude Code for Termux - Installer ===${NC}" echo -e "${GREEN}=== Claude Code for Termux - Installer ===${NC}"
echo "Version ceiling: v$CLAUDE_VERSION" echo "Version ceiling: v$CLAUDE_VERSION"
$DRY_RUN && echo -e "${YELLOW}(dry-run mode - no changes will be made)${NC}" $DRY_RUN && echo -e "${YELLOW}(dry-run mode - no changes will be made)${NC}"
@ -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 resolve; do for script in clauded claude-check-env claude-update; 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
@ -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}" echo -e " ${YELLOW}Sharp install failed (optional, images won't work)${NC}"
} }
# 8. Install zeroconf for mDNS publisher (needed by mdns-publish.py) # 8. Done
step 8 "Installing zeroconf (pip) for mDNS publishing..." step 8 "Verifying installation..."
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..."
if ! $DRY_RUN; then if ! $DRY_RUN; then
echo "" echo ""
"$BIN_DIR/claude-check-env" "$BIN_DIR/claude-check-env"
@ -113,3 +107,6 @@ echo " 2. Authenticate: claude auth"
echo " 3. Start Claude: clauded" echo " 3. Start Claude: clauded"
echo "" echo ""
echo "See docs/TROUBLESHOOTING.md for common issues." 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"