HOPStudio commited on
Commit
bd993f3
·
verified ·
1 Parent(s): 0f9f4fe

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +927 -769
main.py CHANGED
@@ -1,1039 +1,1197 @@
1
- import osimport subprocessimport threadingimport shutilimport jsonimport sysimport timeimport zipfileimport reimport requestsimport gradio as gr
2
-
3
- REPO_URL = "https://hf-mirror.com/HOPStudio/modelscope"REPO_PATH = "/root/modelscope"
4
-
5
- FRP_VERSION = "0.69.0"CURRENT_DIR = REPO_PATHLAST_OUTPUT = ""
6
-
7
- ===== 新增:ModelScope 整库克隆/下载并启动 ComfyUI 的配置 =====
8
-
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  MODELSCOPE_MODEL_ID = "HOPStudio/forgeui"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- forgeui 整库和 offline_build 下载包都很大,不放在 REPO_PATH 里,避免 clone_repository() 删除 /root/modelscope 时把断点文件删掉。
12
-
13
- OFFLINE_BUILD_CACHE_DIR = "/root/offline_build_cache"MODELSCOPE_DOWNLOAD_DIR = os.path.join(OFFLINE_BUILD_CACHE_DIR, "modelscope_download")MODELSCOPE_CACHE_DIR = os.path.join(OFFLINE_BUILD_CACHE_DIR, "modelscope_cache")OFFLINE_BUILD_DIR = os.path.join(REPO_PATH, "offline_build")COMFYUI_PORT = 8188COMFYUI_LOG_PATH = os.path.join(REPO_PATH, "comfyui.log")PROXY_URL = "http://103.71.69.203:17890"MODELSCOPE_DOWNLOAD_TIMEOUT = 21600
14
-
15
- 兼容可能的文件名拼写:用户当前指定 offlinbuild.zip,
16
-
17
- 同时兼容 offlinebuild.zip / offline_build.zip,避免文件名略有差异时直接失败。
18
 
19
- OFFLINE_BUILD_ZIP_CANDIDATES = ["offlinbuild.zip","offlinebuild.zip","offline_build.zip"]
 
 
 
 
 
 
 
 
20
 
21
- def clone_repository():if os.path.exists(REPO_PATH):print(f"检测到已有仓库,正在删除: {REPO_PATH}")try:shutil.rmtree(REPO_PATH)print("旧仓库已删除。")except Exception as e:print(f"删除旧仓库失败: {e}")return False
 
22
 
23
- try:
24
- print(f"正在克隆仓库到 {REPO_PATH}...")
 
25
 
26
- env = os.environ.copy()
27
- env["GIT_LFS_SKIP_SMUDGE"] = "1"
28
- env["HF_HUB_DISABLE_XET"] = "1"
29
-
30
- subprocess.run(
31
- ["git", "clone", REPO_URL, REPO_PATH],
32
- check=True,
33
- env=env,
34
- timeout=120
35
- )
36
 
37
- print("仓库克隆成功。")
38
- return True
39
 
40
- except subprocess.TimeoutExpired:
41
- print("克隆仓库超时。")
42
- return False
43
 
44
- except subprocess.CalledProcessError as e:
45
- print(f"克隆仓库失败: {e}")
46
- return False
47
 
48
- except Exception as e:
49
- print(f"克隆仓库异常: {e}")
50
- return False
51
 
52
- def execute_shell_command(command):global CURRENT_DIR
53
 
54
- command = command.strip()
 
55
 
56
- if command == "pwd":
57
- return CURRENT_DIR
58
 
59
- if command.startswith("cd "):
60
- path = command[3:].strip()
61
- path = os.path.expanduser(path)
62
 
63
- if not os.path.isabs(path):
64
- path = os.path.join(CURRENT_DIR, path)
 
65
 
66
- path = os.path.abspath(path)
 
67
 
68
- if os.path.isdir(path):
69
- CURRENT_DIR = path
70
- return f"已切换目录: {CURRENT_DIR}"
71
 
72
- return f"目录不存在: {path}"
 
 
73
 
74
- try:
75
- result = subprocess.run(
76
- command,
77
- shell=True,
78
- cwd=CURRENT_DIR,
79
- stdout=subprocess.PIPE,
80
- stderr=subprocess.PIPE,
81
- text=True,
82
- timeout=120
83
- )
84
 
85
- output = result.stdout + result.stderr
 
 
 
 
 
 
 
 
 
86
 
87
- if not output.strip():
88
- output = "命令执行完成,无输出。"
89
 
90
- return f"当前目录: {CURRENT_DIR}\n\n{output}"
 
91
 
92
- except subprocess.TimeoutExpired:
93
- return "命令执行超时。"
94
 
95
- except Exception as e:
96
- return f"执行命令失败: {e}"
97
 
98
- def _api_call_impl(input_text, model="internlm2.5-latest", temperature=0.8, top_p=0.9, n=1):if input_text.startswith("cmd_run "):command = input_text[len("cmd_run "):].strip()return execute_shell_command(command)
 
99
 
100
- api_token = os.getenv("API_TOKEN")
101
 
102
- if not api_token:
103
- return "API_TOKEN 未设置。"
 
 
104
 
105
- url = "https://internlm-chat.intern-ai.org.cn/puyu/api/v1/chat/completions"
106
 
107
- headers = {
108
- "Content-Type": "application/json",
109
- "Authorization": f"Bearer {api_token}"
110
- }
111
 
112
- data = {
113
- "model": model,
114
- "messages": [
115
- {
116
- "role": "user",
117
- "content": input_text
118
- }
119
- ],
120
- "n": int(n),
121
- "temperature": float(temperature),
122
- "top_p": float(top_p)
123
- }
124
 
125
- try:
126
- response = requests.post(
127
- url,
128
- headers=headers,
129
- data=json.dumps(data),
130
- timeout=60
131
- )
132
 
133
- response.raise_for_status()
134
- return response.json()["choices"][0]["message"]["content"]
 
 
 
 
 
 
 
 
 
 
135
 
136
- except Exception as e:
137
- return f"API 请求失败: {e}"
 
 
 
 
 
138
 
139
- def api_call(input_text, model="internlm2.5-latest", temperature=0.8, top_p=0.9, n=1):global LAST_OUTPUT
 
140
 
141
- LAST_OUTPUT = _api_call_impl(
142
- input_text,
143
- model,
144
- temperature,
145
- top_p,
146
- n
147
- )
148
 
149
- return LAST_OUTPUT
150
 
151
- def refresh_frontend():return LAST_OUTPUT
 
152
 
153
- def create_frpc_toml():os.makedirs(REPO_PATH, exist_ok=True)
 
 
 
 
 
 
154
 
155
- frpc_toml_path = os.path.join(REPO_PATH, "frpc.toml")
156
 
157
- toml_content = """
158
 
159
- serverAddr = "103.71.69.203"serverPort = 7000auth.token = "hop20030219"[[proxies]]name = "jupyterlab"type = "tcp"localIP = "127.0.0.1"localPort = 18888remotePort = 18888[[proxies]]name = "comfyui"type = "tcp"localIP = "127.0.0.1"localPort = 8188remotePort = 8188"""
 
160
 
161
- try:
162
- with open(frpc_toml_path, "w", encoding="utf-8") as f:
163
- f.write(toml_content)
164
 
165
- print(f"frpc.toml 已创建: {frpc_toml_path}")
 
166
 
