diff --git a/.env.example b/.env.example index b21ea405..f712e8ea 100644 --- a/.env.example +++ b/.env.example @@ -5,8 +5,8 @@ # 监听主机(默认 0.0.0.0) # APP_HOST=0.0.0.0 -# 监听端口(默认 8000) -# APP_PORT=8000 +# 监听端口(默认 15555) +# APP_PORT=15555 # Web UI 访问密钥(默认 admin123,强烈建议修改) # APP_ACCESS_PASSWORD=your_secret_password diff --git a/Dockerfile b/Dockerfile index 35f9cbb5..5748a5a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,14 @@ FROM python:3.11-slim # 设置工作目录 WORKDIR /app +ARG DEFAULT_WEBUI_PORT=15555 + # 设置环境变量 ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ # WebUI 默认配置 WEBUI_HOST=0.0.0.0 \ - WEBUI_PORT=15555 \ + WEBUI_PORT=${DEFAULT_WEBUI_PORT} \ LOG_LEVEL=info \ DEBUG=0 @@ -30,7 +32,7 @@ RUN pip install --no-cache-dir --upgrade pip \ COPY . . # 暴露端口 -EXPOSE 15555 +EXPOSE ${DEFAULT_WEBUI_PORT} # 启动 WebUI CMD ["python", "webui.py"] diff --git a/README.md b/README.md index 3956a88a..4a4f480f 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,20 @@ cp .env.example .env > 优先级:命令行参数 > 环境变量(`.env`)> 数据库设置 > 默认值 +### 修改端口 + +默认端口是 `15555`。现在已经收敛到少数几个固定入口: + +- 本地临时启动改端口:直接用 `python webui.py --port 18080` +- 本地通过 `.env` 改端口:设置 `APP_PORT=18080` +- 源码里的默认端口:修改 `src/config/constants.py` 里的 `DEFAULT_WEBUI_PORT` +- Docker Compose 默认端口:修改 `docker-compose.yml` 顶部的 `x-webui-port` +- Docker 镜像构建默认端口:修改 `Dockerfile` 里的 `ARG DEFAULT_WEBUI_PORT` + +补充说明: +- `src/config/constants.py` 的 `DEFAULT_WEBUI_PORT` 会同时影响默认 Web UI 端口、默认回调地址和 e2e 脚本默认地址。 +- `docker-compose.yml` 里已经把端口映射、容器内 `WEBUI_PORT` 和健康检查统一绑到同一个 `x-webui-port`,改一处就够。 + ### 启动 Web UI ```bash @@ -141,6 +155,18 @@ docker-compose up -d ``` 你可以在 `docker-compose.yml` 中修改相关的环境变量,例如配置端口或者设置 `WEBUI_ACCESS_PASSWORD` 访问密码。 +如果要修改 Docker Compose 对外端口,直接改文件顶部这一行即可: + +```yaml +x-webui-port: &webui-port 15555 +``` + +这一个值会同时同步到: + +- 宿主机端口映射 +- 容器内 `WEBUI_PORT` +- 健康检查访问地址 + #### 直接使用 docker run 如果你不想使用 docker-compose,也可以直接拉取并运行镜像: @@ -165,6 +191,19 @@ docker run -d \ > **注意**:`-v $(pwd)/data:/app/data` 挂载参数非常重要,它确保了你的数据库文件和账户信息在容器重启或更新后不会丢失。 +如果你要把容器端口改成 `18080`,`-p` 和 `WEBUI_PORT` 需要一起改: + +```bash +docker run -d \ + -p 18080:18080 \ + -e WEBUI_HOST=0.0.0.0 \ + -e WEBUI_PORT=18080 \ + -e WEBUI_ACCESS_PASSWORD=your_secure_password \ + -v $(pwd)/data:/app/data \ + --name codex-register \ + ghcr.io/yunxilyf/codex-register:latest +``` + ### 使用远程 PostgreSQL 通过环境变量指定数据库连接字符串: @@ -335,7 +374,7 @@ docker-compose up -d ### 配置说明 -**端口映射**:默认 `15555` 端口,可在 `docker-compose.yml` 中修改。 +**端口映射**:默认 `15555` 端口,修改 `docker-compose.yml` 顶部的 `x-webui-port` 即可。 **数据持久化**: ```yaml @@ -347,9 +386,9 @@ volumes: **环境变量配置**: ```yaml environment: - - APP_ACCESS_PASSWORD=mypassword - - APP_HOST=0.0.0.0 - - APP_PORT=15555 + WEBUI_ACCESS_PASSWORD: mypassword + WEBUI_HOST: 0.0.0.0 + WEBUI_PORT: 15555 ``` ### 常用命令 diff --git a/docker-compose.yml b/docker-compose.yml index bb7e63c6..ed13f1f4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,24 +1,26 @@ +x-webui-port: &webui-port 15555 + services: webui: build: . ports: - - "15555:15555" + - target: *webui-port + published: *webui-port + protocol: tcp environment: - - WEBUI_HOST=0.0.0.0 - - WEBUI_PORT=15555 - - WEBUI_ACCESS_PASSWORD=admin123 - - DEBUG=0 - - LOG_LEVEL=info + WEBUI_HOST: 0.0.0.0 + WEBUI_PORT: *webui-port + WEBUI_ACCESS_PASSWORD: admin123 + DEBUG: 0 + LOG_LEVEL: info volumes: # 挂载数据目录以持久化数据库和日志 - ./data:/app/data - ./logs:/app/logs healthcheck: test: - - CMD - - python - - -c - - import urllib.request; urllib.request.urlopen('http://127.0.0.1:15555/', timeout=5).read() + - CMD-SHELL + - python -c "import os, urllib.request; urllib.request.urlopen('http://127.0.0.1:' + os.environ['WEBUI_PORT'] + '/', timeout=5).read()" interval: 10s timeout: 5s retries: 5 diff --git a/src/config/constants.py b/src/config/constants.py index d0a761d0..43d60344 100644 --- a/src/config/constants.py +++ b/src/config/constants.py @@ -48,6 +48,25 @@ class EmailServiceType(str, Enum): APP_NAME = "OpenAI/Codex CLI 自动注册系统" APP_VERSION = "2.0.0" APP_DESCRIPTION = "自动注册 OpenAI/Codex CLI 账号的系统" +DEFAULT_WEBUI_HOST = "0.0.0.0" +DEFAULT_WEBUI_PORT = 15555 +DEFAULT_WEBUI_LOCAL_HOST = "127.0.0.1" + + +def build_http_url(host: str, port: int, path: str = "") -> str: + """构造本地 HTTP URL。""" + normalized_path = path if not path or path.startswith("/") else f"/{path}" + return f"http://{host}:{port}{normalized_path}" + + +def build_ws_url(host: str, port: int, path: str = "") -> str: + """构造本地 WebSocket URL。""" + normalized_path = path if not path or path.startswith("/") else f"/{path}" + return f"ws://{host}:{port}{normalized_path}" + + +DEFAULT_WEBUI_BASE_URL = build_http_url(DEFAULT_WEBUI_LOCAL_HOST, DEFAULT_WEBUI_PORT) +DEFAULT_WEBUI_WS_BASE_URL = build_ws_url(DEFAULT_WEBUI_LOCAL_HOST, DEFAULT_WEBUI_PORT) # ============================================================================ # OpenAI OAuth 相关常量 @@ -57,7 +76,7 @@ class EmailServiceType(str, Enum): OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann" OAUTH_AUTH_URL = "https://auth.openai.com/oauth/authorize" OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token" -OAUTH_REDIRECT_URI = "http://localhost:15555/auth/callback" +OAUTH_REDIRECT_URI = build_http_url("localhost", DEFAULT_WEBUI_PORT, "/auth/callback") OAUTH_SCOPE = "openid email profile offline_access" # Codex CLI 专用 OAuth 参数(用于生成 Codex 兼容的 auth.json) @@ -280,8 +299,8 @@ def generate_random_user_info() -> dict: ("registration.max_retries", "3", "最大重试次数", "registration"), ("registration.timeout", "120", "超时时间(秒)", "registration"), ("registration.default_password_length", "12", "默认密码长度", "registration"), - ("webui.host", "0.0.0.0", "Web UI 监听主机", "webui"), - ("webui.port", "15555", "Web UI 监听端口", "webui"), + ("webui.host", DEFAULT_WEBUI_HOST, "Web UI 监听主机", "webui"), + ("webui.port", str(DEFAULT_WEBUI_PORT), "Web UI 监听端口", "webui"), ("webui.debug", "true", "调试模式", "webui"), ] diff --git a/src/config/settings.py b/src/config/settings.py index 38de52b4..c201e3a6 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -10,6 +10,8 @@ from pydantic.types import SecretStr from dataclasses import dataclass +from .constants import APP_NAME, APP_VERSION, DEFAULT_WEBUI_HOST, DEFAULT_WEBUI_PORT + class SettingCategory(str, Enum): """设置分类""" @@ -42,13 +44,13 @@ class SettingDefinition: # 应用信息 "app_name": SettingDefinition( db_key="app.name", - default_value="OpenAI/Codex CLI 自动注册系统", + default_value=APP_NAME, category=SettingCategory.GENERAL, description="应用名称" ), "app_version": SettingDefinition( db_key="app.version", - default_value="2.0.0", + default_value=APP_VERSION, category=SettingCategory.GENERAL, description="应用版本" ), @@ -70,13 +72,13 @@ class SettingDefinition: # Web UI 配置 "webui_host": SettingDefinition( db_key="webui.host", - default_value="0.0.0.0", + default_value=DEFAULT_WEBUI_HOST, category=SettingCategory.WEBUI, description="Web UI 监听地址" ), "webui_port": SettingDefinition( db_key="webui.port", - default_value=15555, + default_value=DEFAULT_WEBUI_PORT, category=SettingCategory.WEBUI, description="Web UI 监听端口" ), @@ -584,8 +586,8 @@ class Settings(BaseModel): """ # 应用信息 - app_name: str = "OpenAI/Codex CLI 自动注册系统" - app_version: str = "2.0.0" + app_name: str = APP_NAME + app_version: str = APP_VERSION debug: bool = False # 数据库配置 @@ -608,8 +610,8 @@ def validate_database_url(cls, v): return v # Web UI 配置 - webui_host: str = "0.0.0.0" - webui_port: int = 15555 + webui_host: str = DEFAULT_WEBUI_HOST + webui_port: int = DEFAULT_WEBUI_PORT webui_secret_key: SecretStr = SecretStr("your-secret-key-change-in-production") webui_access_password: SecretStr = SecretStr("admin123") diff --git a/src/core/openai/payment.py b/src/core/openai/payment.py index d4c43a4b..eae5eb61 100644 --- a/src/core/openai/payment.py +++ b/src/core/openai/payment.py @@ -202,7 +202,7 @@ def generate_team_link( "&elements_session_client[is_aggregation_expected]=false" "&client_attribution_metadata[merchant_integration_additional_elements][0]=payment" "&client_attribution_metadata[merchant_integration_additional_elements][1]=address" - f"&key={data["publishable_key"]}" + f"&key={data['publishable_key']}" , proxies=_build_proxies(proxy), timeout=30, diff --git a/tests/e2e/runtime_functionality_check.py b/tests/e2e/runtime_functionality_check.py index 5a01e4c4..867b58bc 100644 --- a/tests/e2e/runtime_functionality_check.py +++ b/tests/e2e/runtime_functionality_check.py @@ -10,6 +10,8 @@ import httpx import websockets +from src.config.constants import DEFAULT_WEBUI_BASE_URL, DEFAULT_WEBUI_WS_BASE_URL + STALE_ERROR = "服务启动时检测到未完成的历史任务,已标记失败,请重新发起。" @@ -254,8 +256,8 @@ def run_verify_recovery_mode(base_url: str, db_path: Path, state_path: Path, rep def main() -> None: parser = argparse.ArgumentParser(description="真实服务功能可用性验证脚本") parser.add_argument("--mode", choices=["live", "prepare-recovery", "verify-recovery"], required=True) - parser.add_argument("--base-url", default="http://127.0.0.1:15555") - parser.add_argument("--ws-url", default="ws://127.0.0.1:15555") + parser.add_argument("--base-url", default=DEFAULT_WEBUI_BASE_URL) + parser.add_argument("--ws-url", default=DEFAULT_WEBUI_WS_BASE_URL) parser.add_argument("--db-path", required=True) parser.add_argument("--report-path", default="tests_runtime/runtime_functionality_report.json") parser.add_argument("--state-path", default="tests_runtime/runtime_recovery_state.json") diff --git a/tests/test_default_webui_port_config.py b/tests/test_default_webui_port_config.py new file mode 100644 index 00000000..13219aa4 --- /dev/null +++ b/tests/test_default_webui_port_config.py @@ -0,0 +1,19 @@ +from src.config.constants import ( + DEFAULT_SETTINGS, + DEFAULT_WEBUI_BASE_URL, + DEFAULT_WEBUI_PORT, + DEFAULT_WEBUI_WS_BASE_URL, + OAUTH_REDIRECT_URI, +) +from src.config.settings import SETTING_DEFINITIONS, Settings + + +def test_default_webui_port_is_shared_from_one_constant(): + default_settings_map = {key: value for key, value, *_ in DEFAULT_SETTINGS} + + assert SETTING_DEFINITIONS["webui_port"].default_value == DEFAULT_WEBUI_PORT + assert Settings().webui_port == DEFAULT_WEBUI_PORT + assert default_settings_map["webui.port"] == str(DEFAULT_WEBUI_PORT) + assert DEFAULT_WEBUI_BASE_URL == f"http://127.0.0.1:{DEFAULT_WEBUI_PORT}" + assert DEFAULT_WEBUI_WS_BASE_URL == f"ws://127.0.0.1:{DEFAULT_WEBUI_PORT}" + assert OAUTH_REDIRECT_URI == f"http://localhost:{DEFAULT_WEBUI_PORT}/auth/callback"