# startup.py import os, pathlib, json, shutil BASE = pathlib.Path(__file__).parent # 通常是 /app # 可选显式指定源码目录(绝对路径),若不设会自动发现: APP_SOURCE_DIR = os.getenv("APP_SOURCE_DIR") # 入口文件候选名(可覆盖) ENTRY_HINTS = [s.strip() for s in os.getenv( "APP_ENTRY_HINTS", "script.py,app.py,main.py,server.py" ).split(",") if s.strip()] def find_source_dir() -> pathlib.Path | None: # 1) 环境变量优先 if APP_SOURCE_DIR: p = pathlib.Path(APP_SOURCE_DIR) if p.is_dir(): return p # 2) 常见候选根 candidates = [BASE, BASE / "src"] for d in BASE.iterdir(): if d.is_dir(): candidates.append(d) if (d / "src").is_dir(): candidates.append(d / "src") # 3) 在候选根里按入口名搜索 for root in candidates: for entry in ENTRY_HINTS: for path in root.rglob(entry): if "site-packages" in str(path): continue return path.parent return None SRC_READONLY = find_source_dir() if not SRC_READONLY: # 打印部分目录树帮助定位 listing = "\n".join([str(p) for p in BASE.rglob("*")][:200]) raise FileNotFoundError( f"找不到源码目录,请设置 APP_SOURCE_DIR。已尝试在 {BASE} 下自动发现。\n" f"以下为部分目录树(截断):\n{listing}" ) # 选择可写运行目录(优先 /data,失败回退 /tmp) pref = pathlib.Path(os.getenv("APP_RUNTIME_DIR", "/data/grade_query_app")) RUNTIME = pref try: RUNTIME.mkdir(parents=True, exist_ok=True) (RUNTIME / ".probe").write_text("ok", encoding="utf-8") (RUNTIME / ".probe").unlink(missing_ok=True) except Exception: RUNTIME = pathlib.Path("/tmp/grade_query_app") RUNTIME.mkdir(parents=True, exist_ok=True) # 把源码复制到可写目录,保持相对路径语义(templates 等) shutil.copytree(SRC_READONLY, RUNTIME, dirs_exist_ok=True) # 把 Secrets 写成运行期文件 cookies_txt = os.getenv("COOKIES_TXT", "").strip() students_json = os.getenv("STUDENTS_JSON", "").strip() if cookies_txt: (RUNTIME / "cookies.txt").write_text(cookies_txt, encoding="utf-8") print(f"✅ cookies.txt -> {RUNTIME/'cookies.txt'}") if students_json: try: data = json.loads(students_json) (RUNTIME / "students.json").write_text( json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8" ) print(f"✅ students.json (parsed JSON) -> {RUNTIME/'students.json'}") except Exception: (RUNTIME / "students.json").write_text(students_json, encoding="utf-8") print(f"✅ students.json (raw text) -> {RUNTIME/'students.json'}") # 暴露给 wsgi.py 使用 os.environ["APP_RUNTIME_DIR_RESOLVED"] = str(RUNTIME) for hint in ENTRY_HINTS: if (RUNTIME / hint).exists(): os.environ["APP_ENTRY_FILE_RESOLVED"] = hint break print(f"ℹ️ Using runtime dir: {RUNTIME}, entry: {os.environ.get('APP_ENTRY_FILE_RESOLVED','(auto)')}")