TNOT commited on
Commit
3202591
·
1 Parent(s): d69f065

feat: auto-bootstrap micromamba for cloud MFA setup

Browse files
Files changed (2) hide show
  1. app.py +102 -3
  2. src/mfa_runner.py +11 -3
app.py CHANGED
@@ -86,6 +86,10 @@ def setup_mfa_linux() -> bool:
86
  """
87
  import shutil
88
  import importlib.util
 
 
 
 
89
 
90
  def _run_cmd_ok(cmd, timeout=30):
91
  try:
@@ -94,20 +98,100 @@ def setup_mfa_linux() -> bool:
94
  except Exception:
95
  return False, "", ""
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  def verify_mfa_working() -> bool:
 
98
  commands = []
99
 
100
  # 优先官方 conda/mamba 入口,避免 PATH 中残留的 pip 版 mfa(缺少 _kalpy)
101
  conda = shutil.which("conda")
102
  if conda:
 
103
  commands.append([conda, "run", "-n", "base", "mfa", "--help"])
104
 
105
  micromamba = shutil.which("micromamba")
106
  if micromamba:
 
107
  commands.append([micromamba, "run", "-n", "base", "mfa", "--help"])
108
 
109
  mamba = shutil.which("mamba")
110
  if mamba:
 
111
  commands.append([mamba, "run", "-n", "base", "mfa", "--help"])
112
 
113
  commands.extend([
@@ -135,24 +219,39 @@ def setup_mfa_linux() -> bool:
135
  return False
136
 
137
  # 检查是否已可用
138
- if shutil.which("mfa") and verify_mfa_working():
139
  logger.info("MFA 已安装���工作正常")
140
  return True
141
 
142
  logger.info("MFA 不可用,Linux 下将使用 conda/mamba 从 conda-forge 安装(官方推荐)...")
143
 
144
  try:
 
 
 
 
 
 
 
145
  install_attempts = []
146
 
147
  mamba = shutil.which("mamba")
148
  if mamba:
149
  install_attempts.append((
150
  "mamba",
151
- [mamba, "install", "-y", "-c", "conda-forge", "montreal-forced-aligner"],
152
  ))
153
 
154
  micromamba = shutil.which("micromamba")
155
  if micromamba:
 
 
 
 
 
 
 
 
156
  install_attempts.append((
157
  "micromamba(base)",
158
  [micromamba, "install", "-y", "-n", "base", "-c", "conda-forge", "montreal-forced-aligner"],
@@ -162,7 +261,7 @@ def setup_mfa_linux() -> bool:
162
  if conda:
163
  install_attempts.append((
164
  "conda",
165
- [conda, "install", "-y", "-c", "conda-forge", "montreal-forced-aligner"],
166
  ))
167
 
168
  if not install_attempts:
 
86
  """
87
  import shutil
88
  import importlib.util
89
+ import tarfile
90
+ import tempfile
91
+ import urllib.request
92
+ import stat
93
 
94
  def _run_cmd_ok(cmd, timeout=30):
95
  try:
 
98
  except Exception:
99
  return False, "", ""
100
 
