fix(launcher): сообщение при запуске не совпадало с настройками; смена токена

Владелец привязал хаб на сервере к сети, а лаунчер всё равно напечатал
«Headless Mode», показал http://127.0.0.1:5800 и посоветовал пробросить порт
через ssh -L. Текст был зашит и настроек не читал: инструкция вела поднимать
туннель к серверу, который уже был виден напрямую. Инструкция, не
совпадающая с реальностью, хуже отсутствующей — тот же класс дефекта, что и
снятая заглушка про «вход через веб невозможен».

Теперь сообщение читает web_api_host из hub_settings.json: при 127.0.0.1
показывает проброс порта и подсказывает про enable_lan_access.py, при
сетевой привязке — реальный адрес из hostname -I и напоминание про токен.

Добавлен --rotate: смена токена понадобилась немедленно, потому что
выданный токен был вставлен в переписку и скомпрометирован. Без флага
поведение прежнее — повторный запуск токен не трогает.

Проверено: без --rotate токен сохраняется, с --rotate меняется; bash -n на
лаунчере проходит; переводы строк LF.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-25 10:47:29 +07:00
parent 2ca42a39c4
commit e90f6331a2
2 changed files with 51 additions and 11 deletions

View file

@ -95,16 +95,48 @@ if ! check_health; then
fi fi
# 4. Headless Server Check (SSH / No Graphical Display) # 4. Headless Server Check (SSH / No Graphical Display)
#
# Текст здесь раньше был зашит: он всегда советовал пробрасывать порт и
# показывал 127.0.0.1, даже когда хаб уже привязан к сети и доступен напрямую.
# Инструкция, не совпадающая с настройками, хуже отсутствующей — владелец
# поднимал туннель к серверу, который и так был виден.
if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then
SETTINGS_FILE="$HERMES_HOME/hub_settings.json"
BIND_HOST="127.0.0.1"
if [ -f "$SETTINGS_FILE" ]; then
BIND_HOST="$("$PYTHON_BIN" - "$SETTINGS_FILE" <<'PYEOF' 2>/dev/null || echo "127.0.0.1"
import json, sys
try:
print(json.load(open(sys.argv[1], encoding="utf-8")).get("web_api_host", "127.0.0.1"))
except Exception:
print("127.0.0.1")
PYEOF
)"
fi
echo "======================================================================" echo "======================================================================"
echo " Hermes Hub Web Server is running (Headless Mode)" echo " Hermes Hub Web Server is running (no graphical display)"
echo " Web Interface URL: $TARGET_URL"
echo "" echo ""
echo " To access the interface from your local computer, forward the port:" if [ "$BIND_HOST" = "127.0.0.1" ] || [ -z "$BIND_HOST" ]; then
echo " ssh -L $PORT:127.0.0.1:$PORT user@server" echo " Listening on 127.0.0.1:$PORT — reachable from this machine only."
echo "" echo ""
echo " Then open in your browser:" echo " To open it from another computer, forward the port:"
echo " $TARGET_URL" echo " ssh -L $PORT:127.0.0.1:$PORT <user>@<this-host>"
echo " then open http://127.0.0.1:$PORT/ there."
echo ""
echo " To expose it on the local network instead:"
echo " python3 $HERMES_HOME/scripts/enable_lan_access.py"
else
LAN_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
[ -n "$LAN_IP" ] || LAN_IP="<address of this host>"
echo " Listening on $BIND_HOST:$PORT — reachable over the network."
echo ""
echo " Open from another computer:"
echo " http://$LAN_IP:$PORT/"
echo ""
echo " A token is required: paste it in Settings on first open."
echo " No port forwarding needed."
fi
echo "======================================================================" echo "======================================================================"
exit 0 exit 0
fi fi

View file

@ -2,6 +2,7 @@
python3 scripts/enable_lan_access.py # показать, что будет сделано python3 scripts/enable_lan_access.py # показать, что будет сделано
python3 scripts/enable_lan_access.py --yes # применить python3 scripts/enable_lan_access.py --yes # применить
python3 scripts/enable_lan_access.py --yes --rotate # сменить токен
Почему подтверждение обязательно. Каталог берётся из HERMES_HOME, а эта Почему подтверждение обязательно. Каталог берётся из HERMES_HOME, а эта
переменная часто задана в окружении незаметно для запускающего. При отладке переменная часто задана в окружении незаметно для запускающего. При отладке
@ -20,6 +21,7 @@ from pathlib import Path
def main() -> int: def main() -> int:
apply = "--yes" in sys.argv apply = "--yes" in sys.argv
rotate = "--rotate" in sys.argv
env_home = os.environ.get("HERMES_HOME") env_home = os.environ.get("HERMES_HOME")
home = Path(env_home) if env_home else (Path.home() / ".hermes") home = Path(env_home) if env_home else (Path.home() / ".hermes")
target = home / "hub_settings.json" target = home / "hub_settings.json"
@ -37,10 +39,16 @@ def main() -> int:
print(f"\nОТКАЗ: {target} повреждён. Разберитесь вручную — перезаписывать не буду.") print(f"\nОТКАЗ: {target} повреждён. Разберитесь вручную — перезаписывать не буду.")
return 1 return 1
existing = data.get("web_api_token") existing = None if rotate else data.get("web_api_token")
keep = sorted(k for k in data if not k.startswith("web_api")) keep = sorted(k for k in data if not k.startswith("web_api"))
print(f"Будут сохранены: {', '.join(keep) if keep else '(файл пуст)'}") print(f"Будут сохранены: {', '.join(keep) if keep else '(файл пуст)'}")
print(f"Токен : {'уже задан, оставляю прежний' if existing else 'будет создан новый'}") if rotate:
note = "БУДЕТ ЗАМЕНЁН — прежний перестанет действовать"
elif existing:
note = "уже задан, оставляю прежний"
else:
note = "будет создан новый"
print(f"Токен : {note}")
if not apply: if not apply:
print("\nПоказан предпросмотр. Чтобы применить, повторите с --yes") print("\nПоказан предпросмотр. Чтобы применить, повторите с --yes")