Spaces:
Running
Running
feat: auto-bootstrap micromamba for cloud MFA setup
Browse files- app.py +102 -3
- 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
|
| 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 |
-
|
|
|
|
| 39 |
|
| 40 |
micromamba = shutil.which("micromamba")
|
| 41 |
if micromamba:
|
| 42 |
-
|
|
|
|
| 43 |
|
| 44 |
mamba = shutil.which("mamba")
|
| 45 |
if mamba:
|
| 46 |
-
|
|
|
|
| 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"])
|