Логика запуска и Caddyfile переехали внутрь app

Файлы вне папки app обновление не доставляло: чтобы изменить порядок
запуска или конфигурацию Caddy, требовалась переустановка. Теперь .bat -
заглушка из трёх строк, вызывающая app/launcher.py, а Caddyfile создаётся
из настроек [https] в config.toml. Отредактированный вручную Caddyfile
не затирается: новая версия откладывается рядом как Caddyfile.new.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vladimir Bryzgalov
2026-08-16 17:23:47 +05:00
co-authored by Claude Opus 5
parent cd3acafa8a
commit febec7f124
5 changed files with 251 additions and 47 deletions
+131
View File
@@ -0,0 +1,131 @@
"""Запуск сервиса: обновление, 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())
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} {{
\treverse_proxy 127.0.0.1:{port} {{
\t\t# Реальный адрес клиента - иначе список разрешённых адресов
\t\t# увидит вместо него localhost и пустит кого угодно.
\t\theader_up X-Forwarded-For {{remote_host}}
\t}}
\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())