init: extraer herramientas mDNS de claude-code-termux
Scripts (bin/): - mdns-resolve: resolver .local por multicast UDP (stdlib Python). - mdns-publish.py: publicar este dispositivo como movil.local (zeroconf). - ssh-mdns-proxy: ProxyCommand SSH con orden mDNS -> Tailscale -> cache. - ssh-fallback: ProxyCommand con fallback Tailscale -> LAN. - resolve: resolver genérico para cualquier comando (ping, curl, kubectl...). Config (config/): - bashrc.snippet: aliases r/pingr/curlr/sshr + auto-arranque publisher. - ssh_config.example: entradas ProxyCommand para .local y hostnames cortos. Docs y setup: - README.md standalone con guía de uso, comportamiento por red y gotchas (getent ausente en Bionic, shebang bash-only, mDNS no cruza redes, zeroconf no sobrevive tar-backups, no silenciar logs). - install.sh independiente con --dry-run. Origen: extraído de andresgarcia0313/claude-code-termux para reutilizarse sin acoplar a Claude Code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
0b6642ec84
|
|
@ -0,0 +1,7 @@
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.bak-*
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Andrés García
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
# mdns-termux
|
||||||
|
|
||||||
|
**mDNS + Tailscale hostname resolution for Termux (Android).**
|
||||||
|
|
||||||
|
Termux runs on Android's Bionic libc, which does **not** resolve `.local` names
|
||||||
|
and lacks `getent`. This repo ships small, dependency-light helpers that fix
|
||||||
|
that — without root, and without a custom system resolver.
|
||||||
|
|
||||||
|
Works on any network: LAN via multicast, off-LAN via Tailscale MagicDNS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What's in here
|
||||||
|
|
||||||
|
| Script | Role |
|
||||||
|
|--------|------|
|
||||||
|
| `mdns-resolve` | Resolve `<host>.local` via raw multicast UDP (pure Python, stdlib only). |
|
||||||
|
| `mdns-publish.py` | Publish this device on the LAN as `movil.local` using `zeroconf`. |
|
||||||
|
| `ssh-mdns-proxy` | SSH `ProxyCommand`: mDNS → Tailscale DNS → cached IP. |
|
||||||
|
| `ssh-fallback` | SSH `ProxyCommand`: Tailscale → LAN, for flaky Tailscale. |
|
||||||
|
| `resolve` | Generic resolver for **any** command (ping, curl, kubectl, nc…). Same order. |
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux.git
|
||||||
|
cd mdns-termux
|
||||||
|
bash install.sh # or --dry-run first
|
||||||
|
```
|
||||||
|
|
||||||
|
Then follow the printed reminders (append `bashrc.snippet`, set your
|
||||||
|
`TAILSCALE_DOMAIN`, optionally merge `ssh_config.example`).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Generic resolver — works in any command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
resolve lenovo # -> 100.69.236.16
|
||||||
|
ping $(resolve hp62a)
|
||||||
|
curl "http://$(resolve dell):8080"
|
||||||
|
kubectl --server="https://$(resolve master):6443" get nodes
|
||||||
|
```
|
||||||
|
|
||||||
|
Environment knobs:
|
||||||
|
|
||||||
|
- `TAILSCALE_DOMAIN` — defaults to `tailb0bb74.ts.net`. Set to your own tailnet.
|
||||||
|
- `MDNS_TIMEOUT` — seconds waiting for multicast (default `1`, keeps off-LAN snappy).
|
||||||
|
|
||||||
|
### Shell helpers (from `config/bashrc.snippet`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
alias r='resolve'
|
||||||
|
pingr <host> # ping -c 3 to resolved IP
|
||||||
|
curlr <host> [flags] # curl http://<resolved-ip>
|
||||||
|
sshr <host> [cmd...] # ssh via resolved IP with correct HostKeyAlias
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSH by hostname (via `ProxyCommand`)
|
||||||
|
|
||||||
|
Drop `config/ssh_config.example` into `~/.ssh/config`. Then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh lenovo # resolves via mDNS then Tailscale, connects
|
||||||
|
ssh lenovo.local # forces mDNS-first path
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publishing this device as `movil.local`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python ~/.local/bin/mdns-publish.py # foreground
|
||||||
|
nohup python ~/.local/bin/mdns-publish.py >>~/.cache/mdns.log 2>&1 & disown
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behavior by network
|
||||||
|
|
||||||
|
| Scenario | Path | Typical latency |
|
||||||
|
|--------------------|--------------------------------------------|-----------------|
|
||||||
|
| On home LAN | mDNS multicast (1 s cap) → else Tailscale | < 100 ms |
|
||||||
|
| Mobile data / away | mDNS fails → Tailscale MagicDNS | < 200 ms |
|
||||||
|
| No Tailscale | Both fail → cached last-known IP | instant |
|
||||||
|
|
||||||
|
## Gotchas found the hard way
|
||||||
|
|
||||||
|
- **`getent` doesn't exist on Termux.** Scripts that shell out to it silently
|
||||||
|
fail. This repo uses `python -c "import socket; socket.gethostbyname(...)"`
|
||||||
|
as the portable fallback on Bionic.
|
||||||
|
- **Shebang matters.** `#!/data/data/com.termux/files/usr/bin/bash` is required
|
||||||
|
for `declare -A`. A stray backslash (`#\!`) makes the kernel fall through to
|
||||||
|
`sh`, which 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, only Tailscale resolves.
|
||||||
|
- **`zeroconf` is not `pkg`-managed.** After restoring Termux from a `tar`
|
||||||
|
backup, re-run `pip install zeroconf` or `mdns-publish.py` will silently die
|
||||||
|
with `ModuleNotFoundError`.
|
||||||
|
- **Silent failures are the enemy.** The `.bashrc` snippet deliberately logs
|
||||||
|
to `~/.cache/mdns.log` instead of `/dev/null`. Keep it that way.
|
||||||
|
|
||||||
|
## Tailscale on Termux
|
||||||
|
|
||||||
|
Tailscale runs as the **Android app**, not as a Termux CLI — `tailscale up`
|
||||||
|
inside a shell won't work. The app provides MagicDNS for
|
||||||
|
`*.<your-tailnet>.ts.net`, which Bionic-libc resolves fine (it's just a
|
||||||
|
regular DNS lookup).
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [claude-code-termux](https://devops.ingeniumcodex.com/andresgarcia0313/claude-code-termux)
|
||||||
|
— Claude Code CLI setup for Termux. Uses this repo for SSH/host resolution.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT — see `LICENSE`.
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
#!/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()
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
#!/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()
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/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"
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/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"
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# === mdns-termux - .bashrc additions ===
|
||||||
|
# Append these lines to your ~/.bashrc
|
||||||
|
|
||||||
|
# Ensure ~/.local/bin is on PATH
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|
||||||
|
# Your Tailscale tailnet (change to yours). Used as fallback for resolve/sshr.
|
||||||
|
export TAILSCALE_DOMAIN="tailXXXXXX.ts.net"
|
||||||
|
|
||||||
|
# --- mDNS publisher (this device as movil.local on the LAN) ---
|
||||||
|
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"
|
||||||
|
# Add your own short-name -> canonical mappings here:
|
||||||
|
case "$h" in dell) canonical="dell-latitude3400";; lenovo) canonical="lenovo-ideapad";; esac
|
||||||
|
ssh -o "HostKeyAlias=${canonical}.${TAILSCALE_DOMAIN}" \
|
||||||
|
"YOUR_USER@$(resolve "$h")" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Auto-start publisher on shell login (optional) ---
|
||||||
|
# Uncomment if you want movil.local advertised whenever you open a Termux shell.
|
||||||
|
# Failures land in ~/.cache/mdns.log — do NOT redirect to /dev/null.
|
||||||
|
#
|
||||||
|
# mkdir -p ~/.cache
|
||||||
|
# pgrep -f "mdns-publish" >/dev/null 2>&1 || \
|
||||||
|
# nohup python ~/.local/bin/mdns-publish.py >>~/.cache/mdns.log 2>&1 & disown
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
# 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>
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/data/data/com.termux/files/usr/bin/bash
|
||||||
|
# install.sh - Install mdns-termux scripts to ~/.local/bin
|
||||||
|
# Usage: bash install.sh [--dry-run]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DRY_RUN=0
|
||||||
|
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=1
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
BIN_DIR="$HOME/.local/bin"
|
||||||
|
|
||||||
|
run() {
|
||||||
|
if [[ $DRY_RUN -eq 1 ]]; then
|
||||||
|
echo "[dry-run] $*"
|
||||||
|
else
|
||||||
|
eval "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
step() { echo ""; echo ">>> Step $1: $2"; }
|
||||||
|
|
||||||
|
# 1. Verify Termux
|
||||||
|
step 1 "Verifying Termux environment..."
|
||||||
|
if [[ -z "${PREFIX:-}" || "$PREFIX" != *"com.termux"* ]]; then
|
||||||
|
echo "ERROR: This installer targets Termux. \$PREFIX not set as expected." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " Termux detected: $PREFIX"
|
||||||
|
|
||||||
|
# 2. Base packages
|
||||||
|
step 2 "Installing required packages (pkg + pip)..."
|
||||||
|
run "pkg install -y python proot"
|
||||||
|
run "pip install --upgrade zeroconf"
|
||||||
|
|
||||||
|
# 3. Copy scripts
|
||||||
|
step 3 "Installing scripts to $BIN_DIR..."
|
||||||
|
run "mkdir -p '$BIN_DIR'"
|
||||||
|
for script in mdns-resolve mdns-publish.py ssh-mdns-proxy ssh-fallback resolve; do
|
||||||
|
run "cp '$SCRIPT_DIR/bin/$script' '$BIN_DIR/$script'"
|
||||||
|
run "chmod +x '$BIN_DIR/$script'"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 4. Reminders
|
||||||
|
step 4 "Manual steps remaining:"
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
a) Append aliases to ~/.bashrc:
|
||||||
|
cat $SCRIPT_DIR/config/bashrc.snippet >> ~/.bashrc
|
||||||
|
Then edit TAILSCALE_DOMAIN inside your ~/.bashrc.
|
||||||
|
|
||||||
|
b) (Optional) Merge SSH config for .local + short-name resolution:
|
||||||
|
cat $SCRIPT_DIR/config/ssh_config.example >> ~/.ssh/config
|
||||||
|
Adjust User and hostnames.
|
||||||
|
|
||||||
|
c) Test:
|
||||||
|
resolve <hostname>
|
||||||
|
pingr <hostname>
|
||||||
|
|
||||||
|
EOF
|
||||||
|
echo "Done."
|
||||||
Loading…
Reference in New Issue