Caddy ругался на два места. header_up X-Forwarded-For был лишним: Caddy сам игнорирует этот заголовок из входящего запроса и подставляет реальный адрес, защита от подделки обеспечивается его поведением по умолчанию. Пустая строка перед глобальным блоком делала файл неформатированным по мнению caddy fmt. Проверено caddy fmt и caddy validate на сгенерированном файле. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
133 lines
5.5 KiB
Python
133 lines
5.5 KiB
Python
"""Запуск сервиса: обновление, 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())
|