Files
talkscore-asr/app/launcher.py
T
Vladimir BryzgalovandClaude Opus 5 cd02fabb2e Caddyfile без предупреждений при запуске
Caddy ругался на два места. header_up X-Forwarded-For был лишним: Caddy сам
игнорирует этот заголовок из входящего запроса и подставляет реальный адрес,
защита от подделки обеспечивается его поведением по умолчанию. Пустая строка
перед глобальным блоком делала файл неформатированным по мнению caddy fmt.

Проверено caddy fmt и caddy validate на сгенерированном файле.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 19:00:49 +05:00

133 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Запуск сервиса: обновление, Caddyfile, HTTPS.
Раньше вся эта последовательность жила в .bat-файлах, а Caddyfile правился
руками. Оба файла лежат вне папки app, поэтому обновление их не доставляло:
чтобы получить новый порядок запуска, приходилось переустанавливать сервис.
Теперь .bat - это трёхстрочная заглушка, которая просто зовёт этот модуль,
а Caddyfile создаётся здесь из настроек. Меняется только код внутри app,
который обновление возит штатно.
"""
import subprocess
import sys
from pathlib import Path
__all__ = ["render_caddyfile", "ensure_caddyfile", "main"]
# По этой строке отличаем свой файл от отредактированного вручную.
MARK = "# Создан автоматически talkscore-asr. Правки будут перезаписаны."
CADDY_WAIT_SEC = 3
def render_caddyfile(domains: str, port: int, max_upload_mb: int) -> str:
"""Собирает конфигурацию Caddy из настроек сервиса."""
listed = ", ".join(d.strip() for d in domains.split(",") if d.strip())
# Пустой строки перед блоком быть не должно: caddy fmt считает это
# неформатированным файлом и ругается при каждом запуске.
return f"""{MARK}
# Домены и предел размера берутся из config.toml, менять нужно там.
{{
\t# Caddy очень подробно рассказывает про сертификаты. В консоли из-за этого
\t# не видно сообщений сервиса, поэтому его вывод уходит в файл.
\tlog {{
\t\toutput file logs/caddy.log {{
\t\t\troll_size 10MiB
\t\t\troll_keep 5
\t\t}}
\t\tformat console
\t\tlevel INFO
\t}}
}}
{listed} {{
\t# X-Forwarded-For здесь намеренно не задаётся: Caddy сам игнорирует этот
\t# заголовок из входящего запроса и подставляет реальный адрес клиента.
\t# Не добавляйте сюда trusted_proxies - тогда Caddy начнёт верить заголовку
\t# от клиента, и любой сможет назваться разрешённым адресом.
\treverse_proxy 127.0.0.1:{port}
\trequest_body {{
\t\tmax_size {max_upload_mb}MB
\t}}
}}
"""
def ensure_caddyfile(base_dir: Path, content: str) -> Path | None:
"""Записывает Caddyfile, не затирая ручные правки.
Если файл создан не нами, откладываем новую версию рядом: молча стереть
чужую настройку хуже, чем оставить её устаревшей.
"""
target = base_dir / "Caddyfile"
if target.exists():
existing = target.read_text(encoding="utf-8", errors="replace")
if MARK not in existing:
spare = base_dir / "Caddyfile.new"
if existing.strip() != content.strip():
spare.write_text(content, encoding="utf-8")
return spare
return None
if existing == content:
return None
target.write_text(content, encoding="utf-8")
return target
def start_caddy(base_dir: Path) -> subprocess.Popen | None:
"""Поднимает Caddy отдельным процессом. Его отсутствие не должно ронять запуск."""
binary = base_dir / "bin" / "caddy.exe"
if not binary.is_file():
binary = base_dir / "bin" / "caddy"
if not binary.is_file():
print("\n Caddy не найден в папке bin - запускаюсь без HTTPS.\n")
return None
(base_dir / "logs").mkdir(exist_ok=True)
return subprocess.Popen(
[str(binary), "run", "--config", str(base_dir / "Caddyfile")],
cwd=str(base_dir))
def main(argv: list[str] | None = None) -> int:
argv = sys.argv[1:] if argv is None else argv
https = "https" in argv
from app import updater
from app.config import ConfigError, load_settings
try:
settings = load_settings()
except ConfigError as exc:
print(f"\n {exc}\n")
return 1
updater.main()
caddy = None
if https:
if not settings.https_domains.strip():
print("\n В config.toml не заданы домены: раздел [https], ключ domains."
"\n Без них сертификат выпустить не на что.\n")
return 1
written = ensure_caddyfile(
settings.base_dir,
render_caddyfile(settings.https_domains, settings.port,
settings.max_upload_mb))
if written is not None and written.name.endswith(".new"):
print(f"\n Caddyfile правили вручную, поэтому не трогаю его."
f"\n Новая версия отложена рядом: {written}\n")
caddy = start_caddy(settings.base_dir)
try:
from app.main import run
run()
finally:
if caddy is not None:
caddy.terminate()
return 0
if __name__ == "__main__":
sys.exit(main())