Add Joplin, old Mac, and Fedora WiFi runbooks; simplify JA3 Whisky scripts and hotspot recovery.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ahauimix
2026-06-19 22:26:33 +03:00
parent 1938b7c743
commit 4bc9629a09
381 changed files with 3442 additions and 773 deletions

View File

@@ -1,111 +1,110 @@
# Emergency: сеть после смены хотспота (macOS)
Симптом: **отключили Mac от WiFi / переподключились к другому хотспоту — «интернета нет», помогает только перезагрузка.**
Документ описывает **проверенную на машине причину** в вашей конфигурации, **ручное восстановление без перезагрузки** и **скрипт** из репозитория.
# Emergency: сеть после переподключения хотспота (macOS)
---
## Причина (точная по конфигурации)
## Одна команда (из любой папки)
На macOS в **Системных настройках → Сеть → WiFi → Подробнее → Прокси** часто включён **автоматический прокси (PAC)** с URL вида **`http://localhost:1089/proxy.pac`** (см. [PROXY_GUIDE.md](./PROXY_GUIDE.md) → раздел про системный PAC).
```bash
bash /Users/eternal/code/local_machine/docs/connectivity/scripts/macos-hotspot-network-recover.sh recover
```
Проверка на рабочей машине (2026): **PAC для сервиса WiFi включён (Enabled: Yes)**, при этом **на порту 1089 нет процесса**, который отдаёт `proxy.pac` — соединение к `127.0.0.1:1089` отклоняется.
Запросит **пароль sudo**, затем: PAC/SOCKS off → **DHCP**`ifconfig down`**`route -n flush`** → DNS → DHCP → **Joplin alias `*.100`**.
В таком состоянии:
Диагностика:
1. Система и часть приложений **сначала** должны получить PAC с localhost; при недоступном URL поведение **нестабильно** (часть трафика «в никуда», таймауты).
2. При **смене хотспота** меняются шлюз, DNS, часто появляется **captive portal**; страница входа может **не открыться**, пока PAC мешает или DNS закеширован со старой сети.
3. **Перезагрузка** «лечит», потому что заново поднимаются **configd**, **mDNSResponder**, порядок инициализации WiFi и иногда снова стартует локальный PACсервер (Clash / Surge / свой сервис на 1089).
Дополнительные усилители сбоя (проверять при `status`):
- **Системный SOCKS** на `127.0.0.1:1080` (Shadowsocks и т.д.) — если включён, а клиент не слушает порт, трафик через системный прокси тоже падает.
- **VPN / туннели** с политикой «весь трафик» — после смены сети маршруты могут остаться битые до отключения VPN.
**SOCKS Telegram (1081) и SSHтуннели сами по себе не отключают WiFi** — они приложенческие. Проблема класса «всё мёртво до ребута» у вас с высокой вероятностью — **системный PAC/SOCKS на localhost без живого сервиса + кэш DNS / состояние WiFi**.
```bash
bash /Users/eternal/code/local_machine/docs/connectivity/scripts/macos-hotspot-network-recover.sh status
```
---
## Быстро: один скрипт (рекомендуется)
## RCA (почему старый `recover` не помогал)
Из корня репозитория `local_machine`:
### 1. Ядерный сброс не выполнялся
```bash
chmod +x ./docs/connectivity/scripts/macos-hotspot-network-recover.sh
./docs/connectivity/scripts/macos-hotspot-network-recover.sh status
./docs/connectivity/scripts/macos-hotspot-network-recover.sh recover
Скрипт проверял `sudo -n` (passwordless). Без NOPASSWD в sudoers он **пропускал** `route flush` и делал только toggle WiFi — **недостаточно** при ARP `REJECT`.
Ручная «ядерная кнопка» работала, потому что вы вводили пароль и реально сбрасывали маршруты.
**Исправлено:** `recover` всегда вызывает `sudo` с запросом пароля.
### 2. Manual IPv4 на WiFi (Joplin) ломал маршрут
`/var/log/wifi.log` при reconnect:
```
IP Address: 10.219.227.100
Default Gateway IP Address: 10.219.227.39
No Router MAC Address
ROUTER_MAC_ADDR = 0x000000000000
```
`recover` делает: **выключает PAC** (те же сервисы, что и `macos-system-pac.sh`), **выключает системный SOCKS на WiFi**, по возможности **сбрасывает DNS** и **кратко перезапускает WiFi** и DHCP.
В prefs оставался шлюз `10.219.227.39`, хотспот уже в `10.36.136.x``ping``Host is down`, `curl` мёртв.
Если скрипт пишет, что нет `sudo` без пароля — выполните вручную команды из раздела «Вручную» ниже.
**Исправлено:** WiFi **только DHCP**; Joplin — **alias** `<подсеть>.100` на `en0` (не трогает default route). См. [apps/joplin/STABLE_SETUP.md](./apps/joplin/STABLE_SETUP.md).
После появления интернета, если вам снова нужен PAC:
### 3. PAC / SOCKS (вторично)
```bash
./docs/connectivity/scripts/macos-pac-on.sh
```
Только **после** того как процесс на **1089** реально запущен и отдаёт `proxy.pac`.
PAC на `:1089` без сервера усиливает сбои, но **не объясняет** `ping 8.8.8.8 → No route to host`.
---
## Вручную (без скрипта)
## Что делает `recover` сейчас
### 1. Отключить автоматический PAC
| Шаг | Действие |
|-----|----------|
| 1 | PAC off |
| 2 | SOCKS off |
| 3 | `networksetup -setdhcp Wi-Fi` (снимает Manual) |
| 4 | `sudo ifconfig en0 down` |
| 5 | `sudo route -n flush` |
| 6 | DNS flush + `ifconfig up` + `ipconfig DHCP` |
| 7 | Ждёт gateway (до 30 с) |
| 8 | `ifconfig en0 alias <subnet>.100` для Joplin |
**GUI:** Системные настройки → Сеть → WiFi → Подробнее → Прокси → снять **«Автоматическая настройка прокси (PAC)»**.
---
**CLI** (из репо):
```bash
./docs/connectivity/scripts/macos-pac-off.sh
./docs/connectivity/scripts/macos-system-pac.sh status
```
### 2. Отключить системный SOCKS (если включали для Shadowsocks)
```bash
networksetup -setsocksfirewallproxystate "Wi-Fi" off
networksetup -getsocksfirewallproxy "Wi-Fi"
```
### 3. Сброс DNS
## Ядерная кнопка (если скрипт недоступен)
```bash
sudo networksetup -setdhcp "Wi-Fi"
sudo ifconfig en0 down
sudo route -n flush
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
```
### 4. Обновить DHCP и «пнуть» WiFi
Узнать интерфейс WiFi (часто `en0`):
```bash
networksetup -listallhardwareports | sed -n '/Hardware Port: Wi-Fi/,/Ethernet Address/p'
```
Дальше (подставьте свой `Device`, например `en0`):
```bash
sudo ifconfig en0 up
sudo ipconfig set en0 DHCP
networksetup -setairportpower en0 off
sleep 2
networksetup -setairportpower en0 on
bash /Users/eternal/code/local_machine/docs/connectivity/scripts/macos-hotspot-network-recover.sh recover
```
### 5. Captive portal
Откройте в браузере **`http://captive.apple.com`** или **`http://neverssl.com`** — должна открыться страница входа в сеть отели/кафе.
---
## Профилактика (архитектурно)
## Диагностика
1. **Не держать PAC включённым**, если сервис на **1089** не запущен: сначала поднять Clash/Surge/свой HTTPсервер с PAC, **затем** `macos-pac-on.sh`.
2. Перед подключением к **чужому хотспоту** с обязательной страницей входа — **временно PAC off** (`macos-pac-off.sh`), залогиниться в сеть, затем при необходимости снова **on**.
3. Разнести роли: **глобальный PAC** — только когда стабильно есть локальный конфиг‑сервер; для «чистого» интернета в дороге — **PAC выключен**, прокси только в приложениях (Telegram SOCKS **1081**, Cursor **10809** и т.д.) — см. [TELEGRAM_GUIDE.md](./TELEGRAM_GUIDE.md), [PROXY_EMERGENCY.md](./PROXY_EMERGENCY.md).
```bash
networksetup -getinfo "Wi-Fi"
ifconfig en0 | grep inet
route -n get default
bash /Users/eternal/code/local_machine/docs/connectivity/scripts/macos-hotspot-network-recover.sh status
./docs/apps/joplin/scripts/joplin-webdav-status.sh
grep -E 'Router MAC|No Router' /var/log/wifi.log | tail -5
```
| Признак | Причина |
|---------|---------|
| Manual + другая подсеть в gateway | Старый Joplin static |
| `No Router MAC` в wifi.log | ARP на шлюз не прошёл |
| `REJECT` в route get gateway | Нужен route flush |
| Primary DHCP OK, нет `*.100` alias | Запустить `recover` |
---
## Профилактика
1. **Не** использовать Manual WiFi для Joplin — только DHCP + alias.
2. После **каждого** reconnect: одна команда `recover` выше.
3. PAC on — только если на **1089** слушает сервер.
4. `exit-via-ru-do.sh`**после** рабочего `curl https://1.1.1.1`.
---
@@ -113,10 +112,6 @@ networksetup -setairportpower en0 on
| Документ | Зачем |
|----------|--------|
| [PROXY_GUIDE.md](./PROXY_GUIDE.md) | Системный PAC, порты, скрипты `macos-pac-*.sh` |
| [PROXY_EMERGENCY.md](./PROXY_EMERGENCY.md) | SSH, SOCKS 1081, прод |
| [README.md](./README.md) | Оглавление `connectivity/` |
---
*Если после `recover` сеть всё ещё мёртвая — проверьте аппаратный тумблер WiFi, другое SSID, и логи: Консоль → `Wi-Fi` / `configd`.*
| [apps/joplin/STABLE_SETUP.md](./apps/joplin/STABLE_SETUP.md) | DHCP + alias |
| [PROXY_EMERGENCY.md](./PROXY_EMERGENCY.md) | SSH / SOCKS |
| [README.md](./README.md) | Оглавление |

