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:
38
docs/apps/anytype/scripts/diagnose-phone-usb-mac.sh
Executable file
38
docs/apps/anytype/scripts/diagnose-phone-usb-mac.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
# Диагностика: Mac не видит телефон по USB (заряжается, но нет data / USB-модема).
|
||||
# Использование:
|
||||
# ./docs/apps/anytype/scripts/diagnose-phone-usb-mac.sh
|
||||
# ./docs/apps/anytype/scripts/diagnose-phone-usb-mac.sh watch # опрос каждые 2 с
|
||||
|
||||
set -e
|
||||
|
||||
watch="${1:-}"
|
||||
|
||||
scan() {
|
||||
printf '\n=== %s ===\n' "$(date '+%H:%M:%S')"
|
||||
echo "--- USB (iPhone / Android) ---"
|
||||
if system_profiler SPUSBDataType 2>/dev/null | grep -qiE 'iPhone|iPad|Apple Mobile|Android|MTP|PTP|Vendor ID: 0x05ac|Vendor ID: 0x18d1|Vendor ID: 0x04e8|Vendor ID: 0x2717'; then
|
||||
system_profiler SPUSBDataType 2>/dev/null | grep -B1 -A6 -iE 'iPhone|iPad|Apple Mobile|Android|MTP|PTP|Vendor ID: 0x05ac|Vendor ID: 0x18d1|Vendor ID: 0x04e8|Vendor ID: 0x2717'
|
||||
else
|
||||
echo "Телефон в USB-дереве НЕ найден (только зарядка или кабель/порт без data)."
|
||||
fi
|
||||
echo "--- usbmuxd (iPhone) ---"
|
||||
pgrep -lf usbmuxd 2>/dev/null || echo "usbmuxd не запущен"
|
||||
if command -v idevice_id >/dev/null 2>&1; then
|
||||
echo "idevice_id -l: $(idevice_id -l 2>&1 || true)"
|
||||
fi
|
||||
echo "--- Сетевые интерфейсы (ожидаем iPhone USB / RNDIS) ---"
|
||||
networksetup -listallnetworkservices 2>/dev/null | grep -iE 'iphone|usb|rndis|android' || echo "(службы USB-модема нет)"
|
||||
echo "--- Совет ---"
|
||||
echo "1) Кабель с передачей данных (не «только зарядка»)"
|
||||
echo "2) Порт Mac напрямую, не через хаб (сейчас виден Genesys USB Hub)"
|
||||
echo "3) iPhone: Разблокировать → «Доверять» → Настройки → Режим модема → USB"
|
||||
echo "4) Android: USB → «Передача файлов» или «USB-модем»"
|
||||
}
|
||||
|
||||
if [ "$watch" = "watch" ]; then
|
||||
echo "Следим за USB. Переподключите телефон. Ctrl+C для выхода."
|
||||
while true; do scan; sleep 2; done
|
||||
else
|
||||
scan
|
||||
fi
|
||||
84
docs/apps/joplin/README.md
Normal file
84
docs/apps/joplin/README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Joplin — локальный синк Mac ↔ телефон (WebDAV, без Docker)
|
||||
|
||||
Синхронизация через **rclone WebDAV** только в локальной сети. Данные на Mac в `~/JoplinSync`. Облака Joplin/Dropbox не используются.
|
||||
|
||||
---
|
||||
|
||||
## Быстрый старт (Mac)
|
||||
|
||||
```bash
|
||||
brew install rclone # если ещё нет
|
||||
chmod +x ./docs/apps/joplin/scripts/joplin-webdav-*.sh
|
||||
./docs/apps/joplin/scripts/joplin-webdav-install.sh
|
||||
```
|
||||
|
||||
Показать URL и статус:
|
||||
|
||||
```bash
|
||||
./docs/apps/joplin/scripts/joplin-webdav-status.sh
|
||||
```
|
||||
|
||||
Учётка: `~/.config/joplin-webdav/env` (права `600`, **не коммитить**).
|
||||
|
||||
Остановка / запуск:
|
||||
|
||||
```bash
|
||||
./docs/apps/joplin/scripts/joplin-webdav-stop.sh
|
||||
./docs/apps/joplin/scripts/joplin-webdav-serve.sh
|
||||
```
|
||||
|
||||
**После смены пароля в `~/.config/joplin-webdav/env`** — обязательно перезапуск (иначе rclone держит старый пароль → **401**):
|
||||
|
||||
```bash
|
||||
./docs/apps/joplin/scripts/joplin-webdav-reload.sh
|
||||
```
|
||||
|
||||
Пароль в Joplin на телефоне и Mac должен **совпадать с `JOPLIN_WEBDAV_PASS` в env**, не со старым из установки.
|
||||
|
||||
Автозапуск при входе: LaunchAgent `com.eternal.joplin-webdav` (ставится install-скриптом).
|
||||
|
||||
---
|
||||
|
||||
## Настройка Joplin (Mac и телефон)
|
||||
|
||||
1. **Настройки → Синхронизация**
|
||||
2. Внизу: **Self-hosting → Click here to select one**
|
||||
3. **WebDAV**
|
||||
4. Заполнить:
|
||||
|
||||
| Поле | Значение |
|
||||
|------|----------|
|
||||
| WebDAV URL | **Mac:** `http://127.0.0.1:8080` · **Телефон:** `http://10.219.227.100:8080` (статический IP на Mac — [STABLE_SETUP](../../connectivity/apps/joplin/STABLE_SETUP.md)) |
|
||||
| Логин | `joplin` |
|
||||
| Пароль | из `~/.config/joplin-webdav/env` → `JOPLIN_WEBDAV_PASS` |
|
||||
|
||||
5. **Проверить конфигурацию синхронизации** → OK → синхронизировать.
|
||||
|
||||
**Важно:** Mac и телефон в **одной сети** (хотспот телефона). URL с `http://`, не `https://`. Без слэша в конце URL.
|
||||
|
||||
Опционально: **Настройки → Шифрование** — E2EE (мастер-пароль), на сервере только шифротекст.
|
||||
|
||||
---
|
||||
|
||||
## Приватность
|
||||
|
||||
- Заметки на телефоне — в песочнице **Joplin**, не «доступ ко всем файлам».
|
||||
- WebDAV слушает `0.0.0.0:8080` только пока запущен rclone; не открывайте порт в интернет.
|
||||
- При включённом фаерволе macOS разрешите вход для **rclone** или порта **8080** в локальной сети.
|
||||
|
||||
---
|
||||
|
||||
## Если не коннектится с телефона
|
||||
|
||||
1. `./docs/apps/joplin/scripts/joplin-webdav-status.sh` — процесс и IP.
|
||||
2. На телефоне в браузере: `http://IP_MAC:8080` (может запросить логин — значит сеть доходит).
|
||||
3. Синк сломался — [RUNBOOK](../../connectivity/apps/joplin/RUNBOOK.md); стабильная схема без смены URL — [STABLE_SETUP](../../connectivity/apps/joplin/STABLE_SETUP.md).
|
||||
4. Логи: `~/Library/Logs/joplin-webdav/`
|
||||
|
||||
---
|
||||
|
||||
## Связанное
|
||||
|
||||
- **[Runbook: синк сломался (хотспот, логи, ETIMEDOUT)](../../connectivity/apps/joplin/RUNBOOK.md)**
|
||||
- [Anytype (P2P, без WebDAV)](../anytype/README.md)
|
||||
- [diagnose-phone-usb-mac.sh](../anytype/scripts/diagnose-phone-usb-mac.sh) — если понадобится USB
|
||||
31
docs/apps/joplin/scripts/joplin-android-hotspot-setup.sh
Executable file
31
docs/apps/joplin/scripts/joplin-android-hotspot-setup.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
# Полная настройка Joplin для Android-хотспота (Mac = клиент Wi‑Fi).
|
||||
# .local с телефона не работает → alias *.100 + LaunchAgent.
|
||||
#
|
||||
# ./joplin-android-hotspot-setup.sh install
|
||||
|
||||
set -e
|
||||
|
||||
ROOT="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
|
||||
|
||||
echo "=== Joplin: Android-хотспот (Mac клиент) ==="
|
||||
echo ""
|
||||
echo "Почему не eternals.local:"
|
||||
echo " Android, раздающий хотспот, не резолвит .local имён подключённых устройств."
|
||||
echo " rclone.log — только 127.0.0.1, запросов с телефона нет."
|
||||
echo ""
|
||||
|
||||
"$ROOT/joplin-hotspot-alias-install.sh"
|
||||
echo ""
|
||||
echo "Поднимаем alias (sudo)..."
|
||||
"$ROOT/joplin-hotspot-alias.sh" ensure
|
||||
echo ""
|
||||
"$ROOT/joplin-hotspot-alias.sh" status
|
||||
echo ""
|
||||
echo "=== На телефоне в Joplin WebDAV ==="
|
||||
grep '^JOPLIN_PHONE_URL=' "${JOPLIN_WEBDAV_CONFIG:-$HOME/.config/joplin-webdav/env}" 2>/dev/null \
|
||||
| cut -d= -f2- || phone_url=$("$ROOT/joplin-hotspot-alias.sh" status | awk '/URL на телефоне/{getline; print}' | tr -d ' ')
|
||||
echo " $(grep '^JOPLIN_PHONE_URL=' "$HOME/.config/joplin-webdav/env" 2>/dev/null | cut -d= -f2-)"
|
||||
echo ""
|
||||
echo "При смене подсети (10.219→10.74) URL меняется — alias LaunchAgent поднимет *.100,"
|
||||
echo "новый URL: ./joplin-hotspot-alias.sh status"
|
||||
25
docs/apps/joplin/scripts/joplin-hotspot-alias-install-sudo.sh
Executable file
25
docs/apps/joplin/scripts/joplin-hotspot-alias-install-sudo.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
# Одноразово: passwordless sudo только для joplin-hotspot-alias.sh ensure
|
||||
set -e
|
||||
|
||||
SCRIPT="$(CDPATH= cd -- "$(dirname "$0")" && pwd)/joplin-hotspot-alias.sh"
|
||||
DEST="/etc/sudoers.d/joplin-hotspot-alias"
|
||||
USER_NAME="$(whoami)"
|
||||
|
||||
[ -x "$SCRIPT" ] || chmod +x "$SCRIPT"
|
||||
|
||||
TMP="$(mktemp)"
|
||||
trap 'rm -f "$TMP"' EXIT
|
||||
|
||||
cat >"$TMP" <<EOF
|
||||
# Joplin WebDAV: alias *.100 на en0 после reconnect хотспота (только этот скрипт)
|
||||
${USER_NAME} ALL=(ALL) NOPASSWD: ${SCRIPT} ensure
|
||||
EOF
|
||||
|
||||
echo "Будет создан $DEST:"
|
||||
cat "$TMP"
|
||||
echo ""
|
||||
sudo cp "$TMP" "$DEST"
|
||||
sudo chmod 440 "$DEST"
|
||||
sudo visudo -c -f "$DEST"
|
||||
echo "OK. LaunchAgent сможет поднимать alias без пароля."
|
||||
45
docs/apps/joplin/scripts/joplin-hotspot-alias-install.sh
Executable file
45
docs/apps/joplin/scripts/joplin-hotspot-alias-install.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
# LaunchAgent: каждые 60 с проверять alias *.100 (нужен joplin-hotspot-alias-install-sudo.sh).
|
||||
set -e
|
||||
|
||||
ROOT="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
|
||||
ALIAS="$ROOT/joplin-hotspot-alias.sh"
|
||||
PLIST="$HOME/Library/LaunchAgents/com.eternal.joplin-hotspot-alias.plist"
|
||||
|
||||
chmod +x "$ROOT"/joplin-hotspot-alias*.sh
|
||||
|
||||
mkdir -p "$HOME/Library/LaunchAgents" "$HOME/Library/Logs/joplin-webdav"
|
||||
|
||||
cat >"$PLIST" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.eternal.joplin-hotspot-alias</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>$ALIAS</string>
|
||||
<string>ensure</string>
|
||||
</array>
|
||||
<key>StartInterval</key>
|
||||
<integer>60</integer>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>$HOME/Library/Logs/joplin-webdav/alias-launchd.out.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>$HOME/Library/Logs/joplin-webdav/alias-launchd.err.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
launchctl bootout "gui/$(id -u)/com.eternal.joplin-hotspot-alias" 2>/dev/null || true
|
||||
launchctl bootstrap "gui/$(id -u)" "$PLIST"
|
||||
launchctl enable "gui/$(id -u)/com.eternal.joplin-hotspot-alias" 2>/dev/null || true
|
||||
|
||||
echo "LaunchAgent com.eternal.joplin-hotspot-alias установлен (каждые 60 с)."
|
||||
echo "Для работы без пароля один раз:"
|
||||
echo " $ROOT/joplin-hotspot-alias-install-sudo.sh"
|
||||
echo "Сейчас:"
|
||||
"$ALIAS" status
|
||||
149
docs/apps/joplin/scripts/joplin-hotspot-alias.sh
Executable file
149
docs/apps/joplin/scripts/joplin-hotspot-alias.sh
Executable file
@@ -0,0 +1,149 @@
|
||||
#!/bin/sh
|
||||
# Стабильный URL для Joplin на телефоне при Android-хотспоте: http://<подсеть>.100:8080
|
||||
# Alias *.100 на en0 (DHCP primary не трогаем). LaunchAgent поднимает alias после reconnect.
|
||||
#
|
||||
# ./joplin-hotspot-alias.sh status
|
||||
# ./joplin-hotspot-alias.sh ensure
|
||||
#
|
||||
# Passwordless: ./joplin-hotspot-alias-install-sudo.sh
|
||||
|
||||
set -e
|
||||
|
||||
CONFIG="${JOPLIN_WEBDAV_CONFIG:-$HOME/.config/joplin-webdav/env}"
|
||||
WIFI_DEV="${WIFI_DEVICE:-en0}"
|
||||
PORT="${JOPLIN_WEBDAV_PORT:-8080}"
|
||||
LOG="$HOME/Library/Logs/joplin-webdav/alias.log"
|
||||
|
||||
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} "
|
||||
}
|
||||
|
||||
log_msg() {
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$LOG"
|
||||
}
|
||||
|
||||
run_priv() {
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
"$@"
|
||||
else
|
||||
sudo "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
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: Android-хотспот (alias *.100)"
|
||||
echo "device: $WIFI_DEV"
|
||||
echo "primary: ${primary:-(none)}"
|
||||
echo "gateway: ${gw:-(none)}"
|
||||
echo "joplin IP: ${target:-(no gateway)}"
|
||||
|
||||
if [ -n "$target" ] && has_ip_on_dev "$target" "$WIFI_DEV"; then
|
||||
echo "alias: OK"
|
||||
elif [ -n "$target" ] && [ "$primary" = "$target" ]; then
|
||||
echo "alias: OK (primary)"
|
||||
elif [ -n "$target" ]; then
|
||||
echo "alias: MISSING — телефон не достучится"
|
||||
echo " fix: $0 ensure"
|
||||
fi
|
||||
|
||||
if [ -n "$url" ]; then
|
||||
echo ""
|
||||
echo "URL на телефоне (работает с Android-хотспота):"
|
||||
echo " $url"
|
||||
echo ""
|
||||
echo "⚠ http://*.local:8080 с телефона НЕ работает (Android не резолвит mDNS клиентов хотспота)"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_ensure() {
|
||||
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" ] || has_ip_on_dev "$target" "$WIFI_DEV"; then
|
||||
write_phone_url_env
|
||||
exit 0
|
||||
fi
|
||||
log_msg "add alias $target on $WIFI_DEV (primary $primary)"
|
||||
run_priv ifconfig "$WIFI_DEV" inet "$target" netmask 255.255.255.0 alias
|
||||
write_phone_url_env
|
||||
echo "alias OK: $target"
|
||||
echo "URL на телефоне: $(phone_url)"
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage: $(basename "$0") status|ensure" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
case "${1:-ensure}" in
|
||||
status) cmd_status ;;
|
||||
ensure) cmd_ensure ;;
|
||||
-h|--help) usage ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
165
docs/apps/joplin/scripts/joplin-mdns-setup.sh
Executable file
165
docs/apps/joplin/scripts/joplin-mdns-setup.sh
Executable file
@@ -0,0 +1,165 @@
|
||||
#!/bin/sh
|
||||
# Стабильный URL для Joplin на телефоне: http://<LocalHostName>.local:8080
|
||||
# Не зависит от подсети хотспота (10.219.x → 10.74.x). Alias *.100 не нужен.
|
||||
#
|
||||
# ./joplin-mdns-setup.sh status
|
||||
# ./joplin-mdns-setup.sh install # записать URL в env (rename опционально)
|
||||
# ./joplin-mdns-setup.sh verify
|
||||
|
||||
set -e
|
||||
|
||||
CONFIG="${JOPLIN_WEBDAV_CONFIG:-$HOME/.config/joplin-webdav/env}"
|
||||
PORT="${JOPLIN_WEBDAV_PORT:-8080}"
|
||||
WIFI_DEV="${WIFI_DEVICE:-en0}"
|
||||
# Пусто = текущий LocalHostName Mac (у вас часто Eternals → eternals.local)
|
||||
PREFERRED_HOST="${JOPLIN_MDNS_HOST:-}"
|
||||
|
||||
load_config() {
|
||||
if [ -f "$CONFIG" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$CONFIG"
|
||||
fi
|
||||
PORT="${JOPLIN_WEBDAV_PORT:-$PORT}"
|
||||
PREFERRED_HOST="${JOPLIN_MDNS_HOST:-$PREFERRED_HOST}"
|
||||
}
|
||||
|
||||
current_local_hostname() {
|
||||
scutil --get LocalHostName 2>/dev/null || true
|
||||
}
|
||||
|
||||
mdns_host() {
|
||||
load_config
|
||||
if [ -n "$PREFERRED_HOST" ]; then
|
||||
printf '%s\n' "$PREFERRED_HOST"
|
||||
else
|
||||
current_local_hostname
|
||||
fi
|
||||
}
|
||||
|
||||
mdns_fqdn() {
|
||||
printf '%s.local\n' "$(mdns_host | tr '[:upper:]' '[:lower:]')"
|
||||
}
|
||||
|
||||
phone_url() {
|
||||
host="$(mdns_host | tr '[:upper:]' '[:lower:]')"
|
||||
printf 'http://%s.local:%s\n' "$host" "$PORT"
|
||||
}
|
||||
|
||||
primary_ip() {
|
||||
ipconfig getifaddr "$WIFI_DEV" 2>/dev/null || true
|
||||
}
|
||||
|
||||
resolve_mdns_ip() {
|
||||
host="$(mdns_fqdn)"
|
||||
dscacheutil -q host -a name "$host" 2>/dev/null \
|
||||
| awk '/ip_address:/ {print $2}' | grep -v '^127\.' | head -1
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
load_config
|
||||
local_name="$(current_local_hostname)"
|
||||
host="$(mdns_host)"
|
||||
ip="$(primary_ip)"
|
||||
resolved="$(resolve_mdns_ip 2>/dev/null || true)"
|
||||
url="$(phone_url)"
|
||||
|
||||
echo "LocalHostName: ${local_name:-?}"
|
||||
echo "mDNS host: $host → $(mdns_fqdn)"
|
||||
echo "primary $WIFI_DEV: ${ip:-(none)}"
|
||||
echo "mDNS → IP: ${resolved:-(none)}"
|
||||
echo ""
|
||||
echo "mDNS URL (НЕ для Android-хотспота — телефон не резолвит .local клиентов):"
|
||||
echo " $url"
|
||||
echo "URL на Mac: http://127.0.0.1:${PORT}"
|
||||
echo "Телефон-хотспот: ./joplin-hotspot-alias.sh status"
|
||||
|
||||
if [ -n "$PREFERRED_HOST" ] && [ "$local_name" != "$PREFERRED_HOST" ]; then
|
||||
echo ""
|
||||
echo "⚠ В env JOPLIN_MDNS_HOST=$PREFERRED_HOST, LocalHostName=$local_name"
|
||||
echo " install переименует Mac или уберите JOPLIN_MDNS_HOST из env"
|
||||
elif [ -n "$ip" ] && [ -n "$resolved" ] && [ "$ip" = "$resolved" ]; then
|
||||
echo ""
|
||||
echo "OK: mDNS на Mac резолвится (для телефона используйте alias *.100)"
|
||||
elif [ -n "$ip" ] && [ -z "$resolved" ]; then
|
||||
echo ""
|
||||
echo "⚠ mDNS пока не резолвится — подождите или переподключите Wi‑Fi"
|
||||
elif [ -z "$ip" ]; then
|
||||
echo ""
|
||||
echo "⚠ Нет IP на $WIFI_DEV"
|
||||
fi
|
||||
}
|
||||
|
||||
write_env_mdns() {
|
||||
load_config
|
||||
url="$(phone_url)"
|
||||
host="$(mdns_host)"
|
||||
if [ ! -f "$CONFIG" ]; then
|
||||
echo "Нет $CONFIG — сначала joplin-webdav-install.sh" >&2
|
||||
return 1
|
||||
fi
|
||||
if grep -q '^JOPLIN_MDNS_HOST=' "$CONFIG" 2>/dev/null; then
|
||||
sed -i '' "s|^JOPLIN_MDNS_HOST=.*|JOPLIN_MDNS_HOST=$host|" "$CONFIG" 2>/dev/null \
|
||||
|| sed -i "s|^JOPLIN_MDNS_HOST=.*|JOPLIN_MDNS_HOST=$host|" "$CONFIG"
|
||||
else
|
||||
printf 'JOPLIN_MDNS_HOST=%s\n' "$host" >>"$CONFIG"
|
||||
fi
|
||||
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_install() {
|
||||
load_config
|
||||
local_name="$(current_local_hostname)"
|
||||
target="$(mdns_host)"
|
||||
|
||||
if [ -n "$PREFERRED_HOST" ] && [ "$local_name" != "$PREFERRED_HOST" ]; then
|
||||
echo "Переименование LocalHostName: $local_name → $PREFERRED_HOST"
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
scutil --set LocalHostName "$PREFERRED_HOST"
|
||||
else
|
||||
sudo scutil --set LocalHostName "$PREFERRED_HOST"
|
||||
fi
|
||||
else
|
||||
echo "Используем текущий LocalHostName: $local_name (→ $(mdns_fqdn))"
|
||||
fi
|
||||
|
||||
write_env_mdns
|
||||
echo ""
|
||||
echo "На телефоне в Joplin WebDAV (один раз):"
|
||||
echo " $(phone_url)"
|
||||
echo ""
|
||||
sleep 2
|
||||
cmd_verify || true
|
||||
}
|
||||
|
||||
cmd_verify() {
|
||||
ip="$(primary_ip)"
|
||||
resolved="$(resolve_mdns_ip 2>/dev/null || true)"
|
||||
if [ -z "$ip" ]; then
|
||||
echo "verify: нет IP на $WIFI_DEV" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$ip" = "$resolved" ]; then
|
||||
echo "verify OK: $(mdns_fqdn) → $ip"
|
||||
exit 0
|
||||
fi
|
||||
echo "verify: mDNS=${resolved:-none} ip=$ip (часто OK через 30с после connect)" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage: $(basename "$0") status|install|verify" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
case "${1:-status}" in
|
||||
status) cmd_status ;;
|
||||
install) cmd_install ;;
|
||||
verify) cmd_verify ;;
|
||||
-h|--help) usage ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
90
docs/apps/joplin/scripts/joplin-webdav-install.sh
Executable file
90
docs/apps/joplin/scripts/joplin-webdav-install.sh
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/bin/sh
|
||||
# Установка: конфиг, launchd (автозапуск), первый старт WebDAV.
|
||||
set -e
|
||||
|
||||
ROOT="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
|
||||
CONFIG_DIR="$HOME/.config/joplin-webdav"
|
||||
CONFIG="$CONFIG_DIR/env"
|
||||
LAUNCH_AGENT="$HOME/Library/LaunchAgents/com.eternal.joplin-webdav.plist"
|
||||
SERVE="$ROOT/joplin-webdav-serve.sh"
|
||||
|
||||
mkdir -p "$CONFIG_DIR" "$HOME/JoplinSync" "$HOME/Library/Logs/joplin-webdav"
|
||||
|
||||
if [ ! -f "$CONFIG" ]; then
|
||||
PASS=$(openssl rand -base64 18 | tr -d '/+=' | head -c 24)
|
||||
cat > "$CONFIG" <<EOF
|
||||
# Локальный WebDAV для Joplin. Только LAN. Не коммитить.
|
||||
JOPLIN_WEBDAV_USER=joplin
|
||||
JOPLIN_WEBDAV_PASS=$PASS
|
||||
JOPLIN_WEBDAV_PORT=8080
|
||||
JOPLIN_WEBDAV_ROOT=$HOME/JoplinSync
|
||||
# Опционально (legacy alias *.100 — не нужен при mDNS):
|
||||
# JOPLIN_STATIC_IP=10.219.227.100
|
||||
# JOPLIN_MDNS_HOST=joplin-mac
|
||||
# JOPLIN_PHONE_URL=http://joplin-mac.local:8080
|
||||
EOF
|
||||
chmod 600 "$CONFIG"
|
||||
echo "Создан $CONFIG"
|
||||
else
|
||||
echo "Конфиг уже есть: $CONFIG"
|
||||
fi
|
||||
|
||||
chmod +x "$ROOT"/joplin-webdav-*.sh
|
||||
|
||||
if ! command -v rclone >/dev/null 2>&1; then
|
||||
echo "Устанавливаю rclone..."
|
||||
brew install rclone
|
||||
fi
|
||||
|
||||
mkdir -p "$HOME/Library/LaunchAgents"
|
||||
cat > "$LAUNCH_AGENT" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.eternal.joplin-webdav</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>$SERVE</string>
|
||||
<string>--foreground</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>$HOME/Library/Logs/joplin-webdav/launchd.out.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>$HOME/Library/Logs/joplin-webdav/launchd.err.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
launchctl bootout "gui/$(id -u)/com.eternal.joplin-webdav" 2>/dev/null || true
|
||||
launchctl bootstrap "gui/$(id -u)" "$LAUNCH_AGENT"
|
||||
launchctl enable "gui/$(id -u)/com.eternal.joplin-webdav" 2>/dev/null || true
|
||||
|
||||
launchctl enable "gui/$(id -u)/com.eternal.joplin-webdav" 2>/dev/null || true
|
||||
|
||||
chmod +x "$ROOT"/joplin-mdns-setup.sh 2>/dev/null || true
|
||||
if [ -x "$ROOT/joplin-mdns-setup.sh" ]; then
|
||||
echo ""
|
||||
echo "=== mDNS (стабильный URL на телефоне) ==="
|
||||
if [ "$(scutil --get LocalHostName 2>/dev/null)" = "joplin-mac" ]; then
|
||||
"$ROOT/joplin-mdns-setup.sh" status || true
|
||||
else
|
||||
echo "LocalHostName ещё не joplin-mac. Один раз:"
|
||||
echo " $ROOT/joplin-mdns-setup.sh install"
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
"$ROOT/joplin-webdav-status.sh"
|
||||
|
||||
echo ""
|
||||
echo "Пароль для Joplin:"
|
||||
grep JOPLIN_WEBDAV_PASS "$CONFIG" | cut -d= -f2-
|
||||
echo ""
|
||||
echo "На телефоне (один раз, навсегда): http://joplin-mac.local:8080"
|
||||
echo "Установка имени: $ROOT/joplin-mdns-setup.sh install"
|
||||
7
docs/apps/joplin/scripts/joplin-webdav-reload.sh
Executable file
7
docs/apps/joplin/scripts/joplin-webdav-reload.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
# Перечитать ~/.config/joplin-webdav/env и перезапустить WebDAV (после смены пароля).
|
||||
set -e
|
||||
uid=$(id -u)
|
||||
launchctl kickstart -k "gui/${uid}/com.eternal.joplin-webdav"
|
||||
sleep 2
|
||||
"$(dirname "$0")/joplin-webdav-status.sh"
|
||||
72
docs/apps/joplin/scripts/joplin-webdav-serve.sh
Executable file
72
docs/apps/joplin/scripts/joplin-webdav-serve.sh
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/bin/sh
|
||||
# Локальный WebDAV для синхронизации Joplin (Mac ↔ телефон по LAN).
|
||||
# Данные: ~/JoplinSync. Учётка: ~/.config/joplin-webdav/env
|
||||
#
|
||||
# ./joplin-webdav-serve.sh # фон
|
||||
# ./joplin-webdav-serve.sh --foreground # передний план (для launchd)
|
||||
|
||||
set -e
|
||||
|
||||
CONFIG="${JOPLIN_WEBDAV_CONFIG:-$HOME/.config/joplin-webdav/env}"
|
||||
LOG_DIR="$HOME/Library/Logs/joplin-webdav"
|
||||
PID_FILE="$HOME/.config/joplin-webdav/rclone.pid"
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||
|
||||
mkdir -p "$LOG_DIR" "$(dirname "$PID_FILE")"
|
||||
|
||||
if [ ! -f "$CONFIG" ]; then
|
||||
echo "Нет $CONFIG — запустите: $SCRIPT_DIR/joplin-webdav-install.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
. "$CONFIG"
|
||||
|
||||
: "${JOPLIN_WEBDAV_USER:?}"
|
||||
: "${JOPLIN_WEBDAV_PASS:?}"
|
||||
: "${JOPLIN_WEBDAV_PORT:=8080}"
|
||||
: "${JOPLIN_WEBDAV_ROOT:=$HOME/JoplinSync}"
|
||||
|
||||
mkdir -p "$JOPLIN_WEBDAV_ROOT"
|
||||
|
||||
RCLONE="$(command -v rclone || echo /opt/homebrew/bin/rclone)"
|
||||
if [ ! -x "$RCLONE" ]; then
|
||||
echo "rclone не найден. Установите: brew install rclone" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run_rclone() {
|
||||
exec "$RCLONE" serve webdav "$JOPLIN_WEBDAV_ROOT" \
|
||||
--addr "0.0.0.0:${JOPLIN_WEBDAV_PORT}" \
|
||||
--user "$JOPLIN_WEBDAV_USER" \
|
||||
--pass "$JOPLIN_WEBDAV_PASS" \
|
||||
--vfs-cache-mode minimal \
|
||||
--log-file "$LOG_DIR/rclone.log" \
|
||||
--log-level INFO
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--foreground|-f)
|
||||
run_rclone
|
||||
;;
|
||||
*)
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
old_pid=$(cat "$PID_FILE")
|
||||
if kill -0 "$old_pid" 2>/dev/null; then
|
||||
echo "Уже запущен (PID $old_pid), порт ${JOPLIN_WEBDAV_PORT}"
|
||||
exit 0
|
||||
fi
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
nohup "$0" --foreground >>"$LOG_DIR/stdout.log" 2>&1 &
|
||||
echo $! >"$PID_FILE"
|
||||
sleep 1
|
||||
if kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
||||
echo "WebDAV запущен (PID $(cat "$PID_FILE")), порт ${JOPLIN_WEBDAV_PORT}"
|
||||
else
|
||||
echo "Не удалось запустить. См. $LOG_DIR/stdout.log" >&2
|
||||
rm -f "$PID_FILE"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
73
docs/apps/joplin/scripts/joplin-webdav-status.sh
Executable file
73
docs/apps/joplin/scripts/joplin-webdav-status.sh
Executable file
@@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
CONFIG="${JOPLIN_WEBDAV_CONFIG:-$HOME/.config/joplin-webdav/env}"
|
||||
PID_FILE="$HOME/.config/joplin-webdav/rclone.pid"
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
||||
|
||||
echo "=== WebDAV Joplin ==="
|
||||
if [ -f "$CONFIG" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$CONFIG"
|
||||
echo "Пользователь: ${JOPLIN_WEBDAV_USER:-?}"
|
||||
echo "Порт: ${JOPLIN_WEBDAV_PORT:-8080}"
|
||||
echo "Папка: ${JOPLIN_WEBDAV_ROOT:-$HOME/JoplinSync}"
|
||||
else
|
||||
echo "Конфиг не найден: $CONFIG"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Процесс ==="
|
||||
if pgrep -f "rclone serve webdav" >/dev/null 2>&1; then
|
||||
pgrep -lf "rclone serve webdav" 2>/dev/null || true
|
||||
else
|
||||
echo "Не запущен → ./docs/apps/joplin/scripts/joplin-webdav-serve.sh"
|
||||
fi
|
||||
|
||||
port="${JOPLIN_WEBDAV_PORT:-8080}"
|
||||
|
||||
echo ""
|
||||
echo "=== URL для телефона (Android-хотспот) ==="
|
||||
if [ -x "$SCRIPT_DIR/joplin-hotspot-alias.sh" ]; then
|
||||
"$SCRIPT_DIR/joplin-hotspot-alias.sh" status
|
||||
else
|
||||
echo "(нет joplin-hotspot-alias.sh)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== mDNS (.local) — с телефона НЕ работает ==="
|
||||
if [ -x "$SCRIPT_DIR/joplin-mdns-setup.sh" ]; then
|
||||
host="$(scutil --get LocalHostName 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo '?')"
|
||||
echo "http://${host}.local:${port} — только Mac/Windows; Android-хотспот не резолвит"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== IP Mac (DHCP, справочно) ==="
|
||||
ip=$(ipconfig getifaddr en0 2>/dev/null || true)
|
||||
[ -n "$ip" ] && echo "primary en0: $ip" || echo "нет IP"
|
||||
|
||||
echo ""
|
||||
echo "=== Порт :8080 ==="
|
||||
lsof -nP -iTCP:"$port" -sTCP:LISTEN 2>/dev/null || echo "не слушает"
|
||||
|
||||
echo ""
|
||||
echo "=== URL в Joplin (Mac) ==="
|
||||
settings="$HOME/.config/joplin-desktop/settings.json"
|
||||
if [ -f "$settings" ]; then
|
||||
grep -o '"sync\.6\.path": *"[^"]*"' "$settings" 2>/dev/null | sed 's/.*"\(http[^"]*\)".*/\1/' \
|
||||
| while read -r u; do echo "settings.json: $u"; done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== rclone (запросы с телефона?) ==="
|
||||
log="$HOME/Library/Logs/joplin-webdav/rclone.log"
|
||||
if [ -f "$log" ]; then
|
||||
phone_lines=$(grep -E 'from 10\.|from 192\.168\.' "$log" 2>/dev/null | tail -3)
|
||||
if [ -n "$phone_lines" ]; then
|
||||
echo "$phone_lines"
|
||||
else
|
||||
echo "нет запросов с LAN (только 127.0.0.1?) — телефон не достучался"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Учётка ==="
|
||||
[ -f "$CONFIG" ] && echo "Пароль: $CONFIG → JOPLIN_WEBDAV_PASS"
|
||||
18
docs/apps/joplin/scripts/joplin-webdav-stop.sh
Executable file
18
docs/apps/joplin/scripts/joplin-webdav-stop.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
PID_FILE="$HOME/.config/joplin-webdav/rclone.pid"
|
||||
|
||||
if [ ! -f "$PID_FILE" ]; then
|
||||
echo "WebDAV не запущен (нет $PID_FILE)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pid=$(cat "$PID_FILE")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
kill "$pid" 2>/dev/null || true
|
||||
sleep 1
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
echo "Остановлен (PID $pid)"
|
||||
else
|
||||
echo "Процесс $pid уже не работает"
|
||||
fi
|
||||
rm -f "$PID_FILE"
|
||||
Reference in New Issue
Block a user