netlops commited on
Commit
c21f770
·
1 Parent(s): 35d960d

feat: add auto-detection for headless environments and auto-switch captcha modes

Browse files

- Add helper function to detect headless/Docker environments in main.py
- Implement auto-downgrade from personal to browser captcha mode in headless environments
- Show warning messages when switching modes automatically
- Update FlowClient to accept database instance for captcha configuration

config: update default server port and enable proxy by default

- Change default server port from 8000 to 18282 in config/setting.toml
- Enable proxy by default with localhost:7897 URL
- Update Docker and docker-compose configurations to use port 38000 for API

build: add China mirror sources for faster dependency installation

- Configure Debian apt to use Tsinghua University mirrors
- Set PyPI to use Tsinghua University index with trusted host
- Configure Playwright to download from npmmirror for faster installation

chore: update gitignore to exclude data and config files

- Add data directory to .gitignore
- Exclude config/setting.toml and config/setting_warp.toml from version control

docs: update docker-compose configurations for new port mappings

- Change exposed port from 8000 to 38000 in docker-compose.yml
- Update proxy service port from 1080 to 31080 in docker-compose.proxy.yml
- Ensure proper volume mapping for Cloudflare WARP data persistence

.gitignore CHANGED
@@ -54,3 +54,7 @@ logs.txt
54
  *.cache
55
 
56
  browser_data
 
 
 
 
 
54
  *.cache
55
 
56
  browser_data
57
+
58
+ data
59
+ config/setting.toml
60
+ config/setting_warp.toml
Dockerfile CHANGED
@@ -2,6 +2,10 @@ FROM python:3.11-slim
2
 
3
  WORKDIR /app
4
 
 
 
 
 
5
  # 安装 Playwright 所需的系统依赖
6
  RUN apt-get update && apt-get install -y \
7
  libnss3 \
@@ -21,9 +25,14 @@ RUN apt-get update && apt-get install -y \
21
  libcairo2 \