View File

@@ -2,7 +2,7 @@
Одна страница на случай, когда «всё слетело». Полная схема, безопасность и нюансы `~/.ssh/config`: [PROXY_GUIDE.md](./PROXY_GUIDE.md).
**Сеть / хотспот (интернет есть только после перезагрузки):** чаще всего системный **PAC на localhost:1089** без живого сервера или системный SOCKS на **1080**. Пошагово: **[HOTSPOT_NETWORK_EMERGENCY.md](./HOTSPOT_NETWORK_EMERGENCY.md)**.
**Сеть / хотспот (интернет есть только после перезагрузки):** чаще всего **Manual IPv4 на WiFi (Joplin)** при смене подсети Android + битые маршруты; также PAC **1089** / SOCKS **1080** без клиента. Пошагово: **[HOTSPOT_NETWORK_EMERGENCY.md](./HOTSPOT_NETWORK_EMERGENCY.md)**.
---

View File

@@ -14,7 +14,9 @@
| **[BUILD_AAB_FROM_RUSSIA_GUIDE.md](./BUILD_AAB_FROM_RUSSIA_GUIDE.md)** | Сборка AAB/APK из России: Maven proxy на проде + SSH-туннель |
| **[WINDOWS_CURSOR_TUNNEL.md](./WINDOWS_CURSOR_TUNNEL.md)** | Cursor из РФ на **Windows**: туннель через прокси, скрипт .ps1, автозапуск |
| **[WINDOWS_SSH_KEY_INSTALL.md](./WINDOWS_SSH_KEY_INSTALL.md)** | Установка SSH-ключа на Windows (один файл `setup-ssh-key-for-proxy.ps1`, только для туннеля) |
| **[HOTSPOT_NETWORK_EMERGENCY.md](./HOTSPOT_NETWORK_EMERGENCY.md)** | Сеть после смены хотспота: причина (PAC localhost), ручной сброс без ребута, `macos-hotspot-network-recover.sh` |
| **[HOTSPOT_NETWORK_EMERGENCY.md](./HOTSPOT_NETWORK_EMERGENCY.md)** | Сеть после reconnect хотспота: Joplin Manual IP + смена подсети Android, route flush, PAC; `macos-hotspot-network-recover.sh` |
| **[apps/joplin/STABLE_SETUP.md](./apps/joplin/STABLE_SETUP.md)** | Joplin: стабильный URL на телефоне (статический IP Mac на Android-хотспоте) |
| **[apps/joplin/RUNBOOK.md](./apps/joplin/RUNBOOK.md)** | Joplin Mac ↔ телефон: диагностика, ETIMEDOUT/401, логи |
| **[scripts/](./scripts/)** | Скрипты для туннелей и локальной работы |
| **[launchd/](./launchd/)** | LaunchAgent для macOS: автозапуск SOCKS-туннеля при входе |
@@ -28,6 +30,7 @@
- **Загрузка файлов на прод через прокси** — [PROXY_GUIDE.md → Загрузка файлов на прод (scp)](./PROXY_GUIDE.md#-загрузка-файлов-на-прод-через-туннель-scp)
- **Безопасность прокси (nginx, fail2ban)** — [PROXY_GUIDE.md → Безопасность](./PROXY_GUIDE.md#-безопасность), SECURITY.md (если есть в папке)
- **Сборка AAB из РФ** — [BUILD_AAB_FROM_RUSSIA_GUIDE.md](./BUILD_AAB_FROM_RUSSIA_GUIDE.md)
- **Joplin: синк Mac ↔ телефон** — настройка [apps/joplin/STABLE_SETUP.md](./apps/joplin/STABLE_SETUP.md), поломки [apps/joplin/RUNBOOK.md](./apps/joplin/RUNBOOK.md), `./docs/apps/joplin/scripts/joplin-webdav-status.sh`
---
@@ -36,7 +39,7 @@
| Скрипт | Описание |
|--------|----------|
| **telegram-socks-tunnel.sh** | SOCKS5 на 1081 для Telegram. `TELEGRAM_SOCKS_VIA_DO=1` — только Mac→RU→DO (`telegram-socks-via-do`); без RU — ошибка. См. PROXY_GUIDE. |
| **macos-hotspot-network-recover.sh** | После смены хотспота: `status` / `recover` (PAC off, SOCKS off на WiFi, DNS, DHCP, перезапуск радио). См. HOTSPOT_NETWORK_EMERGENCY.md. |
| **macos-hotspot-network-recover.sh** | Reconnect хотспота: `bash …/macos-hotspot-network-recover.sh recover` — DHCP, route flush (sudo), Joplin alias `*.100`. См. HOTSPOT_NETWORK_EMERGENCY.md. |
| **ssh-hunab-prod.sh** | Опционально: сначала `hunab-prod-direct`, иначе `hunab-prod` (в конфиге по умолчанию уже через RU). См. PROXY_GUIDE. |
---

View File

@@ -0,0 +1,23 @@
# Joplin (локальный синк Mac ↔ телефон)
WebDAV через rclone на Mac, Android-хотспот.
| Документ | Назначение |
|----------|------------|
| **[STABLE_SETUP.md](./STABLE_SETUP.md)** | Рабочая схема: alias `*.100` |
| **[RUNBOOK.md](./RUNBOOK.md)** | Синк сломался |
| [docs/apps/joplin/scripts/](../../../apps/joplin/scripts/) | Скрипты |
## URL (Android-хотспот)
| | |
|---|---|
| Mac | `http://127.0.0.1:8080` |
| Телефон | `http://<подсеть>.100:8080` (см. `joplin-hotspot-alias.sh status`) |
**Не** `http://*.local:8080`с телефона-хотспота не работает.
```bash
./docs/apps/joplin/scripts/joplin-webdav-status.sh
./docs/apps/joplin/scripts/joplin-hotspot-alias.sh status
```

View File

@@ -0,0 +1,263 @@
# Runbook: Joplin — синк Mac ↔ телефон (WebDAV / Android-хотспот)
Симптом: **синхронизация перестала работать** между Joplin на Mac и на телефоне.
Стек: **rclone serve webdav** на Mac → `~/JoplinSync` → Joplin (Mac + Android) по HTTP. Облако Joplin не используется.
**Рабочая схема (2026-06, проверено):** Mac на **DHCP**, alias **`*.100`** на `en0`, телефон → `http://<подсеть>.100:8080`. Подробно: [STABLE_SETUP.md](./STABLE_SETUP.md).
---
## Архитектура
```
Телефон (Joplin) ──HTTP──► Mac en0 alias *.100:8080 ──► rclone ──► ~/JoplinSync/
Mac (Joplin) ──HTTP──► 127.0.0.1:8080 (тот же rclone)
```
| Компонент | Путь / имя |
|-----------|------------|
| Данные | `~/JoplinSync` |
| Учётка | `~/.config/joplin-webdav/env` (`600`, не в git) |
| WebDAV | LaunchAgent `com.eternal.joplin-webdav` |
| Alias `*.100` | LaunchAgent `com.eternal.joplin-hotspot-alias` (опционально + sudoers) |
| Лог rclone | `~/Library/Logs/joplin-webdav/rclone.log` |
| Лог alias | `~/Library/Logs/joplin-webdav/alias.log` |
| Лог Joplin Mac | `~/.config/joplin-desktop/log.txt` |
| Настройки Mac | `~/.config/joplin-desktop/settings.json``sync.6.path` |
Скрипты: `docs/apps/joplin/scripts/`.
---
## Шаг 0: диагностика
```bash
./docs/apps/joplin/scripts/joplin-webdav-status.sh
./docs/apps/joplin/scripts/joplin-hotspot-alias.sh status
```
| Что смотреть | OK | Проблема |
|--------------|-----|----------|
| rclone / `:8080` | процесс слушает | `joplin-webdav-serve.sh` или `joplin-webdav-install.sh` |
| `alias: OK` | `*.100` на en0 | `joplin-hotspot-alias.sh ensure` (sudo) |
| rclone.log | `from 10.` или `from 192.` | только `127.0.0.1` — телефон не достучался |
| Mac URL | `127.0.0.1:8080` | настроить WebDAV на Mac |
---
## Правильные URL
### Android раздаёт хотспот, Mac — клиент WiFi (основной сценарий)
| Устройство | WebDAV URL |
|------------|------------|
| **Mac** | `http://127.0.0.1:8080` |
| **Телефон** | `http://<подсеть>.100:8080` — сейчас часто `http://10.74.21.100:8080` |
Узнать актуальный URL:
```bash
./docs/apps/joplin/scripts/joplin-hotspot-alias.sh status
# или
grep JOPLIN_PHONE_URL ~/.config/joplin-webdav/env
```
**Не использовать на телефоне:**
| URL | Почему |
|-----|--------|
| `http://eternals.local:8080` | Android-хотспот **не резолвит** `.local` клиентов; в rclone.log нет запросов с телефона |
| `http://10.219.227.100:8080` | старая подсеть, если хотспот уже выдал `10.74.x` |
| `http://10.x.x.84:8080` (primary DHCP) | IP меняется при reconnect |
### Общее для Joplin
- Логин: `joplin`
- Пароль: `JOPLIN_WEBDAV_PASS` из env
- **Без** `/` в конце URL
- **Без пробела** в конце
- `http://`, не `https://`
- **Настройки → Синхронизация → Self-hosting → WebDAV** (не «Прокси»)
После смены URL на Mac: **Quit Joplin** → открыть снова → **Синхронизировать**.
---
## Симптом → причина → действие
### 1. «Синк OK, заметок нет на Mac»
Mac Joplin не на WebDAV — в `~/JoplinSync` только данные с телефона.
→ URL `http://127.0.0.1:8080`, проверить конфигурацию, синк.
---
### 2. `ETIMEDOUT` / `connect ETIMEDOUT 10.x.x.x:8080`
**Причина:** на телефоне **устаревший IP** (другая подсеть или нет alias `.100`).
**Признаки:** Joplin `ETIMEDOUT`; rclone — нет свежих `GET`/`PUT` с `10.`.
**Действие:**
```bash
./docs/apps/joplin/scripts/joplin-hotspot-alias.sh ensure # sudo
./docs/apps/joplin/scripts/joplin-hotspot-alias.sh status # новый URL
```
→ Обновить URL на телефоне **только если сменилась подсеть** (`10.219``10.74`).
---
### 3. `401 Unauthorized`
Пароль в Joplin ≠ env, или rclone не перезапущен.
```bash
grep JOPLIN_WEBDAV_PASS ~/.config/joplin-webdav/env
./docs/apps/joplin/scripts/joplin-webdav-reload.sh
```
---
### 4. `Not a valid URL: …/info.json`
Пробел в конце URL в настройках Joplin → переписать URL.
---
### 5. Порт 8080 не слушает
```bash
./docs/apps/joplin/scripts/joplin-webdav-status.sh
./docs/apps/joplin/scripts/joplin-webdav-serve.sh
```
Лог: `~/Library/Logs/joplin-webdav/launchd.err.log`.
---
### 6. `ECONNREFUSED` / телефон «не достаёт», Mac — да
**Причина:** alias **`*.100` отсутствует** на `en0` (слетел после reconnect).
```bash
ifconfig en0 | grep inet
./docs/apps/joplin/scripts/joplin-hotspot-alias.sh status
curl -s -o /dev/null -w '%{http_code}\n' http://10.74.21.100:8080/ # подставьте *.100 из status
```
`joplin-hotspot-alias.sh ensure`
В браузере **на телефоне**: `http://<subnet>.100:8080` — запрос логина
→ Нет интернета на Mac: [HOTSPOT_NETWORK_EMERGENCY.md](../../HOTSPOT_NETWORK_EMERGENCY.md)
---
### 7. `eternals.local` / `*.local` не работает с телефона
**Ожидаемо.** mDNS на Mac резолвится, Android-хотспот — нет. Использовать **`http://<subnet>.100:8080`**, не `.local`.
---
## Процедура: переподключили хотспот / сменили WiFi
### 1. Интернет на Mac
Если `curl` / браузер мёртвы:
```bash
bash /Users/eternal/code/local_machine/docs/connectivity/scripts/macos-hotspot-network-recover.sh recover
```
WiFi должен быть **DHCP**, не Manual ([HOTSPOT_NETWORK_EMERGENCY.md](../../HOTSPOT_NETWORK_EMERGENCY.md)).
### 2. Joplin
```bash
./docs/apps/joplin/scripts/joplin-webdav-status.sh
./docs/apps/joplin/scripts/joplin-hotspot-alias.sh ensure # если alias MISSING
```
Чеклист:
- [ ] rclone слушает `:8080`
- [ ] `alias: OK` (или `ensure` выполнен)
- [ ] **Mac:** `http://127.0.0.1:8080` — без изменений
- [ ] **Телефон:** URL из `status`**не менять**, если подсеть та же (`10.74.21.x`)
- [ ] Подсеть **сменилась** → один раз новый `http://<new-subnet>.100:8080`
- [ ] В rclone.log появились `from 10.` после синка на телефоне
### 3. Авто-alias (рекомендуется один раз)
```bash
bash ./docs/apps/joplin/scripts/joplin-android-hotspot-setup.sh install
bash ./docs/apps/joplin/scripts/joplin-hotspot-alias-install-sudo.sh
```
LaunchAgent поднимает `*.100` каждые 60 с после reconnect.
---
## Скрипты
| Скрипт | Когда |
|--------|--------|
| `joplin-webdav-status.sh` | Первая диагностика |
| `joplin-hotspot-alias.sh status\|ensure` | URL телефона, поднять alias |
| `joplin-android-hotspot-setup.sh install` | Полная настройка хотспота |
| `joplin-hotspot-alias-install-sudo.sh` | Alias без пароля (LaunchAgent) |
| `joplin-webdav-install.sh` | Первая установка rclone + launchd |
| `joplin-webdav-reload.sh` | После смены пароля в env |
| `joplin-mdns-setup.sh` | Справочно; **не для URL на телефоне-хотспоте** |
Путь: `docs/apps/joplin/scripts/`.
---
## Логи
**rclone.log** — успех: `PROPFIND`, `GET`, `PUT from <IP>`. Телефон часто виден как IP **шлюза** (`10.74.21.61`), не IP Mac — нормально для Android.
**log.txt Mac:**
```bash
grep -E 'Synchronizer: \[error\]|ETIMEDOUT|ECONNREFUSED|Unauthorized|Not a valid URL' \
~/.config/joplin-desktop/log.txt | tail -20
```
---
## Безопасность
- WebDAV только в LAN (`0.0.0.0:8080` пока rclone работает); не пробрасывать в интернет.
- env и пароль — не в git.
---
## Связанные документы
| Документ | Зачем |
|----------|--------|
| [STABLE_SETUP.md](./STABLE_SETUP.md) | Схема alias `*.100`, установка |
| [README.md](./README.md) | Оглавление |
| [docs/apps/joplin/README.md](../../../apps/joplin/README.md) | Установка с нуля |
| [HOTSPOT_NETWORK_EMERGENCY.md](../../HOTSPOT_NETWORK_EMERGENCY.md) | Сеть macOS после reconnect |
---
## История инцидентов
| Дата | Симптом | Корень | Фикс |
|------|---------|--------|------|
| 2026-06-02 | 401 | Пароль env ≠ Joplin | `joplin-webdav-reload.sh` |
| 2026-06-08 | ETIMEDOUT | Старый IP на телефоне | Обновить URL |
| 2026-06-09 | Телефон не достаёт `.100` | Mac без alias | `joplin-hotspot-alias ensure` |
| 2026-06-15 | Нет интернета после reconnect | Manual WiFi + битые маршруты | `macos-hotspot-network-recover.sh recover`, DHCP |
| 2026-06-19 | `eternals.local` не работает | Android-хотспот не резолвит mDNS клиентов | URL `http://10.74.21.100:8080` + alias |
| 2026-06-19 | Синк OK | alias `.100` + LaunchAgent | [STABLE_SETUP.md](./STABLE_SETUP.md) |
---
*Актуальный URL телефона: `./docs/apps/joplin/scripts/joplin-hotspot-alias.sh status`.*

View File

@@ -0,0 +1,55 @@
# Стабильная настройка: Android-хотспот + Mac + Joplin
---
## Важно: `.local` с телефона **не работает**
`http://eternals.local:8080` резолвится на **Mac**, но **Android, раздающий хотспот, не умеет** резолвить `.local` имён **подключённых** клиентов (Mac). Запросы до rclone не доходят.
**Рабочая схема:** alias **`*.100`** на `en0` + URL `http://<подсеть>.100:8080`.
| Устройство | URL |
|------------|-----|
| Mac | `http://127.0.0.1:8080` |
| Телефон | `http://10.74.21.100:8080` (подсеть меняется → см. ниже) |
---
## Установка (один раз)
```bash
./docs/apps/joplin/scripts/joplin-webdav-install.sh
bash ./docs/apps/joplin/scripts/joplin-android-hotspot-setup.sh install
```
На телефоне — URL из вывода (`joplin-hotspot-alias.sh status`).
Автоподнятие alias после reconnect:
```bash
./docs/apps/joplin/scripts/joplin-hotspot-alias-install-sudo.sh # sudoers, без пароля
```
---
## После reconnect
1. Alias поднимается LaunchAgent (если sudoers установлен)
2. Проверка: `./docs/apps/joplin/scripts/joplin-webdav-status.sh`
3. Если подсеть **та же** (`10.74.21.x`) — URL на телефоне **не менять**
4. Если подсеть **сменилась** (`10.219``10.74`) — **один раз** новый URL из `status`
Интернет пропал: `bash .../macos-hotspot-network-recover.sh recover`
---
## Почему URL иногда меняется
Android выдаёт **разные подсети** (`10.219.227.x`, `10.74.21.x`). Без Tailscale/VPN **нельзя** иметь один HTTP-URL при любой подсети с телефона-хотспота. Фиксированный **хвост `.100`** + автоподнятие alias минимизируют ручную работу.
---
## Связанное
- [RUNBOOK.md](./RUNBOOK.md)
- [HOTSPOT_NETWORK_EMERGENCY.md](../../HOTSPOT_NETWORK_EMERGENCY.md)

View File

@@ -1,32 +1,244 @@
#!/bin/sh
# Сброс сетевого стека macOS после смены хотспота / «интернет не поднимается без перезагрузки».
# Типичная причина в связке с docs/connectivity: системный PAC на localhost:1089 при мёртвом/ещё не
# поднятом PAC-сервере + кэш DNS / WiFi. См. HOTSPOT_NETWORK_EMERGENCY.md
# Сброс сети после переподключения Android-хотспота (macOS).
#
# Использование (из корня репозитория):
# ./docs/connectivity/scripts/macos-hotspot-network-recover.sh status
# ./docs/connectivity/scripts/macos-hotspot-network-recover.sh recover
# RCA (почему старый recover не помогал):
# - Ядерный сброс (route flush, ifconfig down) выполнялся только при sudo -n (passwordless).
# - Без NOPASSWD в sudoers скрипт делал лишь toggle WiFi — ARP REJECT и битые маршруты оставались.
# - Manual IPv4 на WiFi (Joplin 10.219.227.100) конфликтует с новой подсетью хотспота (10.36.x).
#
# Решение:
# - WiFi всегда DHCP (правильный шлюз после reconnect).
# - Joplin: вторичный IP (alias) *.100 в текущей подсети — не трогает default route.
# - recover всегда запрашивает sudo (пароль) для полного сброса.
#
# Из любой папки (одна команда, чинит интернет + Joplin alias):
# bash /Users/eternal/code/local_machine/docs/connectivity/scripts/macos-hotspot-network-recover.sh recover
#
# Переменные:
# MACOS_PAC_SERVICES — как в macos-system-pac.sh (по умолчанию Wi-Fi|Thunderbolt Bridge 2)
# WIFI_DEVICE например en0; иначе авто: первый Device у Hardware Port: Wi-Fi
# HOTSPOT_JOPLIN_MODE=alias|manual|off alias (по умолчанию при ~/.config/joplin-webdav/env), manual, off
# HOTSPOT_RECOVER_SKIP_JOPLIN=1 не трогать Joplin alias
# JOPLIN_STATIC_IP последний октет .100 из env; подсеть — текущая
# MACOS_PAC_SERVICES, WIFI_DEVICE
set -e
ROOT="$(cd "$(dirname "$0")" && pwd)"
PAC_SCRIPT="$ROOT/macos-system-pac.sh"
JOPLIN_ENV="${JOPLIN_WEBDAV_CONFIG:-$HOME/.config/joplin-webdav/env}"
JOPLIN_ALIAS_SCRIPT="${JOPLIN_ALIAS_SCRIPT:-$(cd "$ROOT/../../apps/joplin/scripts" 2>/dev/null && pwd)/joplin-hotspot-alias.sh}"
SCRIPT_SELF="$ROOT/$(basename "$0")"
wifi_device() {
if [ -n "${WIFI_DEVICE:-}" ]; then
printf '%s\n' "$WIFI_DEVICE"
return
fi
# "Device: en0" под блоком "Hardware Port: Wi-Fi"
networksetup -listallhardwareports 2>/dev/null \
| awk '/Hardware Port: Wi-Fi$/ {p=1; next} p && /^Device: / {print $2; exit}'
}
ipv4_subnet() {
printf '%s\n' "$1" | awk -F. '{print $1"."$2"."$3}'
}
ipv4_last_octet() {
printf '%s\n' "$1" | awk -F. '{print $4}'
}
load_joplin_env() {
if [ -f "$JOPLIN_ENV" ]; then
# shellcheck disable=SC1090
. "$JOPLIN_ENV"
fi
}
wifi_config_method() {
networksetup -getinfo "Wi-Fi" 2>/dev/null | awk 'NR==1 {print $1; exit}'
}
wifi_manual_router() {
networksetup -getinfo "Wi-Fi" 2>/dev/null | awk -F': ' '/^Router: / {print $2; exit}'
}
wifi_manual_ip() {
networksetup -getinfo "Wi-Fi" 2>/dev/null | awk -F': ' '/^IP address: / {print $2; exit}'
}
default_gateway() {
route -n get default 2>/dev/null | awk '/gateway:/{print $2; exit}'
}
joplin_mode() {
if [ "${HOTSPOT_RECOVER_SKIP_JOPLIN:-0}" = "1" ]; then
echo off
return
fi
if [ -n "${HOTSPOT_JOPLIN_MODE:-}" ]; then
echo "$HOTSPOT_JOPLIN_MODE"
return
fi
# mDNS (joplin-mac.local) — URL на телефоне не зависит от подсети; alias не нужен
echo off
}
joplin_target_ip() {
load_joplin_env
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}"
}
list_en0_alias_ips() {
dev="${1:-en0}"
ifconfig "$dev" 2>/dev/null | awk '/inet / && /alias/ {print $2}'
}
sudo_run() {
echo " \$ sudo $*"
sudo "$@"
}
ensure_dhcp_wifi() {
method="$(wifi_config_method 2>/dev/null || true)"
if [ "$method" = "Manual" ]; then
echo " WiFi Manual $(wifi_manual_ip)$(wifi_manual_router) — переводим на DHCP..."
networksetup -setdhcp "Wi-Fi" 2>/dev/null || sudo_run networksetup -setdhcp "Wi-Fi"
else
echo " WiFi уже DHCP (или не Manual)."
fi
}
nuclear_network_reset() {
dev="$1"
echo " Ядерный сброс на $dev (нужен пароль sudo)..."
sudo_run ifconfig "$dev" down
sudo_run route -n flush
sudo_run dscacheutil -flushcache
sudo_run killall -HUP mDNSResponder
sudo_run ifconfig "$dev" up
sudo_run ipconfig set "$dev" DHCP
}
wait_for_connectivity() {
dev="$1"
i=0
while [ "$i" -lt 30 ]; do
gw="$(default_gateway 2>/dev/null || true)"
ip="$(ipconfig getifaddr "$dev" 2>/dev/null || true)"
if [ -n "$gw" ] && [ -n "$ip" ]; then
if ping -c 1 -W 1500 "$gw" >/dev/null 2>&1; then
echo " Связность OK: IP=$ip gateway=$gw (${i}s)"
return 0
fi
fi
sleep 1
i=$((i + 1))
done
echo " WARN: gateway/IP не поднялись за 30с — проверьте captive portal" >&2
return 1
}
remove_joplin_aliases() {
dev="$1"
target="$2"
for ip in $(list_en0_alias_ips "$dev"); do
case "$ip" in
*.*.*.100|"$target")
echo " Удаляем alias $ip на $dev"
sudo_run ifconfig "$dev" inet "$ip" -alias 2>/dev/null || true
;;
esac
done
}
setup_joplin_alias() {
dev="$1"
target="$(joplin_target_ip 2>/dev/null || true)"
[ -n "$target" ] || { echo " Joplin alias: нет gateway" >&2; return 1; }
primary="$(ipconfig getifaddr "$dev" 2>/dev/null || true)"
if [ "$primary" = "$target" ]; then
echo " Joplin: primary IP уже $target"
return 0
fi
remove_joplin_aliases "$dev" "$target"
if ifconfig "$dev" 2>/dev/null | grep -q "inet $target "; then
echo " Joplin alias $target уже на $dev"
return 0
fi
echo " Joplin alias: $target/24 на $dev (primary DHCP: ${primary:-?})"
sudo_run ifconfig "$dev" inet "$target" netmask 255.255.255.0 alias
echo " URL на телефоне: http://${target}:8080"
if [ -f "$JOPLIN_ENV" ] && [ -n "${JOPLIN_STATIC_IP:-}" ]; then
cur_sub="$(ipv4_subnet "$target")"
env_sub="$(ipv4_subnet "$JOPLIN_STATIC_IP")"
if [ "$cur_sub" != "$env_sub" ]; then
echo " ⚠ Подсеть сменилась ($env_sub.x → $cur_sub.x) — обновите URL в Joplin на телефоне"
fi
fi
}
setup_joplin_manual() {
gw="$(default_gateway 2>/dev/null || true)"
[ -n "$gw" ] || return 1
target="$(joplin_target_ip 2>/dev/null || true)"
echo " Joplin manual (legacy): $target → router $gw"
networksetup -setmanual "Wi-Fi" "$target" 255.255.255.0 "$gw"
}
print_manual_ipv4_status() {
info="$(networksetup -getinfo "Wi-Fi" 2>/dev/null || true)"
echo "$info" | head -5
method="$(printf '%s\n' "$info" | awk 'NR==1 {print $1; exit}')"
dev="$(wifi_device 2>/dev/null || echo en0)"
current_ip="$(ipconfig getifaddr "$dev" 2>/dev/null || true)"
gw="$(default_gateway 2>/dev/null || true)"
echo "--- runtime $dev ---"
echo "primary IP: ${current_ip:-(none)}"
echo "default gateway: ${gw:-(none)}"
aliases="$(list_en0_alias_ips "$dev" | tr '\n' ' ')"
if [ -n "$aliases" ]; then
echo "aliases: $aliases"
fi
if [ -f "$JOPLIN_ENV" ]; then
target="$(joplin_target_ip 2>/dev/null || true)"
echo "joplin target: ${target:-?} (mode: $(joplin_mode))"
if [ -n "$target" ] && ! ifconfig "$dev" 2>/dev/null | grep -q "inet $target "; then
echo "⚠ Joplin IP $target не на интерфейсе — телефон не достучится"
fi
fi
if [ "$method" = "Manual" ]; then
manual_ip="$(printf '%s\n' "$info" | awk -F': ' '/^IP address: / {print $2; exit}')"
manual_router="$(printf '%s\n' "$info" | awk -F': ' '/^Router: / {print $2; exit}')"
if [ -n "$manual_router" ] && [ -n "$gw" ]; then
if [ "$(ipv4_subnet "$manual_router")" != "$(ipv4_subnet "$gw")" ]; then
echo "⚠ MISMATCH: Manual router $(ipv4_subnet "$manual_router").x vs gateway $(ipv4_subnet "$gw").x"
echo " → запустите: bash $SCRIPT_SELF recover"
elif [ -n "$current_ip" ] && [ "$current_ip" != "$manual_ip" ]; then
echo "⚠ MISMATCH: Manual IP $manual_ip, runtime $current_ip"
fi
fi
fi
if [ -n "$gw" ]; then
echo "--- gateway $gw ---"
arp -n "$gw" 2>/dev/null || true
route -n get "$gw" 2>/dev/null | grep -E 'interface|flags' || true
fi
}
usage() {
printf '%s\n' "Usage: $(basename "$0") status|recover" >&2
cat <<EOF
Usage: $(basename "$0") status|recover
recover — PAC/SOCKS off, DHCP, ядерный сброс (sudo), Joplin alias *.100
status — диагностика
Полная команда из любой папки:
bash $SCRIPT_SELF recover
EOF
exit 1
}
@@ -35,69 +247,70 @@ cmd=$1
case "$cmd" in
status)
echo "=== Wi-Fi device (hardware) ==="
echo "=== Wi-Fi device ==="
wifi_device || true
echo "=== networksetup -listallnetworkservices (excerpt) ==="
networksetup -listallnetworkservices 2>/dev/null | head -15 || true
echo "=== PAC / autoproxy (Wi-Fi + Thunderbolt Bridge 2 if present) ==="
echo "=== IPv4 ==="
print_manual_ipv4_status || true
echo "=== PAC ==="
if [ -x "$PAC_SCRIPT" ]; then
MACOS_PAC_SERVICES="${MACOS_PAC_SERVICES:-Wi-Fi|Thunderbolt Bridge 2}" "$PAC_SCRIPT" status || true
fi
echo "=== SOCKS firewall (Wi-Fi) ==="
echo "=== SOCKS (Wi-Fi) ==="
networksetup -getsocksfirewallproxy "Wi-Fi" 2>/dev/null || true
echo "=== Web proxy (Wi-Fi) ==="
networksetup -getwebproxy "Wi-Fi" 2>/dev/null || true
echo "=== Listener on 1089 (PAC URL default) ==="
if lsof -i :1089 -sTCP:LISTEN 2>/dev/null | head -5; then
:
else
echo "(none — при Enabled PAC в Safari/системе возможны сбои)"
fi
echo "=== PAC listener :1089 ==="
lsof -i :1089 -sTCP:LISTEN 2>/dev/null | head -3 || echo "(none)"
echo "=== Default route ==="
route -n get default 2>/dev/null | head -12 || true
echo "=== Quick reachability (ICMP may be blocked on some hotspots) ==="
ping -c 1 -W 2000 1.1.1.1 >/dev/null 2>&1 && echo "ping 1.1.1.1: ok" || echo "ping 1.1.1.1: fail"
route -n get default 2>/dev/null | head -8 || echo "(no default route)"
gw="$(default_gateway 2>/dev/null || true)"
if [ -n "$gw" ]; then
ping -c 1 -W 2000 "$gw" >/dev/null 2>&1 && echo "ping gateway: ok" || echo "ping gateway: fail"
fi
curl -sS --max-time 5 -o /dev/null https://1.1.1.1 2>/dev/null && echo "curl: ok" || echo "curl: fail"
;;
recover)
echo "1) Отключаем автоматический PAC на выбранных сервисах..."
echo "=== Hotspot recover (DHCP + route flush) ==="
echo "Скрипт запросит пароль sudo для сброса маршрутов."
echo ""
echo "1) PAC off..."
if [ -x "$PAC_SCRIPT" ]; then
MACOS_PAC_SERVICES="${MACOS_PAC_SERVICES:-Wi-Fi|Thunderbolt Bridge 2}" "$PAC_SCRIPT" off
else
echo "WARN: нет $PAC_SCRIPT — вручную: Системные настройки → Сеть → WiFi → Подробнее → Прокси → снять «Автоматическая настройка прокси»" >&2
fi
echo "2) Отключаем системный SOCKS-прокси на Wi-Fi (если был включён Shadowsocks и т.п.)..."
if networksetup -listallnetworkservices 2>/dev/null | grep -Fqx "Wi-Fi"; then
networksetup -setsocksfirewallproxystate "Wi-Fi" off 2>/dev/null || true
fi
echo "3) Сброс DNS-кэша (нужен sudo)..."
if sudo -n true 2>/dev/null; then
sudo dscacheutil -flushcache 2>/dev/null || true
sudo killall -HUP mDNSResponder 2>/dev/null || true
echo " mDNSResponder перезагружен."
else
echo " Пропуск (нет sudo без пароля). Выполните вручную:"
echo " sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder"
fi
echo "2) SOCKS off..."
networksetup -setsocksfirewallproxystate "Wi-Fi" off 2>/dev/null || true
echo "3) WiFi → DHCP (снимаем конфликтный Manual Joplin)..."
ensure_dhcp_wifi
dev="$(wifi_device)"
if [ -n "$dev" ]; then
echo "4) Обновление DHCP на $dev (нужен sudo)..."
if sudo -n true 2>/dev/null; then
sudo ipconfig set "$dev" DHCP 2>/dev/null || true
else
echo " Пропуск. Вручную: sudo ipconfig set $dev DHCP"
fi
echo "5) Краткое выключение/включение WiFi радиомодуля ($dev)..."
networksetup -setairportpower "$dev" off 2>/dev/null || true
sleep 2
networksetup -setairportpower "$dev" on 2>/dev/null || true
else
echo "45) Не удалось определить WiFi device — пропуск DHCP/airport power. Вручную выключите/включите WiFi в меню."
if [ -z "$dev" ]; then
echo "Ошибка: WiFi device не найден" >&2
exit 1
fi
echo "=== Готово. Проверка: ==="
"$0" status
echo "4) Ядерный сброс $dev..."
nuclear_network_reset "$dev"
echo "5) Ожидание DHCP..."
wait_for_connectivity "$dev" || true
mode="$(joplin_mode)"
case "$mode" in
alias)
echo "6) Joplin: alias IP (не ломает маршрутизацию)..."
if [ -x "$JOPLIN_ALIAS_SCRIPT" ]; then
"$JOPLIN_ALIAS_SCRIPT" ensure || true
else
setup_joplin_alias "$dev" || true
fi
;;
manual)
echo "6) Joplin: manual WiFi (legacy)..."
setup_joplin_manual || true
;;
*)
echo "6) Joplin: пропуск (нет $JOPLIN_ENV)"
;;
esac
echo ""
echo "Если интернет всё ещё нет: откройте хотспот в браузере (captive portal), затем при необходимости снова включите PAC:"
echo " ./docs/connectivity/scripts/macos-pac-on.sh"
echo "=== Итог ==="
"$SCRIPT_SELF" status
echo ""
echo "Captive portal: http://neverssl.com"
;;
*)
usage