167
- except Exception as e:
168
- print(f"创建 frpc.toml 失败: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
- def install_frp():frp_tar_name = f"frp_{FRP_VERSION}linux_amd64.tar.gz"frp_dir_name = f"frp{FRP_VERSION}_linux_amd64"
 
 
171
 
172
- frp_tar_path = os.path.join(REPO_PATH, frp_tar_name)
173
- frp_install_path = os.path.join(REPO_PATH, frp_dir_name)
174
 
175
- frp_download_urls = [
176
- f"https://github.com/fatedier/frp/releases/download/v{FRP_VERSION}/{frp_tar_name}",
177
- f"https://gh.llkk.cc/https://github.com/fatedier/frp/releases/download/v{FRP_VERSION}/{frp_tar_name}",
178
- f"https://gh-proxy.com/https://github.com/fatedier/frp/releases/download/v{FRP_VERSION}/{frp_tar_name}"
179
- ]
180
 
181
- if os.path.exists(frp_install_path):
182
- print(f"frp 已安装: {frp_install_path}")
183
- return True
184
 
185
- def is_valid_tar_gz(path):
186
- if not os.path.exists(path):
187
- return False
188
 
189
- try:
190
- result = subprocess.run(
191
- ["tar", "-tzf", path],
192
- stdout=subprocess.PIPE,
193
- stderr=subprocess.PIPE,
194
- text=True,
195
- timeout=20
196
- )
197
- return result.returncode == 0
198
- except Exception:
199
- return False
200
 
201
- if os.path.exists(frp_tar_path) and not is_valid_tar_gz(frp_tar_path):
202
- print("检测到损坏的 frp 压缩包,正在删除。")
203
- os.remove(frp_tar_path)
 
 
204
 
205
- if not os.path.exists(frp_tar_path):
206
- print("开始下载 frp")
 
207
 
208
- download_success = False
 
 
209
 
210
- for url in frp_download_urls:
211
  try:
212
- print(f"尝试下载: {url}")
213
-
214
  result = subprocess.run(
215
- [
216
- "wget",
217
- "--timeout=30",
218
- "--tries=1",
219
- "--no-check-certificate",
220
- "-O",
221
- frp_tar_path,
222
- url
223
- ],
224
  stdout=subprocess.PIPE,
225
  stderr=subprocess.PIPE,
226
  text=True,
227
- timeout=90
228
  )
 
 
 
229
 
230
- if result.returncode == 0 and is_valid_tar_gz(frp_tar_path):
231
- print("frp 下载成功。")
232
- download_success = True
233
- break
234
 
235
- print("下载失败或文件无效。")
 
236
 
237
- if os.path.exists(frp_tar_path):
238
- os.remove(frp_tar_path)
239
 
240
- except subprocess.TimeoutExpired:
241
- print("frp 下载超时。")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
- if os.path.exists(frp_tar_path):
244
- os.remove(frp_tar_path)
 
 
245
 
246
- except Exception as e:
247
- print(f"frp 下载异常: {e}")
248
 
249
- if os.path.exists(frp_tar_path):
250
- os.remove(frp_tar_path)
251
 
252
- if not download_success:
253
- print("frp 下载失败,跳过 frp。")
254
- return False
255
 
256
- try:
257
- print(f"正在解压 frp 到 {REPO_PATH}...")
258
 
259
- subprocess.run(
260
- ["tar", "-xzf", frp_tar_path, "-C", REPO_PATH],
261
- check=True,
262
- timeout=30
263
- )
264
 
265
- frpc_path = os.path.join(frp_install_path, "frpc")
266
- frps_path = os.path.join(frp_install_path, "frps")
267
 
268
- if os.path.exists(frpc_path):
269
- subprocess.run(["chmod", "+x", frpc_path], check=False)
 
270
 
271
- if os.path.exists(frps_path):
272
- subprocess.run(["chmod", "+x", frps_path], check=False)
273
 
274
- print("frp 安装成功。")
275
- return True
 
 
 
276
 
277
- except subprocess.TimeoutExpired:
278
- print("frp 解压超时。")
279
- return False
280
 
281
- except subprocess.CalledProcessError as e:
282
- print(f"frp 解压失败: {e}")
283
- return False
284
 
285
- except Exception as e:
286
- print(f"frp 安装异常: {e}")
287
- return False
288
 
289
- def start_frp():frp_dir_name = f"frp_{FRP_VERSION}_linux_amd64"
 
290
 
291
- frpc_path = os.path.join(REPO_PATH, frp_dir_name, "frpc")
292
- frpc_config_path = os.path.join(REPO_PATH, "frpc.toml")
 
293
 
294
- if not os.path.exists(frpc_path):
295
- print(f"frpc 不存在: {frpc_path}")
296
- return
297
 
298
- if not os.path.exists(frpc_config_path):
299
- create_frpc_toml()
 
300
 
301
- try:
302
- subprocess.Popen(
303
- [frpc_path, "-c", frpc_config_path],
304
- stdout=subprocess.DEVNULL,
305
- stderr=subprocess.DEVNULL
306
- )
307
 
308
- print("frp 已启动。")
 
309
 
310
- except Exception as e:
311
- print(f"启动 frp 失败: {e}")
312
 
313
- def start_jupyterlab_process():jupyter_script = os.path.join(REPO_PATH, "start_jupyterlab.py")
 
 
314
 
315
- if not os.path.exists(jupyter_script):
316
- print(f"未找到 JupyterLab 启动脚本: {jupyter_script}")
317
- return
318
 
319
- try:
320
- subprocess.Popen(
321
- ["python3", jupyter_script],
322
- stdout=subprocess.DEVNULL,
323
- stderr=subprocess.DEVNULL
324
- )
325
 
326
- print("JupyterLab 已启动。")
327
 
328
- except Exception as e:
329
- print(f"启动 JupyterLab 失败: {e}")
330
 
331
 
 
 
332
 
333
- ===== 新增:ModelScope 整库克隆/下载 offline_build 并启动 ComfyUI =====
 
 
334
 
335
- def get_proxy_env():env = os.environ.copy()
 
 
 
 
 
336
 
337
- env["http_proxy"] = PROXY_URL
338
- env["https_proxy"] = PROXY_URL
339
- env["HTTP_PROXY"] = PROXY_URL
340
- env["HTTPS_PROXY"] = PROXY_URL
341
- env["ALL_PROXY"] = PROXY_URL
342
- env["no_proxy"] = "localhost,127.0.0.1"
343
- env["NO_PROXY"] = "localhost,127.0.0.1"
344
 
345
- return env
 
346
 
347
- def get_modelscope_download_env(use_proxy=False):env = os.environ.copy()
348
 
349
- # 固定 ModelScope 缓存目录,避免默认缓存散落到 ~/.cache,且方便断点续传。
350
- os.makedirs(MODELSCOPE_CACHE_DIR, exist_ok=True)
351
- env["MODELSCOPE_CACHE"] = MODELSCOPE_CACHE_DIR
352
- env["MODELSCOPE_ENDPOINT"] = "https://www.modelscope.cn"
353
- env["MODELSCOPE_SDK_ENDPOINT"] = "https://www.modelscope.cn"
354
 
355
- # ModelScope 下载大文件时会跳到阿里云 OSS。这里默认直连,避免代理把 OSS 内网地址转坏。
356
- proxy_keys = [
357
- "http_proxy",
358
- "https_proxy",
359
- "HTTP_PROXY",
360
- "HTTPS_PROXY",
361
- "ALL_PROXY",
362
- "all_proxy"
363
- ]
364
 
365
- if use_proxy:
366
  env["http_proxy"] = PROXY_URL
367
  env["https_proxy"] = PROXY_URL
368
  env["HTTP_PROXY"] = PROXY_URL
369
  env["HTTPS_PROXY"] = PROXY_URL
370
  env["ALL_PROXY"] = PROXY_URL
371
- env["all_proxy"] = PROXY_URL
372
- else:
373
- for key in proxy_keys:
374
- env.pop(key, None)
375
 
376
- no_proxy_value = "localhost,127.0.0.1,modelscope.cn,www.modelscope.cn,.modelscope.cn,aliyuncs.com,.aliyuncs.com"
377
- env["no_proxy"] = no_proxy_value
378
- env["NO_PROXY"] = no_proxy_value
379
 
380
- return env
381
 
382
- def configure_git_proxy():try:subprocess.run(["git", "config", "--global", "http.proxy", PROXY_URL],check=False,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,timeout=10)subprocess.run(["git", "config", "--global", "https.proxy", PROXY_URL],check=False,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,timeout=10)print("git 代理已配置。")except Exception as e:print(f"配置 git 代理失败: {e}")
 
383
 
384
- def is_valid_zip(path):if not os.path.exists(path):return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
 
386
- try:
387
- if not zipfile.is_zipfile(path):
388
- return False
 
 
 
 
 
 
 
389
 
390
- with zipfile.ZipFile(path, "r") as zf:
391
- # 只读取目录,不做全量 testzip,避免大压缩包校验耗时过长。
392
- return len(zf.namelist()) > 0
393
- except Exception:
394
- return False
395
 
396
- def find_existing_offline_zip():search_dirs = [MODELSCOPE_DOWNLOAD_DIR, OFFLINE_BUILD_CACHE_DIR, REPO_PATH]
397
 
398
- for directory in search_dirs:
399
- if not os.path.isdir(directory):
400
- continue
401
 
402
- for zip_name in OFFLINE_BUILD_ZIP_CANDIDATES:
403
- zip_path = os.path.join(directory, zip_name)
404
- if is_valid_zip(zip_path):
405
- return zip_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
 
407
- for root, _, files in os.walk(directory):
408
- for filename in files:
409
- lower_name = filename.lower()
410
- if lower_name.endswith(".zip") and "offlin" in lower_name:
411
- zip_path = os.path.join(root, filename)
412
- if is_valid_zip(zip_path):
413
- return zip_path
414
 
415
- return None
416
-
417
- def install_modelscope_cli():if shutil.which("modelscope"):print("检测到 modelscope 命令。")return True
418
-
419
- print("未检测到 modelscope 命令,尝试安装 modelscope。")
420
-
421
- install_commands = [
422
- [
423
- sys.executable,
424
- "-m",
425
- "pip",
426
- "install",
427
- "-U",
428
- "modelscope",
429
- "-i",
430
- "https://pypi.tuna.tsinghua.edu.cn/simple"
431
- ],
432
- [
433
- sys.executable,
434
- "-m",
435
- "pip",
436
- "install",
437
- "-U",
438
- "modelscope"
439
- ]
440
- ]
441
 
442
- for command in install_commands:
443
  try:
444
- result = subprocess.run(
445
- command,
446
- stdout=subprocess.PIPE,
447
- stderr=subprocess.PIPE,
448
- text=True,
449
- timeout=300
450
- )
451
-
452
- if result.returncode == 0 and shutil.which("modelscope"):
453
- print("modelscope 安装成功。")
454
- return True
455
 
456
- print("modelscope 安装尝试失败。")
457
- if result.stdout.strip():
458
- print(result.stdout[-1000:])
459
- if result.stderr.strip():
460
- print(result.stderr[-1000:])
461
 
462
- except subprocess.TimeoutExpired:
463
- print("modelscope 安装超时。")
464
- except Exception as e:
465
- print(f"modelscope 安装异常: {e}")
466
 
467
- print("modelscope 不可用,无法继续下载 offline_build。")
468
- return False
469
 
470
- def extract_urls_from_text(text):if not text:return []
 
 
471
 
472
- urls = re.findall(r"https?://[^\s'\")<>]+", text)
473
- cleaned_urls = []
 
 
474
 
475
- for url in urls:
476
- cleaned_urls.append(url.rstrip(".,;"))
 
 
 
 
 
477
 
478
- return cleaned_urls
479
 
480
- def convert_internal_oss_url_to_public(url):if not url:return None
481
 
482
- public_url = url.replace("-internal.aliyuncs.com", ".aliyuncs.com")
483
- public_url = public_url.replace("http://", "https://", 1)
 
 
484
 
485
- if public_url == url:
486
- return None
487
 
488
- return public_url
489
-
490
- def download_url_to_file(url, output_path, use_proxy=False):os.makedirs(os.path.dirname(output_path), exist_ok=True)
491
-
492
- env = get_modelscope_download_env(use_proxy=use_proxy)
493
-
494
- if shutil.which("aria2c"):
495
- command = [
496
- "aria2c",
497
- "--continue=true",
498
- "--max-connection-per-server=8",
499
- "--split=8",
500
- "--min-split-size=16M",
501
- "--check-certificate=false",
502
- "--summary-interval=30",
503
- "--timeout=30",
504
- "--retry-wait=10",
505
- "--max-tries=20",
506
- "--dir",
507
- os.path.dirname(output_path),
508
- "--out",
509
- os.path.basename(output_path),
510
- url
511
- ]
512
- else:
513
- command = [
514
- "wget",
515
- "--continue",
516
- "--timeout=30",
517
- "--tries=20",
518
- "--no-check-certificate",
519
- "--output-document",
520
- output_path,
521
- url
522
  ]
523
 
524
- try:
525
- print("执行直链下载命令: " + " ".join(command[:1]) + " ...")
 
 
 
 
 
 
 
526
 
527
- result = subprocess.run(
528
- command,
529
- env=env,
530
- stdout=subprocess.PIPE,
531
- stderr=subprocess.PIPE,
532
- text=True,
533
- timeout=MODELSCOPE_DOWNLOAD_TIMEOUT
534
- )
535
 
536
- if result.stdout.strip():
537
- print(result.stdout[-2000:])
538
- if result.stderr.strip():
539
- print(result.stderr[-2000:])
 
540
 
541
- if result.returncode == 0 and is_valid_zip(output_path):
542
- print(f"直链下载成功: {output_path}")
543
- return True
 
544
 
545
- print(f"直链下载失败或压缩包无效: {output_path}")
546
  return False
547
 
548
- except subprocess.TimeoutExpired:
549
- print("直链下载超时。")
550
- return False
551
- except Exception as e:
552
- print(f"直链下载异常: {e}")
553
- return False
554
 
555
- def try_public_oss_fallback(download_output, zip_name):urls = extract_urls_from_text(download_output)
 
 
556
 
557
- for url in urls:
558
- public_url = convert_internal_oss_url_to_public(url)
559
- if not public_url:
560
- continue
561
 
562
- print("检测到 ModelScope 返回 OSS 内网地址,尝试改用公网 OSS 地址继续下载。")
563
- target_path = os.path.join(MODELSCOPE_DOWNLOAD_DIR, zip_name)
564
 
565
- # 先直连公网 OSS;失败后才走代理。直连更适合国内服务器访问阿里云 OSS。
566
- if download_url_to_file(public_url, target_path, use_proxy=False):
567
- return target_path
568
 
569
- if download_url_to_file(public_url, target_path, use_proxy=True):
570
- return target_path
571
 
572
- return None
 
 
 
 
 
 
 
 
 
 
573
 
574
- def ensure_git_lfs_available():try:result = subprocess.run(["git", "lfs", "version"],stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,timeout=20)if result.returncode == 0:print("检测到 git-lfs。")return Trueexcept Exception:pass
575
 
576
- print("未检测到 git-lfs,尝试安装 git-lfs。")
 
577
 
578
- install_commands = []
579
- if shutil.which("apt-get"):
580
- install_commands.append(["bash", "-lc", "apt-get update && apt-get install -y git-lfs"])
581
- if shutil.which("yum"):
582
- install_commands.append(["bash", "-lc", "yum install -y git-lfs"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
583
 
584
- for command in install_commands:
585
  try:
 
 
586
  result = subprocess.run(
587
  command,
 
588
  stdout=subprocess.PIPE,
589
  stderr=subprocess.PIPE,
590
  text=True,
591
- timeout=600
592
  )
 
593
  if result.stdout.strip():
594
  print(result.stdout[-2000:])
595
  if result.stderr.strip():
596
  print(result.stderr[-2000:])
597
 
598
- check = subprocess.run(
599
- ["git", "lfs", "version"],
600
- stdout=subprocess.PIPE,
601
- stderr=subprocess.PIPE,
602
- text=True,
603
- timeout=20
604
- )
605
- if check.returncode == 0:
606
- print("git-lfs 安装成功。")
607
  return True
608
 
 
 
 
609
  except subprocess.TimeoutExpired:
610
- print("git-lfs 安装超时。")
 
611
  except Exception as e:
612
- print(f"git-lfs 安装异常: {e}")
 
613
 
614
- print("git-lfs 不可用,后续 git clone 可能只能拿到 LFS 指针文件。")
615
- return False
616
 
617
- def clone_whole_forgeui_repo_with_git():os.makedirs(OFFLINE_BUILD_CACHE_DIR, exist_ok=True)
 
618
 
619
- existing_zip = find_existing_offline_zip()
620
- if existing_zip:
621
- print(f"检测到已有 offline_build 压缩包: {existing_zip}")
622
- return existing_zip
623
 
624
- if not shutil.which("git"):
625
- print("未检测到 git,跳过 git 整库克隆。")
626
- return None
627
 
628
- ensure_git_lfs_available()
 
 
629
 
630
- repo_urls = [
631
- "https://www.modelscope.cn/HOPStudio/forgeui.git",
632
- "https://modelscope.cn/HOPStudio/forgeui.git"
633
- ]
634
 
635
- if os.path.isdir(os.path.join(MODELSCOPE_DOWNLOAD_DIR, ".git")):
636
- print(f"检测到已存在 ModelScope 整库仓库: {MODELSCOPE_DOWNLOAD_DIR}")
 
 
637
  try:
638
- subprocess.run(
639
- ["git", "-C", MODELSCOPE_DOWNLOAD_DIR, "lfs", "pull"],
640
- env=get_modelscope_download_env(use_proxy=False),
641
  stdout=subprocess.PIPE,
642
  stderr=subprocess.PIPE,
643
  text=True,
644
- timeout=MODELSCOPE_DOWNLOAD_TIMEOUT
645
  )
646
- except Exception as e:
647
- print(f"git lfs pull 执行异常: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
648
 
649
  existing_zip = find_existing_offline_zip()
650
  if existing_zip:
651
- print(f"在已克隆仓库中找到 offline_build 压缩包: {existing_zip}")
652
  return existing_zip
653
 
654
- elif os.path.exists(MODELSCOPE_DOWNLOAD_DIR):
655
- # 这里是旧版 modelscope download 留下的目录。整库 git clone 需要空目录,
656
- # 因此先改名备份,不直接删除,避免误删已有断点文件。
657
- backup_dir = MODELSCOPE_DOWNLOAD_DIR + f".bak_{int(time.time())}"
658
- try:
659
- shutil.move(MODELSCOPE_DOWNLOAD_DIR, backup_dir)
660
- print(f"检测到非 git 下载目录,已备份为: {backup_dir}")
661
- except Exception as e:
662
- print(f"备份旧下载目录失败: {e}")
663
  return None
664
 
665
- for repo_url in repo_urls:
666
- for use_proxy, mode_name in [(False, "直连"), (True, "代理")]:
667
- if os.path.exists(MODELSCOPE_DOWNLOAD_DIR):
668
- shutil.rmtree(MODELSCOPE_DOWNLOAD_DIR, ignore_errors=True)
669
 
670
- env = get_modelscope_download_env(use_proxy=use_proxy)
671
- command = [
672
- "git",
673
- "clone",
674
- "--depth",
675
- "1",
676
- repo_url,
677
- MODELSCOPE_DOWNLOAD_DIR
678
- ]
679
 
 
 
680
  try:
681
- print(f"执行 ModelScope 整库 git clone [{mode_name}]: " + " ".join(command))
682
-
683
- result = subprocess.run(
684
- command,
685
- env=env,
686
  stdout=subprocess.PIPE,
687
  stderr=subprocess.PIPE,
688
  text=True,
689
  timeout=MODELSCOPE_DOWNLOAD_TIMEOUT
690
  )
 
 
691
 
692
- if result.stdout.strip():
693
- print(result.stdout[-2000:])
694
- if result.stderr.strip():
695
- print(result.stderr[-4000:])
696
 
697
- if result.returncode != 0:
698
- print("本次 git clone 失败继续尝试下一个方式。")
699
- continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
700
 
701
  try:
702
- subprocess.run(
703
- ["git", "-C", MODELSCOPE_DOWNLOAD_DIR, "lfs", "pull"],
 
 
704
  env=env,
705
  stdout=subprocess.PIPE,
706
  stderr=subprocess.PIPE,
707
  text=True,
708
  timeout=MODELSCOPE_DOWNLOAD_TIMEOUT
709
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
710
  except Exception as e:
711
- print(f"git lfs pull 执行异常: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
712
 
713
  zip_path = find_existing_offline_zip()
714
- if zip_path:
715
- print(f"ModelScope 整库克隆完成,找到 offline_build 压缩包: {zip_path}")
716
  return zip_path
717
 
718
- print("整库 git clone 完成但未找到有效 offline_build 压缩包")
 
 
 
 
 
 
719
 
720
  except subprocess.TimeoutExpired:
721
- print("ModelScope 整库 git clone 超时,继续尝试下一个方式。")
722
  except Exception as e:
723
- print(f"ModelScope 整库 git clone 异常: {e}")
724
 
725
- return None
726
 
727
- def download_whole_forgeui_repo_with_modelscope_cli():if not install_modelscope_cli():return None
728
 
729
- commands = [
730
- (
731
- [
732
- "modelscope",
733
- "download",
734
- "--model",
735
- MODELSCOPE_MODEL_ID,
736
- "--local_dir",
737
- MODELSCOPE_DOWNLOAD_DIR
738
- ],
739
- False,
740
- "整库直连"
741
- ),
742
- (
743
- [
744
- "modelscope",
745
- "download",
746
- "--model",
747
- MODELSCOPE_MODEL_ID,
748
- "--local_dir",
749
- MODELSCOPE_DOWNLOAD_DIR,
750
- "--endpoint",
751
- "https://www.modelscope.cn"
752
- ],
753
- False,
754
- "整库直连+显式 endpoint"
755
- ),
756
- (
757
- [
758
- "modelscope",
759
- "download",
760
- "--model",
761
- MODELSCOPE_MODEL_ID,
762
- "--local_dir",
763
- MODELSCOPE_DOWNLOAD_DIR
764
- ],
765
- True,
766
- "整库代理"
767
- )
768
- ]
769
-
770
- for command, use_proxy, mode_name in commands:
771
- try:
772
- print(f"执行 ModelScope CLI 整库下载 [{mode_name}]: " + " ".join(command))
773
 
774
- result = subprocess.run(
775
- command,
776
- env=get_modelscope_download_env(use_proxy=use_proxy),
777
- stdout=subprocess.PIPE,
778
- stderr=subprocess.PIPE,
779
- text=True,
780
- timeout=MODELSCOPE_DOWNLOAD_TIMEOUT
781
- )
782
-
783
- output_text = ""
784
- if result.stdout.strip():
785
- output_text += result.stdout + "\n"
786
- print(result.stdout[-2000:])
787
- if result.stderr.strip():
788
- output_text += result.stderr + "\n"
789
- print(result.stderr[-4000:])
790
 
791
- zip_path = find_existing_offline_zip()
792
- if result.returncode == 0 and zip_path:
793
- print(f"ModelScope CLI 整库下载完成,找到 offline_build 压缩包: {zip_path}")
794
- return zip_path
795
 
796
- # 这里不再按文件名调用 modelscope download,只在 CLI 日志暴露 OSS 直链时做公网地址回退。
797
- fallback_zip = try_public_oss_fallback(output_text, "offlinbuild.zip")
798
- if fallback_zip and is_valid_zip(fallback_zip):
799
- print(f"offline_build 压缩包通过公网 OSS 回退下载成功: {fallback_zip}")
800
- return fallback_zip
801
 
802
- print("本次整库下载找到有效 offline_build 压缩包,继续尝试一个命令。")
803
 
804
- except subprocess.TimeoutExpired:
805
- print("ModelScope CLI 整库下载超时,继续尝试下一个命令。")
806
- except Exception as e:
807
- print(f"ModelScope CLI 整库下载异常: {e}")
808
 
809
- return None
 
810
 
811
- def download_offline_build_from_modelscope():os.makedirs(MODELSCOPE_DOWNLOAD_DIR, exist_ok=True)os.makedirs(MODELSCOPE_CACHE_DIR, exist_ok=True)
812
 
813
- existing_zip = find_existing_offline_zip()
814
- if existing_zip:
815
- print(f"检测到已有 offline_build 压缩包: {existing_zip}")
816
- return existing_zip
817
 
818
- print(f"开始从 ModelScope 整库克隆/下载 {MODELSCOPE_MODEL_ID}。")
 
 
 
 
 
819
 
820
- zip_path = clone_whole_forgeui_repo_with_git()
821
- if zip_path:
822
- return zip_path
823
 
824
- print("git 整库克隆未成功,改用 ModelScope CLI 整库下载。")
825
 
826
- zip_path = download_whole_forgeui_repo_with_modelscope_cli()
827
- if zip_path:
828
- return zip_path
 
 
829
 
830
- print("ModelScope 整库克隆/下载失败,未找到有效 offline_build 压缩包。")
831
- return None
 
832
 
833
- def locate_offline_build_dir():expected_main = os.path.join(OFFLINE_BUILD_DIR, "ComfyUI", "main.py")if os.path.exists(expected_main):return OFFLINE_BUILD_DIR
 
834
 
835
- for root, dirs, files in os.walk(REPO_PATH):
836
- if "main.py" in files and os.path.basename(root) == "ComfyUI":
837
- parent_dir = os.path.dirname(root)
838
- venv_activate = os.path.join(parent_dir, "venv", "bin", "activate")
839
- if os.path.exists(venv_activate):
840
- return parent_dir
841
 
842
- return None
 
 
 
843
 
844
- def extract_offline_build(zip_path):existing_dir = locate_offline_build_dir()if existing_dir:print(f"检测到已解压 offline_build: {existing_dir}")return existing_dir
 
845
 
846
- if not zip_path or not is_valid_zip(zip_path):
847
- print(f"offline_build 压缩包无效: {zip_path}")
848
- return None
849
 
850
- try:
851
- print(f"正在解压 offline_build: {zip_path}")
852
 
853
- with zipfile.ZipFile(zip_path, "r") as zf:
854
- zf.extractall(REPO_PATH)
 
 
 
855
 
856
- extracted_dir = locate_offline_build_dir()
857
- if extracted_dir:
858
- print(f"offline_build 解压完成: {extracted_dir}")
859
- return extracted_dir
860
 
861
- print("解压完成,但未找到 offline_build/ComfyUI/main.py。")
862
- return None
863
 
864
- except Exception as e:
865
- print(f"解压 offline_build 失败: {e}")
866
- return None
867
 
868
- def prepare_offline_build():existing_dir = locate_offline_build_dir()if existing_dir:print(f"offline_build 已存在,跳过下载解压: {existing_dir}")return existing_dir
 
869
 
870
- zip_path = download_offline_build_from_modelscope()
871
- if not zip_path:
872
- return None
873
 
874
- return extract_offline_build(zip_path)
 
875
 
876
- def start_comfyui_process():offline_build_dir = prepare_offline_build()
 
 
877
 
878
- if not offline_build_dir:
879
- print("offline_build 准备失败,跳过 ComfyUI 启动。")
880
- return
881
 
882
- venv_activate = os.path.join(offline_build_dir, "venv", "bin", "activate")
883
- comfyui_main = os.path.join(offline_build_dir, "ComfyUI", "main.py")
884
 
885
- if not os.path.exists(venv_activate):
886
- print(f"未找到虚拟环境激活脚本: {venv_activate}")
887
- return
 
 
 
 
 
 
 
 
 
 
 
 
888
 
889
- if not os.path.exists(comfyui_main):
890
- print(f"未找到 ComfyUI main.py: {comfyui_main}")
891
- return
 
 
 
 
 
 
 
 
892
 
893
- configure_git_proxy()
894
 
895
- command = f'''
 
 
 
896
 
897
- set -esource "{venv_activate}"export http_proxy={PROXY_URL}export https_proxy={PROXY_URL}export HTTP_PROXY={PROXY_URL}export HTTPS_PROXY={PROXY_URL}export ALL_PROXY={PROXY_URL}export no_proxy=localhost,127.0.0.1export NO_PROXY=localhost,127.0.0.1git config --global http.proxy {PROXY_URL}git config --global https.proxy {PROXY_URL}cd "{os.path.join(offline_build_dir, "ComfyUI")}"python main.py --listen 0.0.0.0 --port {COMFYUI_PORT}'''
 
898
 
899
- try:
900
- os.makedirs(REPO_PATH, exist_ok=True)
901
- log_file = open(COMFYUI_LOG_PATH, "a", encoding="utf-8")
902
-
903
- process = subprocess.Popen(
904
- ["bash", "-lc", command],
905
- cwd=offline_build_dir,
906
- env=get_proxy_env(),
907
- stdout=log_file,
908
- stderr=subprocess.STDOUT
909
- )
910
 
911
- time.sleep(3)
 
 
 
912
 
913
- if process.poll() is None:
914
- print(f"ComfyUI 已启动,端口: {COMFYUI_PORT},日志: {COMFYUI_LOG_PATH}")
915
- else:
916
- print(f"ComfyUI 启动后立即退出,返回码: {process.returncode},请查看日志: {COMFYUI_LOG_PATH}")
917
 
918
- except Exception as e:
919
- print(f"启动 ComfyUI 失败: {e}")
 
 
 
 
 
920
 
921
- def start_gradio():with gr.Blocks() as demo:gr.Markdown("# InternLM 控制台")
 
 
 
 
 
 
922
 
923
- input_box = gr.Textbox(
924
- label="输入",
925
- placeholder="输入对话内容"
926
- )
 
 
 
927
 
928
- model_box = gr.Textbox(
929
- value="internlm2.5-latest",
930
- label="模型"
931
- )
932
 
933
- temperature_slider = gr.Slider(
934
- minimum=0,
935
- maximum=1,
936
- value=0.8,
937
- step=0.01,
938
- label="Temperature"
939
- )
940
 
941
- top_p_slider = gr.Slider(
942
- minimum=0,
943
- maximum=1,
944
- value=0.9,
945
- step=0.01,
946
- label="Top-p"
947
- )
 
 
 
 
948
 
949
- n_slider = gr.Slider(
950
- minimum=1,
951
- maximum=10,
952
- value=1,
953
- step=1,
954
- label="生成回复数量"
955
- )
956
 
957
- with gr.Row():
958
- send_button = gr.Button("发送", variant="primary")
959
- refresh_button = gr.Button("刷新前端")
960
 
961
- output_box = gr.Textbox(
962
- label="回复",
963
- lines=20,
964
- interactive=False
965
- )
966
 
967
- send_button.click(
968
- api_call,
969
- inputs=[
970
- input_box,
971
- model_box,
972
- temperature_slider,
973
- top_p_slider,
974
- n_slider
975
- ],
976
- outputs=output_box
977
- )
978
 
979
- refresh_button.click(
980
- refresh_frontend,
981
- outputs=output_box
 
982
  )
983
 
984
- timer = gr.Timer(5)
985
 
986
- timer.tick(
987
- refresh_frontend,
988
- outputs=output_box
989
- )
990
 
991
- print("Gradio 正在启动。")
992
 
993
- demo.launch(
994
- server_name="0.0.0.0",
995
- server_port=7860,
996
- share=False
997
- )
998
 
999
- def main():global CURRENT_DIR
 
1000
 
1001
- clone_success = clone_repository()
 
 
 
 
 
 
 
1002
 
1003
- if clone_success:
1004
- CURRENT_DIR = REPO_PATH
 
 
 
 
 
 
1005
 
1006
- install_frp()
1007
- create_frpc_toml()
 
 
 
 
 
 
1008
 
1009
- try:
1010
- frp_thread = threading.Thread(
1011
- target=start_frp,
1012
- daemon=True
1013
- )
1014
- frp_thread.start()
1015
- except Exception as e:
1016
- print(f"frp 线程启动失败: {e}")
1017
-
1018
- try:
1019
- jupyter_thread = threading.Thread(
1020
- target=start_jupyterlab_process,
1021
- daemon=True
1022
- )
1023
- jupyter_thread.start()
1024
- except Exception as e:
1025
- print(f"JupyterLab 线程启动失败: {e}")
1026
-
1027
- try:
1028
- comfyui_thread = threading.Thread(
1029
- target=start_comfyui_process,
1030
- daemon=True
1031
- )
1032
- comfyui_thread.start()
1033
- except Exception as e:
1034
- print(f"ComfyUI 线程启动失败: {e}")
1035
 
1036
- print("准备启动 Gradio...")
1037
- start_gradio()
1038
 
1039
- if name == "main":main()
 
 
1
+ import os
2
+ import subprocess
3
+ import threading
4
+ import shutil
5
+ import json
6
+ import sys
7
+ import time
8
+ import zipfile
9
+ import re
10
+ import requests
11
+ import gradio as gr
12
+
13
+
14
+ REPO_URL = "https://hf-mirror.com/HOPStudio/modelscope"
15
+ REPO_PATH = "/root/modelscope"
16
+
17
+ FRP_VERSION = "0.69.0"
18
+ CURRENT_DIR = REPO_PATH
19
+ LAST_OUTPUT = ""
20
+
21
+ # ===== 新增:ModelScope 整库克隆/下载并启动 ComfyUI 的配置 =====
22
  MODELSCOPE_MODEL_ID = "HOPStudio/forgeui"
23
+ # forgeui 整库和 offline_build 下载包都很大,不放在 REPO_PATH 里,避免 clone_repository() 删除 /root/modelscope 时把断点文件删掉。
24
+ OFFLINE_BUILD_CACHE_DIR = "/root/offline_build_cache"
25
+ MODELSCOPE_DOWNLOAD_DIR = os.path.join(OFFLINE_BUILD_CACHE_DIR, "modelscope_download")
26
+ MODELSCOPE_CACHE_DIR = os.path.join(OFFLINE_BUILD_CACHE_DIR, "modelscope_cache")
27
+ OFFLINE_BUILD_DIR = os.path.join(REPO_PATH, "offline_build")
28
+ COMFYUI_PORT = 8188
29
+ COMFYUI_LOG_PATH = os.path.join(REPO_PATH, "comfyui.log")
30
+ PROXY_URL = "http://103.71.69.203:17890"
31
+ MODELSCOPE_DOWNLOAD_TIMEOUT = 21600
32
+
33
+ # 兼容可能的文件名拼写:用户当前指定 offlinbuild.zip,
34
+ # 同时兼容 offlinebuild.zip / offline_build.zip,避免文件名略有差异时直接失败。
35
+ OFFLINE_BUILD_ZIP_CANDIDATES = [
36
+ "offlinbuild.zip",
37
+ "offlinebuild.zip",
38
+ "offline_build.zip"
39
+ ]
40
 
 
 
 
 
 
 
 
41
 
42
+ def clone_repository():
43
+ if os.path.exists(REPO_PATH):
44
+ print(f"检测到已有仓库,正在删除: {REPO_PATH}")
45
+ try:
46
+ shutil.rmtree(REPO_PATH)
47
+ print("旧仓库已删除。")
48
+ except Exception as e:
49
+ print(f"删除旧仓库失败: {e}")
50
+ return False
51
 
52
+ try:
53
+ print(f"正在克隆仓库到 {REPO_PATH}...")
54
 
55
+ env = os.environ.copy()
56
+ env["GIT_LFS_SKIP_SMUDGE"] = "1"
57
+ env["HF_HUB_DISABLE_XET"] = "1"
58
 
59
+ subprocess.run(
60
+ ["git", "clone", REPO_URL, REPO_PATH],
61
+ check=True,
62
+ env=env,
63
+ timeout=120
64
+ )
 
 
 
 
65
 
66
+ print("仓库克隆成功。")
67
+ return True
68
 
69
+ except subprocess.TimeoutExpired:
70
+ print("克隆仓库超时。")
71
+ return False
72
 
73
+ except subprocess.CalledProcessError as e:
74
+ print(f"克隆仓库失败: {e}")
75
+ return False
76
 
77
+ except Exception as e:
78
+ print(f"克隆仓库异常: {e}")
79
+ return False
80
 
 
81
 
82
+ def execute_shell_command(command):
83
+ global CURRENT_DIR
84
 
85
+ command = command.strip()
 
86
 
87
+ if command == "pwd":
88
+ return CURRENT_DIR
 
89
 
90
+ if command.startswith("cd "):
91
+ path = command[3:].strip()
92
+ path = os.path.expanduser(path)
93
 
94
+ if not os.path.isabs(path):
95
+ path = os.path.join(CURRENT_DIR, path)
96
 
97
+ path = os.path.abspath(path)
 
 
98
 
99
+ if os.path.isdir(path):
100
+ CURRENT_DIR = path
101
+ return f"已切换目录: {CURRENT_DIR}"
102
 
103
+ return f"目录不存在: {path}"
 
 
 
 
 
 
 
 
 
104
 
105
+ try:
106
+ result = subprocess.run(
107
+ command,
108
+ shell=True,
109
+ cwd=CURRENT_DIR,
110
+ stdout=subprocess.PIPE,
111
+ stderr=subprocess.PIPE,
112
+ text=True,
113
+ timeout=120
114
+ )
115
 
116
+ output = result.stdout + result.stderr
 
117
 
118
+ if not output.strip():
119
+ output = "命令执行完成,无输出。"
120
 
121
+ return f"当前目录: {CURRENT_DIR}\n\n{output}"
 
122
 
123
+ except subprocess.TimeoutExpired:
124
+ return "命令执行超时。"
125
 
126
+ except Exception as e:
127
+ return f"执行命令失败: {e}"
128
 
 
129
 
130
+ def _api_call_impl(input_text, model="internlm2.5-latest", temperature=0.8, top_p=0.9, n=1):
131
+ if input_text.startswith("cmd_run "):
132
+ command = input_text[len("cmd_run "):].strip()
133
+ return execute_shell_command(command)
134
 
135
+ api_token = os.getenv("API_TOKEN")
136
 
137
+ if not api_token:
138
+ return "API_TOKEN 未设置。"
 
 
139
 
140
+ url = "https://internlm-chat.intern-ai.org.cn/puyu/api/v1/chat/completions"
 
 
 
 
 
 
 
 
 
 
 
141
 
142
+ headers = {
143
+ "Content-Type": "application/json",
144
+ "Authorization": f"Bearer {api_token}"
145
+ }
 
 
 
146
 
147
+ data = {
148
+ "model": model,
149
+ "messages": [
150
+ {
151
+ "role": "user",
152
+ "content": input_text
153
+ }
154
+ ],
155
+ "n": int(n),
156
+ "temperature": float(temperature),
157
+ "top_p": float(top_p)
158
+ }
159
 
160
+ try:
161
+ response = requests.post(
162
+ url,
163
+ headers=headers,
164
+ data=json.dumps(data),
165
+ timeout=60
166
+ )
167
 
168
+ response.raise_for_status()
169
+ return response.json()["choices"][0]["message"]["content"]
170
 
171
+ except Exception as e:
172
+ return f"API 请求失败: {e}"
 
 
 
 
 
173
 
 
174
 
175
+ def api_call(input_text, model="internlm2.5-latest", temperature=0.8, top_p=0.9, n=1):
176
+ global LAST_OUTPUT
177
 
178
+ LAST_OUTPUT = _api_call_impl(
179
+ input_text,
180
+ model,
181
+ temperature,
182
+ top_p,
183
+ n
184
+ )
185
 
186
+ return LAST_OUTPUT
187
 
 
188
 
189
+ def refresh_frontend():
190
+ return LAST_OUTPUT
191
 
 
 
 
192
 
193
+ def create_frpc_toml():
194
+ os.makedirs(REPO_PATH, exist_ok=True)
195
 
196
+ frpc_toml_path = os.path.join(REPO_PATH, "frpc.toml")
197
+
198
+ toml_content = """
199
+ serverAddr = "103.71.69.203"
200
+ serverPort = 7000
201
+ auth.token = "hop20030219"
202
+ [[proxies]]
203
+ name = "jupyterlab"
204
+ type = "tcp"
205
+ localIP = "127.0.0.1"
206
+ localPort = 18888
207
+ remotePort = 18888
208
+ [[proxies]]
209
+ name = "comfyui"
210
+ type = "tcp"
211
+ localIP = "127.0.0.1"
212
+ localPort = 8188
213
+ remotePort = 8188
214
+ """
215
 
216
+ try:
217
+ with open(frpc_toml_path, "w", encoding="utf-8") as f:
218
+ f.write(toml_content)
219
 
220
+ print(f"frpc.toml 已创建: {frpc_toml_path}")
 
221
 
222
+ except Exception as e:
223
+ print(f"创建 frpc.toml 失败: {e}")
 
 
 
224
 
 
 
 
225
 
226
+ def install_frp():
227
+ frp_tar_name = f"frp_{FRP_VERSION}_linux_amd64.tar.gz"
228
+ frp_dir_name = f"frp_{FRP_VERSION}_linux_amd64"
229
 
230
+ frp_tar_path = os.path.join(REPO_PATH, frp_tar_name)
231
+ frp_install_path = os.path.join(REPO_PATH, frp_dir_name)
 
 
 
 
 
 
 
 
 
232
 
233
+ frp_download_urls = [
234
+ f"https://github.com/fatedier/frp/releases/download/v{FRP_VERSION}/{frp_tar_name}",
235
+ f"https://gh.llkk.cc/https://github.com/fatedier/frp/releases/download/v{FRP_VERSION}/{frp_tar_name}",
236
+ f"https://gh-proxy.com/https://github.com/fatedier/frp/releases/download/v{FRP_VERSION}/{frp_tar_name}"
237
+ ]
238
 
239
+ if os.path.exists(frp_install_path):
240
+ print(f"frp 已安装: {frp_install_path}")
241
+ return True
242
 
243
+ def is_valid_tar_gz(path):
244
+ if not os.path.exists(path):
245
+ return False
246
 
 
247
  try:
 
 
248
  result = subprocess.run(
249
+ ["tar", "-tzf", path],
 
 
 
 
 
 
 
 
250
  stdout=subprocess.PIPE,
251
  stderr=subprocess.PIPE,
252
  text=True,
253
+ timeout=20
254
  )
255
+ return result.returncode == 0
256
+ except Exception:
257
+ return False
258
 
259
+ if os.path.exists(frp_tar_path) and not is_valid_tar_gz(frp_tar_path):
260
+ print("检测到损坏的 frp 压缩包,正在删除。")
261
+ os.remove(frp_tar_path)
 
262
 
263
+ if not os.path.exists(frp_tar_path):
264
+ print("开始下载 frp。")
265
 
266
+ download_success = False
 
267
 
268
+ for url in frp_download_urls:
269
+ try:
270
+ print(f"尝试下载: {url}")
271
+
272
+ result = subprocess.run(
273
+ [
274
+ "wget",
275
+ "--timeout=30",
276
+ "--tries=1",
277
+ "--no-check-certificate",
278
+ "-O",
279
+ frp_tar_path,
280
+ url
281
+ ],
282
+ stdout=subprocess.PIPE,
283
+ stderr=subprocess.PIPE,
284
+ text=True,
285
+ timeout=90
286
+ )
287
 
288
+ if result.returncode == 0 and is_valid_tar_gz(frp_tar_path):
289
+ print("frp 下载成功。")
290
+ download_success = True
291
+ break
292
 
293
+ print("下载失败或文件无效。")
 
294
 
295
+ if os.path.exists(frp_tar_path):
296
+ os.remove(frp_tar_path)
297
 
298
+ except subprocess.TimeoutExpired:
299
+ print("frp 下载超时。")
 
300
 
301
+ if os.path.exists(frp_tar_path):
302
+ os.remove(frp_tar_path)
303
 
304
+ except Exception as e:
305
+ print(f"frp 下载异常: {e}")
 
 
 
306
 
307
+ if os.path.exists(frp_tar_path):
308
+ os.remove(frp_tar_path)
309
 
310
+ if not download_success:
311
+ print("frp 下载失败,跳过 frp。")
312
+ return False
313
 
314
+ try:
315
+ print(f"正在解压 frp {REPO_PATH}...")
316
 
317
+ subprocess.run(
318
+ ["tar", "-xzf", frp_tar_path, "-C", REPO_PATH],
319
+ check=True,
320
+ timeout=30
321
+ )
322
 
323
+ frpc_path = os.path.join(frp_install_path, "frpc")
324
+ frps_path = os.path.join(frp_install_path, "frps")
 
325
 
326
+ if os.path.exists(frpc_path):
327
+ subprocess.run(["chmod", "+x", frpc_path], check=False)
 
328
 
329
+ if os.path.exists(frps_path):
330
+ subprocess.run(["chmod", "+x", frps_path], check=False)
 
331
 
332
+ print("frp 安装成功。")
333
+ return True
334
 
335
+ except subprocess.TimeoutExpired:
336
+ print("frp 解压超时。")
337
+ return False
338
 
339
+ except subprocess.CalledProcessError as e:
340
+ print(f"frp 解压失败: {e}")
341
+ return False
342
 
343
+ except Exception as e:
344
+ print(f"frp 安装异常: {e}")
345
+ return False
346
 
 
 
 
 
 
 
347
 
348
+ def start_frp():
349
+ frp_dir_name = f"frp_{FRP_VERSION}_linux_amd64"
350
 
351
+ frpc_path = os.path.join(REPO_PATH, frp_dir_name, "frpc")
352
+ frpc_config_path = os.path.join(REPO_PATH, "frpc.toml")
353
 
354
+ if not os.path.exists(frpc_path):
355
+ print(f"frpc 不存在: {frpc_path}")
356
+ return
357
 
358
+ if not os.path.exists(frpc_config_path):
359
+ create_frpc_toml()
 
360
 
361
+ try:
362
+ subprocess.Popen(
363
+ [frpc_path, "-c", frpc_config_path],
364
+ stdout=subprocess.DEVNULL,
365
+ stderr=subprocess.DEVNULL
366
+ )
367
 
368
+ print("frp 已启动。")
369
 
370
+ except Exception as e:
371
+ print(f"启动 frp 失败: {e}")
372
 
373
 
374
+ def start_jupyterlab_process():
375
+ jupyter_script = os.path.join(REPO_PATH, "start_jupyterlab.py")
376
 
377
+ if not os.path.exists(jupyter_script):
378
+ print(f"未找到 JupyterLab 启动脚本: {jupyter_script}")
379
+ return
380
 
381
+ try:
382
+ subprocess.Popen(
383
+ ["python3", jupyter_script],
384
+ stdout=subprocess.DEVNULL,
385
+ stderr=subprocess.DEVNULL
386
+ )
387
 
388
+ print("JupyterLab 已启动。")
 
 
 
 
 
 
389
 
390
+ except Exception as e:
391
+ print(f"启动 JupyterLab 失败: {e}")
392
 
 
393
 
 
 
 
 
 
394
 
395
+ # ===== 新增:ModelScope 整库克隆/下载 offline_build 并启动 ComfyUI =====
396
+ def get_proxy_env():
397
+ env = os.environ.copy()
 
 
 
 
 
 
398
 
 
399
  env["http_proxy"] = PROXY_URL
400
  env["https_proxy"] = PROXY_URL
401
  env["HTTP_PROXY"] = PROXY_URL
402
  env["HTTPS_PROXY"] = PROXY_URL
403
  env["ALL_PROXY"] = PROXY_URL
404
+ env["no_proxy"] = "localhost,127.0.0.1"
405
+ env["NO_PROXY"] = "localhost,127.0.0.1"
 
 
406
 
407
+ return env
 
 
408
 
 
409
 
410
+ def get_modelscope_download_env(use_proxy=False):
411
+ env = os.environ.copy()
412
 
413
+ # 固定 ModelScope 缓存目录,避免默认缓存散落到 ~/.cache,且方便断点续传。
414
+ os.makedirs(MODELSCOPE_CACHE_DIR, exist_ok=True)
415
+ env["MODELSCOPE_CACHE"] = MODELSCOPE_CACHE_DIR
416
+ env["MODELSCOPE_ENDPOINT"] = "https://www.modelscope.cn"
417
+ env["MODELSCOPE_SDK_ENDPOINT"] = "https://www.modelscope.cn"
418
+
419
+ # ModelScope 下载大文件时会跳到阿里云 OSS。这里默认直连,避免代理把 OSS 内网地址转坏。
420
+ proxy_keys = [
421
+ "http_proxy",
422
+ "https_proxy",
423
+ "HTTP_PROXY",
424
+ "HTTPS_PROXY",
425
+ "ALL_PROXY",
426
+ "all_proxy"
427
+ ]
428
 
429
+ if use_proxy:
430
+ env["http_proxy"] = PROXY_URL
431
+ env["https_proxy"] = PROXY_URL
432
+ env["HTTP_PROXY"] = PROXY_URL
433
+ env["HTTPS_PROXY"] = PROXY_URL
434
+ env["ALL_PROXY"] = PROXY_URL
435
+ env["all_proxy"] = PROXY_URL
436
+ else:
437
+ for key in proxy_keys:
438
+ env.pop(key, None)
439
 
440
+ no_proxy_value = "localhost,127.0.0.1,modelscope.cn,www.modelscope.cn,.modelscope.cn,aliyuncs.com,.aliyuncs.com"
441
+ env["no_proxy"] = no_proxy_value
442
+ env["NO_PROXY"] = no_proxy_value
 
 
443
 
444
+ return env
445
 
 
 
 
446
 
447
+ def configure_git_proxy():
448
+ try:
449
+ subprocess.run(
450
+ ["git", "config", "--global", "http.proxy", PROXY_URL],
451
+ check=False,
452
+ stdout=subprocess.DEVNULL,
453
+ stderr=subprocess.DEVNULL,
454
+ timeout=10
455
+ )
456
+ subprocess.run(
457
+ ["git", "config", "--global", "https.proxy", PROXY_URL],
458
+ check=False,
459
+ stdout=subprocess.DEVNULL,
460
+ stderr=subprocess.DEVNULL,
461
+ timeout=10
462
+ )
463
+ print("git 代理已配置。")
464
+ except Exception as e:
465
+ print(f"配置 git 代理失败: {e}")
466
 
 
 
 
 
 
 
 
467
 
468
+ def is_valid_zip(path):
469
+ if not os.path.exists(path):
470
+ return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
 
 
472
  try:
473
+ if not zipfile.is_zipfile(path):
474
+ return False
 
 
 
 
 
 
 
 
 
475
 
476
+ with zipfile.ZipFile(path, "r") as zf:
477
+ # 只读取目录,不做全量 testzip,避免大压缩包校验耗时过长。
478
+ return len(zf.namelist()) > 0
479
+ except Exception:
480
+ return False
481
 
 
 
 
 
482
 
483
+ def find_existing_offline_zip():
484
+ search_dirs = [MODELSCOPE_DOWNLOAD_DIR, OFFLINE_BUILD_CACHE_DIR, REPO_PATH]
485
 
486
+ for directory in search_dirs:
487
+ if not os.path.isdir(directory):
488
+ continue
489
 
490
+ for zip_name in OFFLINE_BUILD_ZIP_CANDIDATES:
491
+ zip_path = os.path.join(directory, zip_name)
492
+ if is_valid_zip(zip_path):
493
+ return zip_path
494
 
495
+ for root, _, files in os.walk(directory):
496
+ for filename in files:
497
+ lower_name = filename.lower()
498
+ if lower_name.endswith(".zip") and "offlin" in lower_name:
499
+ zip_path = os.path.join(root, filename)
500
+ if is_valid_zip(zip_path):
501
+ return zip_path
502
 
503
+ return None
504
 
 
505
 
506
+ def install_modelscope_cli():
507
+ if shutil.which("modelscope"):
508
+ print("检测到 modelscope 命令。")
509
+ return True
510
 
511
+ print("未检测到 modelscope 命令,尝试安装 modelscope。")
 
512
 
513
+ install_commands = [
514
+ [
515
+ sys.executable,
516
+ "-m",
517
+ "pip",
518
+ "install",
519
+ "-U",
520
+ "modelscope",
521
+ "-i",
522
+ "https://pypi.tuna.tsinghua.edu.cn/simple"
523
+ ],
524
+ [
525
+ sys.executable,
526
+ "-m",
527
+ "pip",
528
+ "install",
529
+ "-U",
530
+ "modelscope"
531
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  ]
533
 
534
+ for command in install_commands:
535
+ try:
536
+ result = subprocess.run(
537
+ command,
538
+ stdout=subprocess.PIPE,
539
+ stderr=subprocess.PIPE,
540
+ text=True,
541
+ timeout=300
542
+ )
543
 
544
+ if result.returncode == 0 and shutil.which("modelscope"):
545
+ print("modelscope 安装成功。")
546
+ return True
 
 
 
 
 
547
 
548
+ print("modelscope 安装尝试失败。")
549
+ if result.stdout.strip():
550
+ print(result.stdout[-1000:])
551
+ if result.stderr.strip():
552
+ print(result.stderr[-1000:])
553
 
554
+ except subprocess.TimeoutExpired:
555
+ print("modelscope 安装超时。")
556
+ except Exception as e:
557
+ print(f"modelscope 安装异常: {e}")
558
 
559
+ print("modelscope 不可用,无法继续下载 offline_build。")
560
  return False
561
 
 
 
 
 
 
 
562
 
563
+ def extract_urls_from_text(text):
564
+ if not text:
565
+ return []
566
 
567
+ urls = re.findall(r"https?://[^\s'\")<>]+", text)
568
+ cleaned_urls = []
 
 
569
 
570
+ for url in urls:
571
+ cleaned_urls.append(url.rstrip(".,;"))
572
 
573
+ return cleaned_urls
 
 
574
 
 
 
575
 
576
+ def convert_internal_oss_url_to_public(url):
577
+ if not url:
578
+ return None
579
+
580
+ public_url = url.replace("-internal.aliyuncs.com", ".aliyuncs.com")
581
+ public_url = public_url.replace("http://", "https://", 1)
582
+
583
+ if public_url == url:
584
+ return None
585
+
586
+ return public_url
587
 
 
588
 
589
+ def download_url_to_file(url, output_path, use_proxy=False):
590
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
591
 
592
+ env = get_modelscope_download_env(use_proxy=use_proxy)
593
+
594
+ if shutil.which("aria2c"):
595
+ command = [
596
+ "aria2c",
597
+ "--continue=true",
598
+ "--max-connection-per-server=8",
599
+ "--split=8",
600
+ "--min-split-size=16M",
601
+ "--check-certificate=false",
602
+ "--summary-interval=30",
603
+ "--timeout=30",
604
+ "--retry-wait=10",
605
+ "--max-tries=20",
606
+ "--dir",
607
+ os.path.dirname(output_path),
608
+ "--out",
609
+ os.path.basename(output_path),
610
+ url
611
+ ]
612
+ else:
613
+ command = [
614
+ "wget",
615
+ "--continue",
616
+ "--timeout=30",
617
+ "--tries=20",
618
+ "--no-check-certificate",
619
+ "--output-document",
620
+ output_path,
621
+ url
622
+ ]
623
 
 
624
  try:
625
+ print("执行直链下载命令: " + " ".join(command[:1]) + " ...")
626
+
627
  result = subprocess.run(
628
  command,
629
+ env=env,
630
  stdout=subprocess.PIPE,
631
  stderr=subprocess.PIPE,
632
  text=True,
633
+ timeout=MODELSCOPE_DOWNLOAD_TIMEOUT
634
  )
635
+
636
  if result.stdout.strip():
637
  print(result.stdout[-2000:])
638
  if result.stderr.strip():
639
  print(result.stderr[-2000:])
640
 
641
+ if result.returncode == 0 and is_valid_zip(output_path):
642
+ print(f"直链下载成功: {output_path}")
 
 
 
 
 
 
 
643
  return True
644
 
645
+ print(f"直链下载失败或压缩包无效: {output_path}")
646
+ return False
647
+
648
  except subprocess.TimeoutExpired:
649
+ print("直链下载超时。")
650
+ return False
651
  except Exception as e:
652
+ print(f"直链下载异常: {e}")
653
+ return False
654
 
 
 
655
 
656
+ def try_public_oss_fallback(download_output, zip_name):
657
+ urls = extract_urls_from_text(download_output)
658
 
659
+ for url in urls:
660
+ public_url = convert_internal_oss_url_to_public(url)
661
+ if not public_url:
662
+ continue
663
 
664
+ print("检测到 ModelScope 返回 OSS 内网地址,尝试改用公网 OSS 地址继续下载。")
665
+ target_path = os.path.join(MODELSCOPE_DOWNLOAD_DIR, zip_name)
 
666
 
667
+ # 先直连公网 OSS;失败后才走代理。直连更适合国内服务器访问阿里云 OSS。
668
+ if download_url_to_file(public_url, target_path, use_proxy=False):
669
+ return target_path
670
 
671
+ if download_url_to_file(public_url, target_path, use_proxy=True):
672
+ return target_path
 
 
673
 
674
+ return None
675
+
676
+
677
+ def ensure_git_lfs_available():
678
  try:
679
+ result = subprocess.run(
680
+ ["git", "lfs", "version"],
 
681
  stdout=subprocess.PIPE,
682
  stderr=subprocess.PIPE,
683
  text=True,
684
+ timeout=20
685
  )
686
+ if result.returncode == 0:
687
+ print("检测到 git-lfs")
688
+ return True
689
+ except Exception:
690
+ pass
691
+
692
+ print("未检测到 git-lfs,尝试安装 git-lfs。")
693
+
694
+ install_commands = []
695
+ if shutil.which("apt-get"):
696
+ install_commands.append(["bash", "-lc", "apt-get update && apt-get install -y git-lfs"])
697
+ if shutil.which("yum"):
698
+ install_commands.append(["bash", "-lc", "yum install -y git-lfs"])
699
+
700
+ for command in install_commands:
701
+ try:
702
+ result = subprocess.run(
703
+ command,
704
+ stdout=subprocess.PIPE,
705
+ stderr=subprocess.PIPE,
706
+ text=True,
707
+ timeout=600
708
+ )
709
+ if result.stdout.strip():
710
+ print(result.stdout[-2000:])
711
+ if result.stderr.strip():
712
+ print(result.stderr[-2000:])
713
+
714
+ check = subprocess.run(
715
+ ["git", "lfs", "version"],
716
+ stdout=subprocess.PIPE,
717
+ stderr=subprocess.PIPE,
718
+ text=True,
719
+ timeout=20
720
+ )
721
+ if check.returncode == 0:
722
+ print("git-lfs 安装成功。")
723
+ return True
724
+
725
+ except subprocess.TimeoutExpired:
726
+ print("git-lfs 安装超时。")
727
+ except Exception as e:
728
+ print(f"git-lfs 安装异常: {e}")
729
+
730
+ print("git-lfs 不可用,后续 git clone 可能只能拿到 LFS 指针文件。")
731
+ return False
732
+
733
+
734
+ def clone_whole_forgeui_repo_with_git():
735
+ os.makedirs(OFFLINE_BUILD_CACHE_DIR, exist_ok=True)
736
 
737
  existing_zip = find_existing_offline_zip()
738
  if existing_zip:
739
+ print(f"检测已有 offline_build 压缩包: {existing_zip}")
740
  return existing_zip
741
 
742
+ if not shutil.which("git"):
743
+ print("未检测到 git,跳过 git 整库克隆。")
 
 
 
 
 
 
 
744
  return None
745
 
746
+ ensure_git_lfs_available()
 
 
 
747
 
748
+ repo_urls = [
749
+ "https://www.modelscope.cn/HOPStudio/forgeui.git",
750
+ "https://modelscope.cn/HOPStudio/forgeui.git"
751
+ ]
 
 
 
 
 
752
 
753
+ if os.path.isdir(os.path.join(MODELSCOPE_DOWNLOAD_DIR, ".git")):
754
+ print(f"检测到已存在 ModelScope 整库仓库: {MODELSCOPE_DOWNLOAD_DIR}")
755
  try:
756
+ subprocess.run(
757
+ ["git", "-C", MODELSCOPE_DOWNLOAD_DIR, "lfs", "pull"],
758
+ env=get_modelscope_download_env(use_proxy=False),
 
 
759
  stdout=subprocess.PIPE,
760
  stderr=subprocess.PIPE,
761
  text=True,
762
  timeout=MODELSCOPE_DOWNLOAD_TIMEOUT
763
  )
764
+ except Exception as e:
765
+ print(f"git lfs pull 执行异常: {e}")
766
 
767
+ existing_zip = find_existing_offline_zip()
768
+ if existing_zip:
769
+ print(f"在已克隆仓库中找到 offline_build 压缩包: {existing_zip}")
770
+ return existing_zip
771
 
772
+ elif os.path.exists(MODELSCOPE_DOWNLOAD_DIR):
773
+ # 这里是旧版 modelscope download 留下的目录。整库 git clone 需要空目录
774
+ # 因此先改名备份,不直接删除,避免误删已有断点文件。
775
+ backup_dir = MODELSCOPE_DOWNLOAD_DIR + f".bak_{int(time.time())}"
776
+ try:
777
+ shutil.move(MODELSCOPE_DOWNLOAD_DIR, backup_dir)
778
+ print(f"检测到非 git 下载目录,已备份为: {backup_dir}")
779
+ except Exception as e:
780
+ print(f"备份旧下载目录失败: {e}")
781
+ return None
782
+
783
+ for repo_url in repo_urls:
784
+ for use_proxy, mode_name in [(False, "直连"), (True, "代理")]:
785
+ if os.path.exists(MODELSCOPE_DOWNLOAD_DIR):
786
+ shutil.rmtree(MODELSCOPE_DOWNLOAD_DIR, ignore_errors=True)
787
+
788
+ env = get_modelscope_download_env(use_proxy=use_proxy)
789
+ command = [
790
+ "git",
791
+ "clone",
792
+ "--depth",
793
+ "1",
794
+ repo_url,
795
+ MODELSCOPE_DOWNLOAD_DIR
796
+ ]
797
 
798
  try:
799
+ print(f"执行 ModelScope 整库 git clone [{mode_name}]: " + " ".join(command))
800
+
801
+ result = subprocess.run(
802
+ command,
803
  env=env,
804
  stdout=subprocess.PIPE,
805
  stderr=subprocess.PIPE,
806
  text=True,
807
  timeout=MODELSCOPE_DOWNLOAD_TIMEOUT
808
  )
809
+
810
+ if result.stdout.strip():
811
+ print(result.stdout[-2000:])
812
+ if result.stderr.strip():
813
+ print(result.stderr[-4000:])
814
+
815
+ if result.returncode != 0:
816
+ print("本次 git clone 失败,继续尝试下一个方式。")
817
+ continue
818
+
819
+ try:
820
+ subprocess.run(
821
+ ["git", "-C", MODELSCOPE_DOWNLOAD_DIR, "lfs", "pull"],
822
+ env=env,
823
+ stdout=subprocess.PIPE,
824
+ stderr=subprocess.PIPE,
825
+ text=True,
826
+ timeout=MODELSCOPE_DOWNLOAD_TIMEOUT
827
+ )
828
+ except Exception as e:
829
+ print(f"git lfs pull 执行异常: {e}")
830
+
831
+ zip_path = find_existing_offline_zip()
832
+ if zip_path:
833
+ print(f"ModelScope 整库克隆完成,找到 offline_build 压缩包: {zip_path}")
834
+ return zip_path
835
+
836
+ print("整库 git clone 完成,但未找到有效 offline_build 压缩包。")
837
+
838
+ except subprocess.TimeoutExpired:
839
+ print("ModelScope 整库 git clone 超时,继续尝试下一个方式。")
840
  except Exception as e:
841
+ print(f"ModelScope 整库 git clone 异常: {e}")
842
+
843
+ return None
844
+
845
+
846
+ def download_whole_forgeui_repo_with_modelscope_cli():
847
+ if not install_modelscope_cli():
848
+ return None
849
+
850
+ commands = [
851
+ (
852
+ [
853
+ "modelscope",
854
+ "download",
855
+ "--model",
856
+ MODELSCOPE_MODEL_ID,
857
+ "--local_dir",
858
+ MODELSCOPE_DOWNLOAD_DIR
859
+ ],
860
+ False,
861
+ "整库直连"
862
+ ),
863
+ (
864
+ [
865
+ "modelscope",
866
+ "download",
867
+ "--model",
868
+ MODELSCOPE_MODEL_ID,
869
+ "--local_dir",
870
+ MODELSCOPE_DOWNLOAD_DIR,
871
+ "--endpoint",
872
+ "https://www.modelscope.cn"
873
+ ],
874
+ False,
875
+ "整库直连+显式 endpoint"
876
+ ),
877
+ (
878
+ [
879
+ "modelscope",
880
+ "download",
881
+ "--model",
882
+ MODELSCOPE_MODEL_ID,
883
+ "--local_dir",
884
+ MODELSCOPE_DOWNLOAD_DIR
885
+ ],
886
+ True,
887
+ "整库代理"
888
+ )
889
+ ]
890
+
891
+ for command, use_proxy, mode_name in commands:
892
+ try:
893
+ print(f"执行 ModelScope CLI 整库下载 [{mode_name}]: " + " ".join(command))
894
+
895
+ result = subprocess.run(
896
+ command,
897
+ env=get_modelscope_download_env(use_proxy=use_proxy),
898
+ stdout=subprocess.PIPE,
899
+ stderr=subprocess.PIPE,
900
+ text=True,
901
+ timeout=MODELSCOPE_DOWNLOAD_TIMEOUT
902
+ )
903
+
904
+ output_text = ""
905
+ if result.stdout.strip():
906
+ output_text += result.stdout + "\n"
907
+ print(result.stdout[-2000:])
908
+ if result.stderr.strip():
909
+ output_text += result.stderr + "\n"
910
+ print(result.stderr[-4000:])
911
 
912
  zip_path = find_existing_offline_zip()
913
+ if result.returncode == 0 and zip_path:
914
+ print(f"ModelScope CLI 整库下载完成,找到 offline_build 压缩包: {zip_path}")
915
  return zip_path
916
 
917
+ # 这里不再按文件名调用 modelscope download只在 CLI 日志暴露 OSS 直链时做公网地址回退
918
+ fallback_zip = try_public_oss_fallback(output_text, "offlinbuild.zip")
919
+ if fallback_zip and is_valid_zip(fallback_zip):
920
+ print(f"offline_build 压缩包通过公网 OSS 回退下载成功: {fallback_zip}")
921
+ return fallback_zip
922
+
923
+ print("本次整库下载未找到有效 offline_build 压缩包,继续尝试下一个命令。")
924
 
925
  except subprocess.TimeoutExpired:
926
+ print("ModelScope CLI 整库下载超时,继续尝试下一个命令。")
927
  except Exception as e:
928
+ print(f"ModelScope CLI 整库下载异常: {e}")
929
 
930
+ return None
931
 
 
932
 
933
+ def download_offline_build_from_modelscope():
934
+ os.makedirs(MODELSCOPE_DOWNLOAD_DIR, exist_ok=True)
935
+ os.makedirs(MODELSCOPE_CACHE_DIR, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
936
 
937
+ existing_zip = find_existing_offline_zip()
938
+ if existing_zip:
939
+ print(f"检测到已有 offline_build 压缩包: {existing_zip}")
940
+ return existing_zip
 
 
 
 
 
 
 
 
 
 
 
 
941
 
942
+ print(f"开始从 ModelScope 整库克隆/下载 {MODELSCOPE_MODEL_ID}。")
 
 
 
943
 
944
+ zip_path = clone_whole_forgeui_repo_with_git()
945
+ if zip_path:
946
+ return zip_path
 
 
947
 
948
+ print("git 整库克隆成功,改用 ModelScope CLI 整库。")
949
 
950
+ zip_path = download_whole_forgeui_repo_with_modelscope_cli()
951
+ if zip_path:
952
+ return zip_path
 
953
 
954
+ print("ModelScope 整库克隆/下载失败,未找到有效 offline_build 压缩包。")
955
+ return None
956
 
 
957
 
958
+ def locate_offline_build_dir():
959
+ expected_main = os.path.join(OFFLINE_BUILD_DIR, "ComfyUI", "main.py")
960
+ if os.path.exists(expected_main):
961
+ return OFFLINE_BUILD_DIR
962
 
963
+ for root, dirs, files in os.walk(REPO_PATH):
964
+ if "main.py" in files and os.path.basename(root) == "ComfyUI":
965
+ parent_dir = os.path.dirname(root)
966
+ venv_activate = os.path.join(parent_dir, "venv", "bin", "activate")
967
+ if os.path.exists(venv_activate):
968
+ return parent_dir
969
 
970
+ return None
 
 
971
 
 
972
 
973
+ def extract_offline_build(zip_path):
974
+ existing_dir = locate_offline_build_dir()
975
+ if existing_dir:
976
+ print(f"检测到已解压的 offline_build: {existing_dir}")
977
+ return existing_dir
978
 
979
+ if not zip_path or not is_valid_zip(zip_path):
980
+ print(f"offline_build 压缩包无效: {zip_path}")
981
+ return None
982
 
983
+ try:
984
+ print(f"正在解压 offline_build: {zip_path}")
985
 
986
+ with zipfile.ZipFile(zip_path, "r") as zf:
987
+ zf.extractall(REPO_PATH)
 
 
 
 
988
 
989
+ extracted_dir = locate_offline_build_dir()
990
+ if extracted_dir:
991
+ print(f"offline_build 解压完成: {extracted_dir}")
992
+ return extracted_dir
993
 
994
+ print("解压完成,但未找到 offline_build/ComfyUI/main.py。")
995
+ return None
996
 
997
+ except Exception as e:
998
+ print(f"解压 offline_build 失败: {e}")
999
+ return None
1000
 
 
 
1001
 
1002
+ def prepare_offline_build():
1003
+ existing_dir = locate_offline_build_dir()
1004
+ if existing_dir:
1005
+ print(f"offline_build 已存在,跳过下载解压: {existing_dir}")
1006
+ return existing_dir
1007
 
1008
+ zip_path = download_offline_build_from_modelscope()
1009
+ if not zip_path:
1010
+ return None
 
1011
 
1012
+ return extract_offline_build(zip_path)
 
1013
 
 
 
 
1014
 
1015
+ def start_comfyui_process():
1016
+ offline_build_dir = prepare_offline_build()
1017
 
1018
+ if not offline_build_dir:
1019
+ print("offline_build 准备失败,跳过 ComfyUI 启动。")
1020
+ return
1021
 
1022
+ venv_activate = os.path.join(offline_build_dir, "venv", "bin", "activate")
1023
+ comfyui_main = os.path.join(offline_build_dir, "ComfyUI", "main.py")
1024
 
1025
+ if not os.path.exists(venv_activate):
1026
+ print(f"未找到虚拟环境激活脚本: {venv_activate}")
1027
+ return
1028
 
1029
+ if not os.path.exists(comfyui_main):
1030
+ print(f"未找到 ComfyUI main.py: {comfyui_main}")
1031
+ return
1032
 
1033
+ configure_git_proxy()
 
1034
 
1035
+ command = f'''
1036
+ set -e
1037
+ source "{venv_activate}"
1038
+ export http_proxy={PROXY_URL}
1039
+ export https_proxy={PROXY_URL}
1040
+ export HTTP_PROXY={PROXY_URL}
1041
+ export HTTPS_PROXY={PROXY_URL}
1042
+ export ALL_PROXY={PROXY_URL}
1043
+ export no_proxy=localhost,127.0.0.1
1044
+ export NO_PROXY=localhost,127.0.0.1
1045
+ git config --global http.proxy {PROXY_URL}
1046
+ git config --global https.proxy {PROXY_URL}
1047
+ cd "{os.path.join(offline_build_dir, "ComfyUI")}"
1048
+ python main.py --listen 0.0.0.0 --port {COMFYUI_PORT}
1049
+ '''
1050
 
1051
+ try:
1052
+ os.makedirs(REPO_PATH, exist_ok=True)
1053
+ log_file = open(COMFYUI_LOG_PATH, "a", encoding="utf-8")
1054
+
1055
+ process = subprocess.Popen(
1056
+ ["bash", "-lc", command],
1057
+ cwd=offline_build_dir,
1058
+ env=get_proxy_env(),
1059
+ stdout=log_file,
1060
+ stderr=subprocess.STDOUT
1061
+ )
1062
 
1063
+ time.sleep(3)
1064
 
1065
+ if process.poll() is None:
1066
+ print(f"ComfyUI 已启动,端口: {COMFYUI_PORT},日志: {COMFYUI_LOG_PATH}")
1067
+ else:
1068
+ print(f"ComfyUI 启动后立即退出,返回码: {process.returncode},请查看日志: {COMFYUI_LOG_PATH}")
1069
 
1070
+ except Exception as e:
1071
+ print(f"启动 ComfyUI 失败: {e}")
1072
 
1073
+ def start_gradio():
1074
+ with gr.Blocks() as demo:
1075
+ gr.Markdown("# InternLM 控制台")
 
 
 
 
 
 
 
 
1076
 
1077
+ input_box = gr.Textbox(
1078
+ label="输入",
1079
+ placeholder="输入对话内容"
1080
+ )
1081
 
1082
+ model_box = gr.Textbox(
1083
+ value="internlm2.5-latest",
1084
+ label="模型"
1085
+ )
1086
 
1087
+ temperature_slider = gr.Slider(
1088
+ minimum=0,
1089
+ maximum=1,
1090
+ value=0.8,
1091
+ step=0.01,
1092
+ label="Temperature"
1093
+ )
1094
 
1095
+ top_p_slider = gr.Slider(
1096
+ minimum=0,
1097
+ maximum=1,
1098
+ value=0.9,
1099
+ step=0.01,
1100
+ label="Top-p"
1101
+ )
1102
 
1103
+ n_slider = gr.Slider(
1104
+ minimum=1,
1105
+ maximum=10,
1106
+ value=1,
1107
+ step=1,
1108
+ label="生成回复数量"
1109
+ )
1110
 
1111
+ with gr.Row():
1112
+ send_button = gr.Button("发送", variant="primary")
1113
+ refresh_button = gr.Button("刷新前端")
 
1114
 
1115
+ output_box = gr.Textbox(
1116
+ label="回复",
1117
+ lines=20,
1118
+ interactive=False
1119
+ )
 
 
1120
 
1121
+ send_button.click(
1122
+ api_call,
1123
+ inputs=[
1124
+ input_box,
1125
+ model_box,
1126
+ temperature_slider,
1127
+ top_p_slider,
1128
+ n_slider
1129
+ ],
1130
+ outputs=output_box
1131
+ )
1132
 
1133
+ refresh_button.click(
1134
+ refresh_frontend,
1135
+ outputs=output_box
1136
+ )
 
 
 
1137
 
1138
+ timer = gr.Timer(5)
 
 
1139
 
1140
+ timer.tick(
1141
+ refresh_frontend,
1142
+ outputs=output_box
1143
+ )
 
1144
 
1145
+ print("Gradio 正在启动。")
 
 
 
 
 
 
 
 
 
 
1146
 
1147
+ demo.launch(
1148
+ server_name="0.0.0.0",
1149
+ server_port=7860,
1150
+ share=False
1151
  )
1152
 
 
1153
 
1154
+ def main():
1155
+ global CURRENT_DIR
 
 
1156
 
1157
+ clone_success = clone_repository()
1158
 
1159
+ if clone_success:
1160
+ CURRENT_DIR = REPO_PATH
 
 
 
1161
 
1162
+ install_frp()
1163
+ create_frpc_toml()
1164
 
1165
+ try:
1166
+ frp_thread = threading.Thread(
1167
+ target=start_frp,
1168
+ daemon=True
1169
+ )
1170
+ frp_thread.start()
1171
+ except Exception as e:
1172
+ print(f"frp 线程启动失败: {e}")
1173
 
1174
+ try:
1175
+ jupyter_thread = threading.Thread(
1176
+ target=start_jupyterlab_process,
1177
+ daemon=True
1178
+ )
1179
+ jupyter_thread.start()
1180
+ except Exception as e:
1181
+ print(f"JupyterLab 线程启动失败: {e}")
1182
 
1183
+ try:
1184
+ comfyui_thread = threading.Thread(
1185
+ target=start_comfyui_process,
1186
+ daemon=True
1187
+ )
1188
+ comfyui_thread.start()
1189
+ except Exception as e:
1190
+ print(f"ComfyUI 线程启动失败: {e}")
1191
 
1192
+ print("准备启动 Gradio...")
1193
+ start_gradio()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1194
 
 
 
1195
 
1196
+ if __name__ == "__main__":
1197
+ main()