mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 18:25:26 -04:00
fix: detect HuggingFace token when downloading cookbook models (#3459)
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
"""cookbook_helpers.py — validators + small helpers shared by the cookbook routes.
|
"""cookbook_helpers.py — validators + small helpers shared by the cookbook routes.
|
||||||
Extracted from cookbook_routes.py; the routes module imports the symbols it needs."""
|
Extracted from cookbook_routes.py; the routes module imports the symbols it needs."""
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import ntpath
|
import ntpath
|
||||||
import os
|
import os
|
||||||
import posixpath
|
import posixpath
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -90,6 +92,24 @@ def _validate_token(v: str | None) -> str | None:
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
def load_stored_hf_token(*, state_path: Path | str | None = None) -> str:
|
||||||
|
"""Return the decrypted HF token from cookbook_state.json, else env fallback."""
|
||||||
|
path = Path(state_path) if state_path else Path(os.environ.get("DATA_DIR", "data")) / "cookbook_state.json"
|
||||||
|
token = ""
|
||||||
|
if path.exists():
|
||||||
|
try:
|
||||||
|
state = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
env = state.get("env") if isinstance(state, dict) else {}
|
||||||
|
if isinstance(env, dict) and env.get("hfToken"):
|
||||||
|
from src.secret_storage import decrypt
|
||||||
|
token = decrypt(env.get("hfToken") or "")
|
||||||
|
except Exception:
|
||||||
|
token = ""
|
||||||
|
if not token:
|
||||||
|
token = (os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") or "").strip()
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
def _validate_local_dir(v: str | None) -> str | None:
|
def _validate_local_dir(v: str | None) -> str | None:
|
||||||
if v is None or v == "":
|
if v is None or v == "":
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ from routes.cookbook_helpers import (
|
|||||||
_ps_squote, _bash_squote, _validate_serve_cmd, _parse_serve_phase,
|
_ps_squote, _bash_squote, _validate_serve_cmd, _parse_serve_phase,
|
||||||
_safe_env_prefix, _local_tooling_path_export, _append_serve_preflight_exit_lines,
|
_safe_env_prefix, _local_tooling_path_export, _append_serve_preflight_exit_lines,
|
||||||
_append_serve_exit_code_lines, _append_llama_cpp_linux_accel_build_lines, _cached_model_scan_script,
|
_append_serve_exit_code_lines, _append_llama_cpp_linux_accel_build_lines, _cached_model_scan_script,
|
||||||
|
load_stored_hf_token,
|
||||||
|
_append_vllm_linux_preflight_lines, _ollama_bind_from_cmd, _pip_install_fallback_chain,
|
||||||
|
_pip_install_no_cache, _user_shell_path_bootstrap, _venv_safe_local_pip_install_cmd,
|
||||||
|
_diagnose_serve_output, run_ssh_command_async,
|
||||||
_ollama_bind_from_cmd, _pip_install_fallback_chain, _pip_install_no_cache,
|
_ollama_bind_from_cmd, _pip_install_fallback_chain, _pip_install_no_cache,
|
||||||
_user_shell_path_bootstrap, _venv_safe_local_pip_install_cmd,
|
_user_shell_path_bootstrap, _venv_safe_local_pip_install_cmd,
|
||||||
ModelDownloadRequest, ServeRequest,
|
ModelDownloadRequest, ServeRequest,
|
||||||
@@ -234,14 +238,7 @@ def setup_cookbook_routes() -> APIRouter:
|
|||||||
return state
|
return state
|
||||||
|
|
||||||
def _load_stored_hf_token() -> str:
|
def _load_stored_hf_token() -> str:
|
||||||
if not _cookbook_state_path.exists():
|
return load_stored_hf_token(state_path=_cookbook_state_path)
|
||||||
return ""
|
|
||||||
try:
|
|
||||||
state = json.loads(_cookbook_state_path.read_text(encoding="utf-8"))
|
|
||||||
env = state.get("env") if isinstance(state, dict) else {}
|
|
||||||
return _decrypt_secret(env.get("hfToken") if isinstance(env, dict) else "")
|
|
||||||
except Exception:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _cookbook_ssh_dir() -> Path:
|
def _cookbook_ssh_dir() -> Path:
|
||||||
# The Docker image keeps cookbook keys under /app/.ssh; that path only
|
# The Docker image keeps cookbook keys under /app/.ssh; that path only
|
||||||
|
|||||||
@@ -2054,13 +2054,14 @@ async def _cookbook_env_for_host(host: str) -> Dict[str, Any]:
|
|||||||
else:
|
else:
|
||||||
env_prefix = f'eval "$(conda shell.bash hook)" && conda activate {env_path}'
|
env_prefix = f'eval "$(conda shell.bash hook)" && conda activate {env_path}'
|
||||||
|
|
||||||
|
from routes.cookbook_helpers import load_stored_hf_token
|
||||||
return {
|
return {
|
||||||
"env_prefix": env_prefix,
|
"env_prefix": env_prefix,
|
||||||
"env_type": env_kind,
|
"env_type": env_kind,
|
||||||
"env_path": env_path,
|
"env_path": env_path,
|
||||||
"gpus": env_root.get("gpus") or "",
|
"gpus": env_root.get("gpus") or "",
|
||||||
"platform": platform,
|
"platform": platform,
|
||||||
"hf_token": env_root.get("hfToken") or "",
|
"hf_token": load_stored_hf_token(),
|
||||||
"ssh_port": ssh_port,
|
"ssh_port": ssh_port,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1506,12 +1506,10 @@ export function _hwfitInit() {
|
|||||||
clearTimeout(_hwfitDebounce);
|
clearTimeout(_hwfitDebounce);
|
||||||
_hwfitDebounce = setTimeout(() => _hwfitFetch(), 400);
|
_hwfitDebounce = setTimeout(() => _hwfitFetch(), 400);
|
||||||
});
|
});
|
||||||
// HF Token
|
// HF token save is owned by cookbook.js (_wireTabEvents) — do not wire a
|
||||||
const hfToken = document.getElementById('hwfit-hftoken');
|
// second change/input handler here. The old duplicate ran after cookbook.js
|
||||||
if (hfToken) {
|
// cleared the input on save and overwrote _envState.hfToken with "", so the
|
||||||
hfToken.addEventListener('change', () => { _envState.hfToken = hfToken.value.trim(); _persistEnvState(); });
|
// debounced state sync never persisted the token to cookbook_state.json.
|
||||||
hfToken.addEventListener('input', () => { _envState.hfToken = hfToken.value.trim(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild all server select dropdowns with current servers
|
// Rebuild all server select dropdowns with current servers
|
||||||
function _rebuildServerSelect() {
|
function _rebuildServerSelect() {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
"""Cookbook HF token persistence and lookup."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from routes.cookbook_helpers import load_stored_hf_token
|
||||||
|
from src.secret_storage import encrypt
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_stored_hf_token_reads_encrypted_state(tmp_path, monkeypatch):
|
||||||
|
monkeypatch.setenv("DATA_DIR", str(tmp_path))
|
||||||
|
state_path = tmp_path / "cookbook_state.json"
|
||||||
|
state_path.write_text(
|
||||||
|
json.dumps({"env": {"hfToken": encrypt("hf_test_token_12345")}}),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
assert load_stored_hf_token() == "hf_test_token_12345"
|
||||||
|
assert load_stored_hf_token(state_path=state_path) == "hf_test_token_12345"
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_stored_hf_token_falls_back_to_env_when_state_missing(tmp_path, monkeypatch):
|
||||||
|
monkeypatch.setenv("DATA_DIR", str(tmp_path))
|
||||||
|
monkeypatch.setenv("HF_TOKEN", "hf_from_env")
|
||||||
|
assert load_stored_hf_token() == "hf_from_env"
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_stored_hf_token_prefers_state_over_env(tmp_path, monkeypatch):
|
||||||
|
monkeypatch.setenv("DATA_DIR", str(tmp_path))
|
||||||
|
monkeypatch.setenv("HF_TOKEN", "hf_from_env")
|
||||||
|
state_path = tmp_path / "cookbook_state.json"
|
||||||
|
state_path.write_text(
|
||||||
|
json.dumps({"env": {"hfToken": encrypt("hf_from_state")}}),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
assert load_stored_hf_token() == "hf_from_state"
|
||||||
Reference in New Issue
Block a user