101
+ def _ensure_micromamba_available() -> str | None:
102
+ """自动下载并配置 micromamba(无控制台场景)。"""
103
+ mm_path = shutil.which("micromamba")
104
+ if mm_path:
105
+ return mm_path
106
+
107
+ if PERSISTENT_MODELS_DIR.parent.exists():
108
+ mamba_root = PERSISTENT_MODELS_DIR.parent / "micromamba"
109
+ else:
110
+ mamba_root = BASE_DIR / ".micromamba"
111
+
112
+ mamba_bin_dir = mamba_root / "bin"
113
+ mamba_bin_dir.mkdir(parents=True, exist_ok=True)
114
+ target_bin = mamba_bin_dir / "micromamba"
115
+
116
+ os.environ["MAMBA_ROOT_PREFIX"] = str(mamba_root)
117
+ if str(mamba_bin_dir) not in os.environ.get("PATH", ""):
118
+ os.environ["PATH"] = f"{mamba_bin_dir}:{os.environ.get('PATH', '')}"
119
+
120
+ if target_bin.exists():
121
+ target_bin.chmod(target_bin.stat().st_mode | stat.S_IEXEC)
122
+ logger.info(f"检测到已存在 micromamba: {target_bin}")
123
+ return str(target_bin)
124
+
125
+ urls = [
126
+ "https://micro.mamba.pm/api/micromamba/linux-64/latest",
127
+ "https://gh-proxy.com/https://github.com/mamba-org/micromamba-releases/releases/latest/download/micromamba-linux-64",
128
+ "https://github.com/mamba-org/micromamba-releases/releases/latest/download/micromamba-linux-64",
129
+ ]
130
+
131
+ for url in urls:
132
+ for attempt in range(1, 3):
133
+ try:
134
+ logger.info(f"下载 micromamba: {url} (第{attempt}次)")
135
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".tmp") as tmp:
136
+ tmp_path = Path(tmp.name)
137
+
138
+ try:
139
+ with urllib.request.urlopen(url, timeout=180) as resp:
140
+ data = resp.read()
141
+ tmp_path.write_bytes(data)
142
+
143
+ # 处理 tar.bz2 包格式
144
+ extracted = False
145
+ try:
146
+ with tarfile.open(tmp_path, mode="r:*") as tar:
147
+ member = None
148
+ for m in tar.getmembers():
149
+ if m.name.endswith("/bin/micromamba") or m.name == "bin/micromamba":
150
+ member = m
151
+ break
152
+ if member is not None:
153
+ src = tar.extractfile(member)
154
+ if src is None:
155
+ raise RuntimeError("无法从压缩包读取 micromamba")
156
+ target_bin.write_bytes(src.read())
157
+ extracted = True
158
+ except tarfile.TarError:
159
+ extracted = False
160
+
161
+ # 处理单文件二进制格式
162
+ if not extracted:
163
+ target_bin.write_bytes(tmp_path.read_bytes())
164
+
165
+ target_bin.chmod(target_bin.stat().st_mode | stat.S_IEXEC)
166
+ logger.info(f"micromamba 就绪: {target_bin}")
167
+ return str(target_bin)
168
+ finally:
169
+ if tmp_path.exists():
170
+ tmp_path.unlink(missing_ok=True)
171
+ except Exception as e:
172
+ logger.warning(f"下载/安装 micromamba 失败: {e}")
173
+
174
+ logger.error("自动配置 micromamba 失败")
175
+ return None
176
+
177
  def verify_mfa_working() -> bool:
178
+ mfa_env_name = os.environ.get("JINRIKI_MFA_ENV_NAME", "mfa")
179
  commands = []
180
 
181
  # 优先官方 conda/mamba 入口,避免 PATH 中残留的 pip 版 mfa(缺少 _kalpy)
182
  conda = shutil.which("conda")
183
  if conda:
184
+ commands.append([conda, "run", "-n", mfa_env_name, "mfa", "--help"])
185
  commands.append([conda, "run", "-n", "base", "mfa", "--help"])
186
 
187
  micromamba = shutil.which("micromamba")
188
  if micromamba:
189
+ commands.append([micromamba, "run", "-n", mfa_env_name, "mfa", "--help"])
190
  commands.append([micromamba, "run", "-n", "base", "mfa", "--help"])
191
 
192
  mamba = shutil.which("mamba")
193
  if mamba:
194
+ commands.append([mamba, "run", "-n", mfa_env_name, "mfa", "--help"])
195
  commands.append([mamba, "run", "-n", "base", "mfa", "--help"])
196
 