22
  && rm -rf /var/lib/apt/lists/*
23
 
24
- # 安装 Python 依赖
25
  COPY requirements.txt .
26
- RUN pip install --no-cache-dir -r requirements.txt
 
 
 
 
 
27
 
28
  # 安装 Playwright 浏览器
29
  RUN playwright install chromium
 
2
 
3
  WORKDIR /app
4
 
5
+ # 使用清华镜像源加速 apt (Debian bookworm)
6
+ RUN sed -i 's|deb.debian.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/debian.sources \
7
+ && sed -i 's|security.debian.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list.d/debian.sources
8
+
9
  # 安装 Playwright 所需的系统依赖
10
  RUN apt-get update && apt-get install -y \
11
  libnss3 \
 
25
  libcairo2 \
26
  && rm -rf /var/lib/apt/lists/*
27
 
28
+ # 安装 Python 依赖(使用清华 PyPI 镜像)
29
  COPY requirements.txt .
30
+ RUN pip install --no-cache-dir -r requirements.txt \
31
+ -i https://pypi.tuna.tsinghua.edu.cn/simple/ \
32
+ --trusted-host pypi.tuna.tsinghua.edu.cn
33
+
34
+ # 设置 Playwright 下载镜像(使用 npmmirror)
35
+ ENV PLAYWRIGHT_DOWNLOAD_HOST=https://registry.npmmirror.com/-/binary/playwright
36
 
37
  # 安装 Playwright 浏览器
38
  RUN playwright install chromium
config/setting.toml CHANGED
@@ -12,7 +12,7 @@ max_poll_attempts = 200
12
 
13
  [server]
14
  host = "0.0.0.0"
15
- port = 8000
16
 
17
  [debug]
18
  enabled = false
@@ -21,8 +21,8 @@ log_responses = true
21
  mask_token = true
22
 
23
  [proxy]
24
- proxy_enabled = false
25
- proxy_url = ""
26
 
27
  [generation]
28
  image_timeout = 300
 
12
 
13
  [server]
14
  host = "0.0.0.0"
15
+ port = 18282
16
 
17
  [debug]
18
  enabled = false
 
21
  mask_token = true
22
 
23
  [proxy]
24
+ proxy_enabled = true
25
+ proxy_url = "http://localhost:7897"
26
 
27
  [generation]
28
  image_timeout = 300
config/setting_example.toml ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [global]
2
+ api_key = "han1234"
3
+ admin_username = "admin"
4
+ admin_password = "admin"
5
+
6
+ [flow]
7
+ labs_base_url = "https://labs.google/fx/api"
8
+ api_base_url = "https://aisandbox-pa.googleapis.com/v1"
9
+ timeout = 120
10
+ poll_interval = 3.0
11
+ max_poll_attempts = 200
12
+
13
+ [server]
14
+ host = "0.0.0.0"
15
+ port = 18282
16
+
17
+ [debug]
18
+ enabled = false
19
+ log_requests = true
20
+ log_responses = true
21
+ mask_token = true
22
+
23
+ [proxy]
24
+ proxy_enabled = true
25
+ proxy_url = "http://localhost:7897"
26
+
27
+ [generation]
28
+ image_timeout = 300
29
+ video_timeout = 1500
30
+
31
+ [admin]
32
+ error_ban_threshold = 3
33
+
34
+ [cache]
35
+ enabled = false
36
+ timeout = 7200 # 缓存超时时间(秒), 默认2小时
37
+ base_url = "" # 缓存文件访问的基础URL, 留空则使用服务器地址
38
+
39
+ [captcha]
40
+ captcha_method = "browser" # 打码方式: yescaptcha 或 browser
41
+ yescaptcha_api_key = "" # YesCaptcha API密钥
42
+ yescaptcha_base_url = "https://api.yescaptcha.com"
config/setting_warp_example.toml ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [global]
2
+ api_key = "han1234"
3
+ admin_username = "admin"
4
+ admin_password = "admin"
5
+
6
+ [flow]
7
+ labs_base_url = "https://labs.google/fx/api"
8
+ api_base_url = "https://aisandbox-pa.googleapis.com/v1"
9
+ timeout = 120
10
+ poll_interval = 3.0
11
+ max_poll_attempts = 200
12
+
13
+ [server]
14
+ host = "0.0.0.0"
15
+ port = 8000
16
+
17
+ [debug]
18
+ enabled = false
19
+ log_requests = true
20
+ log_responses = true
21
+ mask_token = true
22
+
23
+ [proxy]
24
+ proxy_enabled = true
25
+ proxy_url = "socks5://warp:1080"
26
+
27
+ [generation]
28
+ image_timeout = 300
29
+ video_timeout = 1500
30
+
31
+ [admin]
32
+ error_ban_threshold = 3
33
+
34
+ [cache]
35
+ enabled = false
36
+ timeout = 7200 # 缓存超时时间(秒), 默认2小时
37
+ base_url = "" # 缓存文件访问的基础URL, 留空则使用服务器地址
38
+
39
+ [captcha]
40
+ captcha_method = "browser" # 打码方式: yescaptcha 或 browser
41
+ yescaptcha_api_key = "" # YesCaptcha API密钥
42
+ yescaptcha_base_url = "https://api.yescaptcha.com"
docker-compose.local.yml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ flow2api:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ image: flow2api:local
9
+ container_name: flow2api
10
+ ports:
11
+ - "38000:8000"
12
+ volumes:
13
+ - ./data:/app/data
14
+ - ./config/setting.toml:/app/config/setting.toml
15
+ environment:
16
+ - PYTHONUNBUFFERED=1
17
+ restart: unless-stopped
docker-compose.proxy.yml CHANGED
@@ -5,7 +5,7 @@ services:
5
  image: thesmallhancat/flow2api:latest
6
  container_name: flow2api
7
  ports:
8
- - "8000:8000"
9
  volumes:
10
  - ./data:/app/data
11
  - ./config/setting_warp.toml:/app/config/setting.toml
@@ -22,7 +22,7 @@ services:
22
  devices:
23
  - /dev/net/tun:/dev/net/tun
24
  ports:
25
- - "1080:1080"
26
  environment:
27
  - WARP_SLEEP=2
28
  cap_add:
@@ -33,4 +33,4 @@ services:
33
  - net.ipv6.conf.all.disable_ipv6=0
34
  - net.ipv4.conf.all.src_valid_mark=1
35
  volumes:
36
- - ./data:/var/lib/cloudflare-warp
 
5
  image: thesmallhancat/flow2api:latest
6
  container_name: flow2api
7
  ports:
8
+ - "38000:8000"
9
  volumes:
10
  - ./data:/app/data
11
  - ./config/setting_warp.toml:/app/config/setting.toml
 
22
  devices:
23
  - /dev/net/tun:/dev/net/tun
24
  ports:
25
+ - "31080:1080"
26
  environment:
27
  - WARP_SLEEP=2
28
  cap_add:
 
33
  - net.ipv6.conf.all.disable_ipv6=0
34
  - net.ipv4.conf.all.src_valid_mark=1
35
  volumes:
36
+ - ./data:/var/lib/cloudflare-warp
docker-compose.yml CHANGED
@@ -5,7 +5,7 @@ services:
5
  image: thesmallhancat/flow2api:latest
6
  container_name: flow2api
7
  ports:
8
- - "8000:8000"
9
  volumes:
10
  - ./data:/app/data
11
  - ./config/setting.toml:/app/config/setting.toml
 
5
  image: thesmallhancat/flow2api:latest
6
  container_name: flow2api
7
  ports:
8
+ - "38000:8000"
9
  volumes:
10
  - ./data:/app/data
11
  - ./config/setting.toml:/app/config/setting.toml
src/main.py CHANGED
@@ -68,18 +68,46 @@ async def lifespan(app: FastAPI):
68
 
69
  # Load captcha configuration from database
70
  captcha_config = await db.get_captcha_config()
71
- config.set_captcha_method(captcha_config.captcha_method)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  config.set_yescaptcha_api_key(captcha_config.yescaptcha_api_key)
73
  config.set_yescaptcha_base_url(captcha_config.yescaptcha_base_url)
74
 
75
  # Initialize browser captcha service if needed
76
  browser_service = None
77
- if captcha_config.captcha_method == "personal":
78
  from .services.browser_captcha_personal import BrowserCaptchaService
79
  browser_service = await BrowserCaptchaService.get_instance(db)
80
  await browser_service.open_login_window()
81
  print("✓ Browser captcha service initialized (webui mode)")
82
- elif captcha_config.captcha_method == "browser":
83
  from .services.browser_captcha import BrowserCaptchaService
84
  browser_service = await BrowserCaptchaService.get_instance(db)
85
  print("✓ Browser captcha service initialized (headless mode)")
@@ -135,7 +163,7 @@ async def lifespan(app: FastAPI):
135
  # Initialize components
136
  db = Database()
137
  proxy_manager = ProxyManager(db)
138
- flow_client = FlowClient(proxy_manager)
139
  token_manager = TokenManager(db, flow_client)
140
  concurrency_manager = ConcurrencyManager()
141
  load_balancer = LoadBalancer(token_manager, concurrency_manager)
 
68
 
69
  # Load captcha configuration from database
70
  captcha_config = await db.get_captcha_config()
71
+
72
+ # Helper function to detect headless/Docker environment
73
+ def is_headless_environment() -> bool:
74
+ """Check if running in a headless environment (Docker, no display, etc.)"""
75
+ import os
76
+ # Check for DISPLAY environment variable (X11)
77
+ if not os.environ.get("DISPLAY"):
78
+ # Check if running in Docker
79
+ if os.path.exists("/.dockerenv") or os.environ.get("DOCKER_CONTAINER"):
80
+ return True
81
+ # Check for common CI/container indicators
82
+ if os.environ.get("CI") or os.environ.get("KUBERNETES_SERVICE_HOST"):
83
+ return True
84
+ # No DISPLAY and not explicitly local
85
+ return True
86
+ return False
87
+
88
+ # Determine effective captcha method
89
+ effective_captcha_method = captcha_config.captcha_method
90
+
91
+ # Auto-downgrade personal mode to browser mode in headless environments
92
+ if captcha_config.captcha_method == "personal" and is_headless_environment():
93
+ print("⚠️ WARNING: 'personal' captcha mode requires a display (X Server).")
94
+ print(" Detected headless environment (Docker/No Display).")
95
+ print(" Auto-switching to 'browser' (headless) mode.")
96
+ print(" To use 'personal' mode, run Flow2API on a machine with a display.")
97
+ effective_captcha_method = "browser"
98
+
99
+ config.set_captcha_method(effective_captcha_method)
100
  config.set_yescaptcha_api_key(captcha_config.yescaptcha_api_key)
101
  config.set_yescaptcha_base_url(captcha_config.yescaptcha_base_url)
102
 
103
  # Initialize browser captcha service if needed
104
  browser_service = None
105
+ if effective_captcha_method == "personal":
106
  from .services.browser_captcha_personal import BrowserCaptchaService
107
  browser_service = await BrowserCaptchaService.get_instance(db)
108
  await browser_service.open_login_window()
109
  print("✓ Browser captcha service initialized (webui mode)")
110
+ elif effective_captcha_method == "browser":
111
  from .services.browser_captcha import BrowserCaptchaService
112
  browser_service = await BrowserCaptchaService.get_instance(db)
113
  print("✓ Browser captcha service initialized (headless mode)")
 
163
  # Initialize components
164
  db = Database()
165
  proxy_manager = ProxyManager(db)
166
+ flow_client = FlowClient(proxy_manager, db)
167
  token_manager = TokenManager(db, flow_client)
168
  concurrency_manager = ConcurrencyManager()
169
  load_balancer = LoadBalancer(token_manager, concurrency_manager)
src/services/flow_client.py CHANGED
@@ -12,8 +12,9 @@ from ..core.config import config
12
  class FlowClient:
13
  """VideoFX API客户端"""
14
 
15
- def __init__(self, proxy_manager):
16
  self.proxy_manager = proxy_manager
 
17
  self.labs_base_url = config.flow_labs_base_url # https://labs.google/fx/api
18
  self.api_base_url = config.flow_api_base_url # https://aisandbox-pa.googleapis.com/v1
19
  self.timeout = config.flow_timeout
@@ -691,7 +692,7 @@ class FlowClient:
691
  if captcha_method == "personal":
692
  try:
693
  from .browser_captcha_personal import BrowserCaptchaService
694
- service = await BrowserCaptchaService.get_instance(self.proxy_manager)
695
  return await service.get_token(project_id)
696
  except Exception as e:
697
  debug_logger.log_error(f"[reCAPTCHA Browser] error: {str(e)}")
@@ -700,7 +701,7 @@ class FlowClient:
700
  elif captcha_method == "browser":
701
  try:
702
  from .browser_captcha import BrowserCaptchaService
703
- service = await BrowserCaptchaService.get_instance(self.proxy_manager)
704
  return await service.get_token(project_id)
705
  except Exception as e:
706
  debug_logger.log_error(f"[reCAPTCHA Browser] error: {str(e)}")
 
12
  class FlowClient:
13
  """VideoFX API客户端"""
14
 
15
+ def __init__(self, proxy_manager, db=None):
16
  self.proxy_manager = proxy_manager
17
+ self.db = db # Database instance for captcha config
18
  self.labs_base_url = config.flow_labs_base_url # https://labs.google/fx/api
19
  self.api_base_url = config.flow_api_base_url # https://aisandbox-pa.googleapis.com/v1
20
  self.timeout = config.flow_timeout
 
692
  if captcha_method == "personal":
693
  try:
694
  from .browser_captcha_personal import BrowserCaptchaService
695
+ service = await BrowserCaptchaService.get_instance(self.db)
696
  return await service.get_token(project_id)
697
  except Exception as e:
698
  debug_logger.log_error(f"[reCAPTCHA Browser] error: {str(e)}")
 
701
  elif captcha_method == "browser":
702
  try:
703
  from .browser_captcha import BrowserCaptchaService
704
+ service = await BrowserCaptchaService.get_instance(self.db)
705
  return await service.get_token(project_id)
706
  except Exception as e:
707
  debug_logger.log_error(f"[reCAPTCHA Browser] error: {str(e)}")