Files
local_machine/docs/apps/joplin/scripts/joplin-hotspot-alias.sh
ahauimix 0238aa7c02 Add Object B and self-growth canons with Cursor ledger rules; refresh connectivity and VPN runbooks.
Track day metrics/anamnesis HARD rules under .cursor/rules and personal docs so each screening updates days/{KIN}, dynamics, and big-data ledger.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 10:22:56 +08:00

213 lines
6.1 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# Стабильный URL для Joplin: http://<подсеть>.100:8080
# Primary IP *.100 через networksetup (Android видит primary, alias — часто нет).
#
# ./joplin-hotspot-alias.sh status
# ./joplin-hotspot-alias.sh ensure|repair
# ./joplin-hotspot-alias.sh dhcp # вернуть DHCP (перед домашним WiFi)
#
# Passwordless: ./joplin-hotspot-alias-install-sudo.sh
set -e
CONFIG="${JOPLIN_WEBDAV_CONFIG:-$HOME/.config/joplin-webdav/env}"
WIFI_DEV="${WIFI_DEVICE:-en0}"
WIFI_SERVICE="${WIFI_SERVICE:-Wi-Fi}"
PORT="${JOPLIN_WEBDAV_PORT:-8080}"
LOG="$HOME/Library/Logs/joplin-webdav/alias.log"
NS="/usr/sbin/networksetup"
ipv4_subnet() {
printf '%s\n' "$1" | awk -F. '{print $1"."$2"."$3}'
}
ipv4_last_octet() {
printf '%s\n' "$1" | awk -F. '{print $4}'
}
default_gateway() {
route -n get default 2>/dev/null | awk '/gateway:/{print $2; exit}'
}
load_config() {
if [ -f "$CONFIG" ]; then
# shellcheck disable=SC1090
. "$CONFIG"
fi
PORT="${JOPLIN_WEBDAV_PORT:-$PORT}"
}
joplin_phone_ip() {
load_config
gw="$(default_gateway 2>/dev/null || true)"
[ -n "$gw" ] || return 1
subnet="$(ipv4_subnet "$gw")"
if [ -n "${JOPLIN_STATIC_IP:-}" ]; then
last="$(ipv4_last_octet "$JOPLIN_STATIC_IP")"
else
last=100
fi
printf '%s\n' "${subnet}.${last}"
}
phone_url() {
ip="$(joplin_phone_ip 2>/dev/null || true)"
[ -n "$ip" ] || return 1
printf 'http://%s:%s\n' "$ip" "$PORT"
}
has_ip_on_dev() {
ip="$1"
dev="$2"
ifconfig "$dev" 2>/dev/null | grep -q "inet ${ip} "
}
# Убрать *.100 alias с других подсетей (после смены хотспота / WiFi)
prune_stale_aliases() {
target="$1"
dev="$2"
ifconfig "$dev" 2>/dev/null | awk '/inet / {print $2}' | while read -r ip; do
case "$ip" in
*.*.*.100)
[ "$ip" = "$target" ] && continue
ifconfig "$dev" -alias "$ip" 2>/dev/null || true
log_msg "removed stale alias $ip"
;;
esac
done
}
is_android_hotspot_gw() {
gw="$1"
case "$gw" in
10.*|172.20.10.*|192.168.43.*|192.168.44.*|192.168.49.*) return 0 ;;
*) return 1 ;;
esac
}
log_msg() {
mkdir -p "$(dirname "$LOG")"
printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$LOG"
}
write_phone_url_env() {
url="$(phone_url 2>/dev/null || true)"
[ -n "$url" ] && [ -f "$CONFIG" ] || return 0
if grep -q '^JOPLIN_PHONE_URL=' "$CONFIG" 2>/dev/null; then
sed -i '' "s|^JOPLIN_PHONE_URL=.*|JOPLIN_PHONE_URL=$url|" "$CONFIG" 2>/dev/null \
|| sed -i "s|^JOPLIN_PHONE_URL=.*|JOPLIN_PHONE_URL=$url|" "$CONFIG"
else
printf 'JOPLIN_PHONE_URL=%s\n' "$url" >>"$CONFIG"
fi
}
cmd_status() {
load_config
target="$(joplin_phone_ip 2>/dev/null || true)"
primary="$(ipconfig getifaddr "$WIFI_DEV" 2>/dev/null || true)"
gw="$(default_gateway 2>/dev/null || true)"
url="$(phone_url 2>/dev/null || true)"
echo "mode: static primary *.100 ($WIFI_SERVICE)"
echo "device: $WIFI_DEV"
echo "primary: ${primary:-(none)}"
echo "gateway: ${gw:-(none)}"
echo "joplin IP: ${target:-(no gateway)}"
if [ -n "$gw" ] && ! is_android_hotspot_gw "$gw"; then
echo "network: домашний/роутер WiFi (не Android-хотспот)"
echo " → телефон должен быть в той же WiFi сети, что Mac"
else
echo "network: похоже на Android-хотспот"
fi
if [ -n "$target" ]; then
ifconfig "$WIFI_DEV" 2>/dev/null | awk -v t="$target" '/inet / { if ($2 ~ /\.100$/ && $2 != t) print "stale ifconfig: " $2 " (repair)" }'
fi
if [ -n "$target" ] && [ "$primary" = "$target" ]; then
echo "static IP: OK (primary = $target)"
elif [ -n "$target" ]; then
echo "static IP: MISSING (primary $primary, нужен $target)"
echo " fix: $0 repair"
fi
if [ -n "$url" ]; then
echo ""
echo "URL на телефоне:"
echo " $url"
echo ""
echo "Проверка: браузер НА ТЕЛЕФОНЕ → URL → запрос логина за 23 сек"
echo "Если браузер висит — телефон не видит Mac (не Joplin)"
echo ""
echo "⚠ http://*.local:8080 с телефона НЕ работает (Android не резолвит mDNS клиентов хотспота)"
fi
}
cmd_ensure() {
force=0
[ "${1:-}" = --force ] && force=1
target="$(joplin_phone_ip 2>/dev/null || true)"
if [ -z "$target" ]; then
log_msg "skip: no gateway"
exit 0
fi
primary="$(ipconfig getifaddr "$WIFI_DEV" 2>/dev/null || true)"
if [ -z "$primary" ]; then
log_msg "skip: no primary IP"
exit 0
fi
if [ "$primary" = "$target" ] && [ "$force" -eq 0 ]; then
[ "$(id -u)" -ne 0 ] && write_phone_url_env
exit 0
fi
if [ "$(id -u)" -ne 0 ]; then
log_msg "setmanual $target (was $primary)"
sudo_arg=ensure
[ "$force" -eq 1 ] && sudo_arg="ensure --force"
# shellcheck disable=SC2086
if ! sudo -n "$0" $sudo_arg; then
echo "static IP FAILED: нужен sudo." >&2
echo " один раз: ./docs/apps/joplin/scripts/joplin-hotspot-alias-install-sudo.sh" >&2
echo " затем: $0 repair" >&2
exit 1
fi
write_phone_url_env
echo "static IP OK: $target"
echo "URL на телефоне: $(phone_url)"
exit 0
fi
gw="$(default_gateway 2>/dev/null || true)"
[ -n "$gw" ] || { log_msg "skip: no gateway"; exit 1; }
prune_stale_aliases "$target" "$WIFI_DEV"
log_msg "networksetup -setmanual $target gw $gw [root]"
"$NS" -setmanual "$WIFI_SERVICE" "$target" "255.255.255.255.0" "$gw"
}
cmd_dhcp() {
if [ "$(id -u)" -ne 0 ]; then
if ! sudo -n "$0" dhcp; then
echo "Нужен sudo: sudo $NS -setdhcp \"$WIFI_SERVICE\"" >&2
exit 1
fi
echo "WiFi → DHCP"
exit 0
fi
"$NS" -setdhcp "$WIFI_SERVICE"
log_msg "setdhcp $WIFI_SERVICE"
}
usage() {
echo "Usage: $(basename "$0") status|ensure|repair|dhcp" >&2
exit 1
}
case "${1:-ensure}" in
status) cmd_status ;;
ensure) cmd_ensure "${2:-}" ;;
repair) "$0" ensure --force ;;
dhcp) cmd_dhcp ;;
-h|--help) usage ;;
*) usage ;;
esac