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:
parent
5c4e41f6b0
commit
c6d54ea5c9
27
README.md
27
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
103
bin/mdns-resolve
103
bin/mdns-resolve
|
|
@ -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()
|
||||
47
bin/resolve
47
bin/resolve
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ mount -t cifs //<device-ip>/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
|
||||
|
||||
|
|
@ -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.
|
||||
|
|
@ -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…).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
17
install.sh
17
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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue