Инсталлятор: убрана кириллица, ломавшая его на запуске

cmd.exe читает .cmd в системной кодировке, поэтому русские комментарии rem
превращались в мусор, и cmd пытался выполнять обрывки строк как команды.
Правило «в батниках только ASCII» уже было записано в шапке файла — и всё
равно нарушено, поэтому теперь его проверяют тесты: любой .cmd проекта
обязан быть чистым ASCII.

Ещё две правки по итогам установки с нуля в чистой папке:
- запись .env вынесена из блока if/else в метки: cmd неверно разбирает блок,
  внутри которого есть rem-строки и вызов PowerShell с переносами через ^,
  и сообщает только «The syntax of the command is incorrect»;
- запуск приложения в конце идёт по полному пути, а не по имени start.cmd —
  относительное имя разрешается не во всех оболочках.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 16:58:10 +03:00
co-authored by Claude Opus 5
parent 9cbc7f02ac
commit 5926967270
2 changed files with 43 additions and 21 deletions
+24 -16
View File
@@ -59,8 +59,8 @@ goto :setup
:download_zip :download_zip
echo [2/4] Downloading archive (git not available or clone failed)... echo [2/4] Downloading archive (git not available or clone failed)...
rem PowerShell does the work: no curl/tar dependency on older Windows. rem PowerShell does the work: no curl/tar dependency on older Windows.
rem Проверяем, что скачался именно архив: сервер, требующий входа, отдаёт rem Make sure we actually got an archive: a server that requires signing in
rem HTML-страницу логина с кодом 200, и распаковка падала бы невнятно. rem answers with an HTML login page, and unpacking would fail confusingly.
powershell -NoProfile -ExecutionPolicy Bypass -Command ^ powershell -NoProfile -ExecutionPolicy Bypass -Command ^
"$ErrorActionPreference='Stop';" ^ "$ErrorActionPreference='Stop';" ^
"$url='%REPO_WEB%/archive/%BRANCH%.zip';" ^ "$url='%REPO_WEB%/archive/%BRANCH%.zip';" ^
@@ -139,26 +139,32 @@ set OLLAMA_ADDR=
set /p OLLAMA_ADDR="Ollama URL [http://localhost:11434]: " set /p OLLAMA_ADDR="Ollama URL [http://localhost:11434]: "
if "%OLLAMA_ADDR%"=="" set OLLAMA_ADDR=http://localhost:11434 if "%OLLAMA_ADDR%"=="" set OLLAMA_ADDR=http://localhost:11434
rem Локальный конфиг делаем из образца: рядом с адресом Ollama человек увидит rem Build the local config from the example file, so the user sees the other
rem и остальные настройки с пояснениями, а не одну голую строку. rem settings with explanations next to the Ollama address.
if exist ".env" ( rem
echo .env already exists - left as is. Edit it by hand if needed. rem Done with labels, not an if/else block: cmd mis-parses a block that holds
) else ( rem rem-lines and a caret-continued PowerShell call, and reports only
rem -Encoding UTF8 обязателен: без него PowerShell 5.1 читает файл в rem "The syntax of the command is incorrect".
rem системной кодировке, и русские пояснения превращаются в кракозябры. if exist ".env" goto :env_exists
rem -Encoding UTF8 is required: without it PowerShell 5.1 reads the file in the
rem system codepage and the Russian comments turn into mojibake.
powershell -NoProfile -Command ^ powershell -NoProfile -Command ^
"$src = if (Test-Path '.env.example') { Get-Content '.env.example' -Encoding UTF8 } else { @('OLLAMA_HOST=') };" ^ "$src = if (Test-Path '.env.example') { Get-Content '.env.example' -Encoding UTF8 } else { @('OLLAMA_HOST=') };" ^
"$out = $src -replace '^\s*#?\s*OLLAMA_HOST=.*', 'OLLAMA_HOST=%OLLAMA_ADDR%';" ^ "$out = $src -replace '^\s*#?\s*OLLAMA_HOST=.*', 'OLLAMA_HOST=%OLLAMA_ADDR%';" ^
"if (-not ($out -match '^OLLAMA_HOST=')) { $out += 'OLLAMA_HOST=%OLLAMA_ADDR%' }" ^ "if (-not ($out -match '^OLLAMA_HOST=')) { $out += 'OLLAMA_HOST=%OLLAMA_ADDR%' }" ^
"Set-Content -Path '.env' -Value $out -Encoding UTF8" "Set-Content -Path '.env' -Value $out -Encoding UTF8"
if errorlevel 1 ( if not exist ".env" echo OLLAMA_HOST=%OLLAMA_ADDR%>.env
> .env echo OLLAMA_HOST=%OLLAMA_ADDR% echo Saved to .env: OLLAMA_HOST=%OLLAMA_ADDR%
) goto :env_done
echo Saved to .env ^(OLLAMA_HOST=%OLLAMA_ADDR%^)
) :env_exists
echo .env already exists - left as is. Edit it by hand if needed.
:env_done
rem Check whether Ollama actually answers, and report in plain language. rem Check whether Ollama actually answers, and report in plain language.
venv\Scripts\python.exe -c "from hlamingo import ollama, config; ok=ollama.check(); print((' Ollama ' + config.OLLAMA_HOST + (' отвечает.' if ok else ' НЕ отвечает — приложение будет работать, но без разбора фраз и поиска по смыслу.')))" venv\Scripts\python.exe -c "from hlamingo import ollama, config; print(' Ollama ' + config.OLLAMA_HOST + (' otvechaet.' if ollama.check() else ' NE otvechaet - prilozhenie zapustitsya, no bez razbora fraz.'))"
echo. echo.
echo ======================================== echo ========================================
@@ -181,8 +187,10 @@ echo.
set RUNNOW= set RUNNOW=
set /p RUNNOW="Start it now? [Y/n]: " set /p RUNNOW="Start it now? [Y/n]: "
if /i "%RUNNOW%"=="n" goto :end if /i "%RUNNOW%"=="n" goto :end
rem Full path, not a bare name: depending on the shell that launched the
rem installer, a relative .cmd name may not resolve.
pushd "%TARGET%" pushd "%TARGET%"
call start.cmd call "%CD%\start.cmd"
popd popd
:end :end
+14
View File
@@ -177,6 +177,20 @@ config.DB_FILE.write_text("{ битый json", encoding="utf-8")
r.check("битая база не роняет", c.get("/api/tree").status_code == 200) r.check("битая база не роняет", c.get("/api/tree").status_code == 200)
r.check("пустой ввод не роняет", "reply" in j("POST", "/api/say", text=" ")) r.check("пустой ввод не роняет", "reply" in j("POST", "/api/say", text=" "))
print("\n== Батники: только ASCII ==")
# cmd.exe читает .cmd в системной кодировке. Кириллица внутри — даже
# в комментарии rem — превращается в мусор, и cmd пытается выполнить обрывки
# как команды. На этом уже дважды ломался инсталлятор.
import pathlib # noqa: E402
root = pathlib.Path(__file__).resolve().parent.parent
for cmd_file in sorted(root.rglob("*.cmd")):
if "venv" in cmd_file.parts:
continue
lines = cmd_file.read_text(encoding="utf-8").splitlines()
bad = [(n, l) for n, l in enumerate(lines, 1) if any(ord(ch) > 127 for ch in l)]
r.check("%s без не-ASCII" % cmd_file.relative_to(root), not bad,
bad[0] if bad else "")
print("\n== Настройки из браузера ==") print("\n== Настройки из браузера ==")
d = c.get("/api/config").get_json() d = c.get("/api/config").get_json()
r.check("отдаёт текущие", d.get("ollama_host") and d.get("chat_model"), d) r.check("отдаёт текущие", d.get("ollama_host") and d.get("chat_model"), d)