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>
213 lines
6.1 KiB
Bash
Executable File
213 lines
6.1 KiB
Bash
Executable File
#!/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 (перед домашним Wi‑Fi)
|
||
#
|
||
# 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 с других подсетей (после смены хотспота / Wi‑Fi)
|
||
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: домашний/роутер Wi‑Fi (не Android-хотспот)"
|
||
echo " → телефон должен быть в той же Wi‑Fi сети, что 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 → запрос логина за 2–3 сек"
|
||
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 "Wi‑Fi → 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
|