feat(web): скрипт открытия хаба в домашнюю сеть, с подтверждением цели

Владелец хочет попадать в хаб на сервере по сети, а не через SSH-туннель.
Для этого нужны привязка к 0.0.0.0 и токен: без токена при небlocalhost
привязке сервер отказывается стартовать.

Скрипт ДОПОЛНЯЕТ hub_settings.json, а не переписывает: рядом лежат тема,
интервал обновления квот и параметры маршрутизации. Запись атомарная,
повторный запуск сохраняет прежний токен.

Изменение требует явного --yes, а каталог печатается всегда. Причина не
теоретическая: при отладке скрипт молча взял HERMES_HOME из окружения и
записал настройки не в тестовую копию, а в живой хаб рабочей машины,
привязав его к сети. Откачено, наружу ничего не вышло — адрес читается при
старте, процесс не перезапускался, netstat подтвердил только 127.0.0.1.
Предпросмотр по умолчанию закрывает этот класс ошибки.

Попутно install-linux.sh разворачивает scripts/: они входили в поставку, но
на установленной машине не оказывались, поэтому вспомогательных инструментов
там просто не было.

Проверено на копии настроек: предпросмотр не меняет файл, применение
сохраняет прежние ключи, повторный запуск не меняет токен, посторонний
каталог не затрагивается.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hermes Team 2026-08-25 10:27:16 +07:00
parent 11aa914fbb
commit 44808bdc4e
2 changed files with 76 additions and 0 deletions

View file

@ -96,6 +96,15 @@ if [ -d "$REPO_ROOT/assets" ]; then
cp -r "$REPO_ROOT/assets/"* "$HERMES_HOME/plugins/antigravity-provider/assets/" 2>/dev/null || true cp -r "$REPO_ROOT/assets/"* "$HERMES_HOME/plugins/antigravity-provider/assets/" 2>/dev/null || true
fi fi
# Deploy helper scripts. Они лежат в поставке, но раньше не разворачивались,
# поэтому вспомогательных инструментов (например открытия хаба в домашнюю сеть)
# на установленной машине просто не оказывалось.
if [ -d "$REPO_ROOT/scripts" ]; then
mkdir -p "$HERMES_HOME/scripts"
cp -r "$REPO_ROOT/scripts/"*.py "$HERMES_HOME/scripts/" 2>/dev/null || true
cp -r "$REPO_ROOT/scripts/"*.sh "$HERMES_HOME/scripts/" 2>/dev/null || true
fi
# 4. Write Deployment Manifest # 4. Write Deployment Manifest
echo "[4/6] Writing deployment manifest..." echo "[4/6] Writing deployment manifest..."
MANIFEST_FILE="$HERMES_HOME/plugins/antigravity-provider/deployment_manifest.json" MANIFEST_FILE="$HERMES_HOME/plugins/antigravity-provider/deployment_manifest.json"

View file

@ -0,0 +1,67 @@
"""Открыть веб-хаб в домашнюю сеть: привязка к 0.0.0.0 плюс обязательный токен.
python3 scripts/enable_lan_access.py # показать, что будет сделано
python3 scripts/enable_lan_access.py --yes # применить
Почему подтверждение обязательно. Каталог берётся из HERMES_HOME, а эта
переменная часто задана в окружении незаметно для запускающего. При отладке
скрипт из-за этого записал настройки не туда, куда предполагалось, и привязал
к сети не тот хаб. Поэтому цель печатается всегда, а изменение требует --yes.
Настройки ДОПОЛНЯЮТСЯ, а не переписываются: рядом лежат тема, интервал
обновления квот и параметры маршрутизации. Запись атомарная.
"""
import json
import os
import secrets
import sys
from pathlib import Path
def main() -> int:
apply = "--yes" in sys.argv
env_home = os.environ.get("HERMES_HOME")
home = Path(env_home) if env_home else (Path.home() / ".hermes")
target = home / "hub_settings.json"
print(f"Каталог Hermes : {home}")
print(f" источник : {'переменная HERMES_HOME' if env_home else 'по умолчанию ~/.hermes'}")
print(f"Файл настроек : {target}")
print(f" существует : {'да' if target.exists() else 'нет, будет создан'}")
data = {}
if target.exists():
try:
data = json.loads(target.read_text(encoding="utf-8"))
except ValueError:
print(f"\nОТКАЗ: {target} повреждён. Разберитесь вручную — перезаписывать не буду.")
return 1
existing = data.get("web_api_token")
keep = sorted(k for k in data if not k.startswith("web_api"))
print(f"Будут сохранены: {', '.join(keep) if keep else '(файл пуст)'}")
print(f"Токен : {'уже задан, оставляю прежний' if existing else 'будет создан новый'}")
if not apply:
print("\nПоказан предпросмотр. Чтобы применить, повторите с --yes")
return 0
token = existing or secrets.token_urlsafe(32)
data["web_api_host"] = "0.0.0.0"
data["web_api_token"] = token
data.setdefault("web_api_port", 5800)
tmp = target.with_suffix(".json.tmp")
tmp.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
os.replace(tmp, target)
print(f"\nГотово: {target}")
print("\nТОКЕН — вставить в «Настройки» в интерфейсе:")
print(f" {token}")
print("\nДальше: перезапустить хаб, затем открыть http://<адрес-сервера>:5800")
print("Токен хранится в этом файле; в снапшот и в /api/settings он не попадает.")
return 0
if __name__ == "__main__":
raise SystemExit(main())