Files
MoneyPrinterTurbo/app/config/config.py
2026-06-12 14:59:20 +08:00

199 lines
6.4 KiB
Python
Raw Permalink 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.

import os
import shutil
import socket
import toml
from loguru import logger
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
config_file = f"{root_dir}/config.toml"
_CONTAINER_CGROUP_MARKERS = ("docker", "containerd", "kubepods", "libpod", "podman")
_DOCKER_HOST_GATEWAY_NAME = "host.docker.internal"
def is_running_in_container(
dockerenv_path: str = "/.dockerenv",
containerenv_path: str = "/run/.containerenv",
cgroup_path: str = "/proc/1/cgroup",
) -> bool:
"""
判断当前进程是否运行在容器内。
这个判断主要用于 Ollama 默认地址选择:
- 普通本机运行时,`localhost` 指向用户机器本身;
- Docker 容器内,`localhost` 指向容器自己,访问宿主机 Ollama
通常需要使用 `host.docker.internal`。
不能只判断 `/proc/1/cgroup` 是否存在,因为普通 Linux 也会有这个文件。
这里只在检测到明确的容器标记时返回 True避免误伤非 Docker Linux 用户。
参数保留为可注入路径,便于单元测试覆盖不同运行环境。
"""
if os.path.isfile(dockerenv_path) or os.path.isfile(containerenv_path):
return True
try:
with open(cgroup_path, mode="r", encoding="utf-8") as fp:
cgroup_content = fp.read().lower()
except OSError:
return False
return any(marker in cgroup_content for marker in _CONTAINER_CGROUP_MARKERS)
def _can_resolve_hostname(hostname: str) -> bool:
try:
socket.gethostbyname(hostname)
except OSError:
return False
return True
def _decode_linux_route_gateway(hex_gateway: str) -> str:
# /proc/net/route 里的 Gateway 是 16 进制小端序,例如 010011AC 表示
# 172.17.0.1。这里单独解析,是为了在原生 Linux Docker 没有
# host.docker.internal DNS 记录时,还能尝试访问容器默认网关上的宿主机。
if len(hex_gateway) != 8:
raise ValueError("invalid gateway length")
octets = [
str(int(hex_gateway[index : index + 2], 16))
for index in range(6, -1, -2)
]
return ".".join(octets)
def get_container_default_gateway_ip(route_path: str = "/proc/net/route") -> str:
"""
读取 Linux 容器里的默认网关 IP。
Docker Desktop 通常提供 `host.docker.internal`,但原生 Linux Docker
默认不一定提供这个 DNS 名称。默认网关通常可以作为访问宿主机服务的
兜底地址;如果用户的 Ollama 只监听 127.0.0.1,则仍需要用户让
Ollama 监听宿主机网卡或手动配置 `ollama_base_url`。
"""
try:
with open(route_path, mode="r", encoding="utf-8") as fp:
route_lines = fp.readlines()
except OSError:
return ""
for line in route_lines[1:]:
fields = line.strip().split()
if len(fields) < 3:
continue
destination = fields[1]
gateway = fields[2]
if destination != "00000000" or gateway == "00000000":
continue
try:
return _decode_linux_route_gateway(gateway)
except ValueError:
logger.warning(f"invalid container gateway route entry: {line.strip()}")
return ""
return ""
def get_default_ollama_base_url() -> str:
"""
返回 Ollama 的默认 OpenAI-compatible base_url。
用户显式配置 `ollama_base_url` 时不会走这里;这里只处理“未配置时的
最佳默认值”。容器内默认指向宿主机,普通本机运行默认指向 localhost。
"""
if not is_running_in_container():
return "http://localhost:11434/v1"
if _can_resolve_hostname(_DOCKER_HOST_GATEWAY_NAME):
return f"http://{_DOCKER_HOST_GATEWAY_NAME}:11434/v1"
gateway_ip = get_container_default_gateway_ip()
if gateway_ip:
logger.info(
"host.docker.internal is not resolvable, fallback to container "
f"default gateway for Ollama: {gateway_ip}"
)
return f"http://{gateway_ip}:11434/v1"
logger.warning(
"failed to resolve host.docker.internal and container default gateway; "
"fallback to host.docker.internal for Ollama"
)
return f"http://{_DOCKER_HOST_GATEWAY_NAME}:11434/v1"
def load_config():
# fix: IsADirectoryError: [Errno 21] Is a directory: '/MoneyPrinterTurbo/config.toml'
if os.path.isdir(config_file):
shutil.rmtree(config_file)
if not os.path.isfile(config_file):
example_file = f"{root_dir}/config.example.toml"
if os.path.isfile(example_file):
shutil.copyfile(example_file, config_file)
logger.info("copy config.example.toml to config.toml")
logger.info(f"load config from file: {config_file}")
try:
_config_ = toml.load(config_file)
except Exception as e:
logger.warning(f"load config failed: {str(e)}, try to load as utf-8-sig")
with open(config_file, mode="r", encoding="utf-8-sig") as fp:
_cfg_content = fp.read()
_config_ = toml.loads(_cfg_content)
return _config_
def save_config():
with open(config_file, "w", encoding="utf-8") as f:
_cfg["app"] = app
_cfg["azure"] = azure
_cfg["siliconflow"] = siliconflow
_cfg["ui"] = ui
f.write(toml.dumps(_cfg))
_cfg = load_config()
app = _cfg.get("app", {})
whisper = _cfg.get("whisper", {})
proxy = _cfg.get("proxy", {})
azure = _cfg.get("azure", {})
siliconflow = _cfg.get("siliconflow", {})
ui = _cfg.get(
"ui",
{
"hide_log": False,
},
)
hostname = socket.gethostname()
log_level = _cfg.get("log_level", "DEBUG")
listen_host = _cfg.get("listen_host", "0.0.0.0")
listen_port = _cfg.get("listen_port", 8099)
project_name = _cfg.get("project_name", "MoneyPrinterTurbo")
project_description = _cfg.get(
"project_description",
"<a href='https://github.com/harry0703/MoneyPrinterTurbo'>https://github.com/harry0703/MoneyPrinterTurbo</a>",
)
project_version = _cfg.get("project_version", "1.2.9")
reload_debug = False
app["redis_host"] = os.getenv(
"MPT_APP_REDIS_HOST",
os.getenv("REDIS_HOST", app.get("redis_host", "localhost")),
)
imagemagick_path = app.get("imagemagick_path", "")
if imagemagick_path and os.path.isfile(imagemagick_path):
os.environ["IMAGEMAGICK_BINARY"] = imagemagick_path
ffmpeg_path = app.get("ffmpeg_path", "")
if ffmpeg_path and os.path.isfile(ffmpeg_path):
os.environ["IMAGEIO_FFMPEG_EXE"] = ffmpeg_path
logger.info(f"{project_name} v{project_version}")