197
  commands.extend([
 
219
  return False
220
 
221
  # 检查是否已可用
222
+ if verify_mfa_working():
223
  logger.info("MFA 已安装���工作正常")
224
  return True
225
 
226
  logger.info("MFA 不可用,Linux 下将使用 conda/mamba 从 conda-forge 安装(官方推荐)...")
227
 
228
  try:
229
+ mfa_env_name = os.environ.get("JINRIKI_MFA_ENV_NAME", "mfa")
230
+
231
+ # 无控制台场景:自动补齐 micromamba
232
+ if not any([shutil.which("conda"), shutil.which("mamba"), shutil.which("micromamba")]):
233
+ logger.info("未检测到 conda/mamba/micromamba,开始自动配置 micromamba...")
234
+ _ensure_micromamba_available()
235
+
236
  install_attempts = []
237
 
238
  mamba = shutil.which("mamba")
239
  if mamba:
240
  install_attempts.append((
241
  "mamba",
242
+ [mamba, "install", "-y", "-n", "base", "-c", "conda-forge", "montreal-forced-aligner"],
243
  ))
244
 
245
  micromamba = shutil.which("micromamba")
246
  if micromamba:
247
+ install_attempts.append((
248
+ f"micromamba(create:{mfa_env_name})",
249
+ [micromamba, "create", "-y", "-n", mfa_env_name, "-c", "conda-forge", "montreal-forced-aligner"],
250
+ ))
251
+ install_attempts.append((
252
+ f"micromamba(install:{mfa_env_name})",
253
+ [micromamba, "install", "-y", "-n", mfa_env_name, "-c", "conda-forge", "montreal-forced-aligner"],
254
+ ))
255
  install_attempts.append((
256
  "micromamba(base)",
257
  [micromamba, "install", "-y", "-n", "base", "-c", "conda-forge", "montreal-forced-aligner"],
 
261
  if conda:
262
  install_attempts.append((
263
  "conda",
264
+ [conda, "install", "-y", "-n", "base", "-c", "conda-forge", "montreal-forced-aligner"],
265
  ))
266
 
267
  if not install_attempts:
src/mfa_runner.py CHANGED
@@ -32,18 +32,26 @@ IS_WINDOWS = platform.system() == "Windows"
32
  def _resolve_linux_mfa_command() -> Optional[list]:
33
  """解析 Linux/macOS 下可用的 MFA 命令入口,优先官方 conda/mamba 方案。"""
34
  candidates = []
 
 
 
 
 
35
 
36
  conda = shutil.which("conda")
37
  if conda:
38
- candidates.append([conda, "run", "-n", "base", "mfa"])
 
39
 
40
  micromamba = shutil.which("micromamba")
41
  if micromamba:
42
- candidates.append([micromamba, "run", "-n", "base", "mfa"])
 
43
 
44
  mamba = shutil.which("mamba")
45
  if mamba:
46
- candidates.append([mamba, "run", "-n", "base", "mfa"])
 
47
 
48
  # 兜底:系统 PATH 里的 mfa(可能是 pip 版)
49
  candidates.append(["mfa"])
 
32
  def _resolve_linux_mfa_command() -> Optional[list]:
33
  """解析 Linux/macOS 下可用的 MFA 命令入口,优先官方 conda/mamba 方案。"""
34
  candidates = []
35
+ mfa_env_name = os.environ.get("JINRIKI_MFA_ENV_NAME", "mfa")
36
+ env_names = []
37
+ for env_name in [mfa_env_name, "base"]:
38
+ if env_name and env_name not in env_names:
39
+ env_names.append(env_name)
40
 
41
  conda = shutil.which("conda")
42
  if conda:
43
+ for env_name in env_names:
44
+ candidates.append([conda, "run", "-n", env_name, "mfa"])
45
 
46
  micromamba = shutil.which("micromamba")
47
  if micromamba:
48
+ for env_name in env_names:
49
+ candidates.append([micromamba, "run", "-n", env_name, "mfa"])
50
 
51
  mamba = shutil.which("mamba")
52
  if mamba:
53
+ for env_name in env_names:
54
+ candidates.append([mamba, "run", "-n", env_name, "mfa"])
55
 
56
  # 兜底:系统 PATH 里的 mfa(可能是 pip 版)
57
  candidates.append(["mfa"])