File size: 10,603 Bytes
74bf532 8bebe44 74bf532 8bebe44 74bf532 8bebe44 74bf532 8f14c1d 74bf532 8f14c1d 74bf532 8f14c1d 74bf532 8f14c1d 74bf532 2e39262 74bf532 2e39262 74bf532 8f14c1d 74bf532 b56a933 74bf532 b56a933 74bf532 8f14c1d 74bf532 8f14c1d 74bf532 8f14c1d 74bf532 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | """
Push URAAS to Hugging Face Spaces β Lordkiki/APA-URAAS
Usage:
python scripts/push_to_hf.py
What it does:
1. Logs you into HF (paste your write token when prompted)
2. Stages a clean copy of the project:
Dockerfile.hf β Dockerfile (HF Spaces Dockerfile, not the prod one)
README.hf.md β README.md (has the HF Space frontmatter)
3. Uploads everything to the Space via huggingface_hub.upload_folder
4. HF triggers an auto-build β app is live in ~5 minutes
"""
import os
import shutil
import sys
import tempfile
# ββ Config ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
REPO_ID = "Lordkiki/APA-URAAS"
REPO_TYPE = "space"
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Directories/files to never push
IGNORE_DIRS = {
".git", ".claude", "__pycache__", ".pytest_cache", ".mypy_cache",
"storage", "logs", "data", "backups", "node_modules",
".venv", "venv", "env",
}
IGNORE_FILES = {
".env", ".env.prod", ".env.prod.example",
"uraas.db", # DB lives on /data in HF, not in image
"Dockerfile", # replaced by Dockerfile.hf
"README.md", # replaced by README.hf.md (has HF frontmatter)
"docker-compose.yml",
"docker-compose.prod.yml",
"docker-compose.replica.yml",
"docker-compose.demo.yml",
}
IGNORE_EXTS = {
".pyc", ".pyo", ".pyd",
# Database files/backups β belt-and-suspenders beyond the exact
# "uraas.db" name check below and the startswith("uraas.db") check,
# since a missed pattern here means real crawled data (author names,
# DOIs, institutional affiliations, emails) goes to a PUBLIC repo.
# Confirmed this actually happened (2026-07-19): uraas.db.bak, a
# 1819-item/14MB snapshot, was uploaded because only "uraas.db" itself
# was excluded, not the ".bak" variant β deleted from the live Space
# after the fact, but should never have gone up in the first place.
".bak", ".backup", ".old", ".orig", ".db", ".sqlite", ".sqlite3",
}
def should_skip(rel_path: str, is_dir: bool) -> bool:
parts = rel_path.replace("\\", "/").split("/")
name = parts[-1]
if is_dir:
return name in IGNORE_DIRS
# Catches any suffix variant regardless of extension β uraas.db-wal,
# uraas.db-shm, uraas.db-journal, timestamped backups like
# uraas.db.20260629, etc. β not just the exact names/extensions above.
if name.startswith("uraas.db"):
return True
return (
name in IGNORE_FILES
or os.path.splitext(name)[1].lower() in IGNORE_EXTS
)
def stage_project(src: str, dst: str) -> int:
"""Copy src β dst with HF-specific renames and exclusions."""
count = 0
for root, dirs, files in os.walk(src):
rel_root = os.path.relpath(root, src)
# Prune excluded directories in-place so os.walk doesn't recurse
dirs[:] = [
d for d in dirs
if not should_skip(
os.path.join(rel_root, d) if rel_root != "." else d,
is_dir=True,
)
]
for fname in files:
rel = os.path.join(rel_root, fname) if rel_root != "." else fname
if should_skip(rel, is_dir=False):
continue
src_file = os.path.join(root, fname)
# HF-specific renames
if fname == "Dockerfile.hf":
dest_rel = os.path.join(os.path.dirname(rel), "Dockerfile") if os.path.dirname(rel) else "Dockerfile"
elif fname == "README.hf.md":
dest_rel = "README.md"
else:
dest_rel = rel
dst_file = os.path.join(dst, dest_rel)
os.makedirs(os.path.dirname(dst_file), exist_ok=True)
shutil.copy2(src_file, dst_file)
count += 1
return count
def main():
# ββ Ensure huggingface_hub is available ββββββββββββββββββββββββββββββββ
try:
from huggingface_hub import HfApi, login
except ImportError:
print("Installing huggingface_hub...")
os.system(f"{sys.executable} -m pip install huggingface_hub -q")
from huggingface_hub import HfApi, login # type: ignore
_SEP = "=" * 55
print()
print(_SEP)
print(" URAAS -> Hugging Face Spaces")
print(f" Space: {REPO_ID}")
print(_SEP)
# ββ Auth βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Live-verified 2026-08-04: with no HF_TOKEN env var, this used to always
# call login() with no token, which prompts interactively for one β in
# any non-interactive shell (CI, a background task, this script run
# without a TTY) that blocks forever waiting for input that can never
# arrive, even when a valid token is already cached from a previous
# `huggingface-cli login` on the same machine. Try the cached credential
# first; only fall back to an interactive prompt when actually attached
# to a terminal.
token = os.getenv("HF_TOKEN")
if token:
login(token=token, add_to_git_credential=True)
print(" Logged in via HF_TOKEN env var.")
else:
from huggingface_hub import HfFolder
cached = HfFolder.get_token()
if cached:
try:
HfApi().whoami(token=cached)
print(" Logged in via cached HF credential.")
except Exception:
cached = None
if not cached:
if not sys.stdin.isatty():
print()
print(" No HF_TOKEN env var, no cached credential, and no terminal to prompt on.")
print(" Set HF_TOKEN=hf_... and re-run, e.g.:")
print(f" HF_TOKEN=hf_... python {os.path.basename(__file__)}")
sys.exit(1)
print()
print(" Paste your HF write token below.")
print(" (Get one at: https://huggingface.co/settings/tokens)")
print()
login(add_to_git_credential=True)
api = HfApi()
# ββ Stage files ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
print()
print("Staging project filesβ¦")
with tempfile.TemporaryDirectory() as staging:
n = stage_project(REPO_ROOT, staging)
staged_names = os.listdir(staging)
print(f" {n} files staged across {len(staged_names)} top-level items")
# Sanity checks
has_dockerfile = "Dockerfile" in staged_names
has_readme = "README.md" in staged_names
has_start_sh = os.path.exists(os.path.join(staging, "scripts", "start_hf.sh"))
print(f" Dockerfile : {'OK' if has_dockerfile else 'MISSING -- check Dockerfile.hf exists'}")
print(f" README.md : {'OK' if has_readme else 'MISSING -- check README.hf.md exists'}")
print(f" start_hf.sh : {'OK' if has_start_sh else 'MISSING -- check scripts/start_hf.sh'}")
if not has_dockerfile:
print()
print("ERROR: Dockerfile missing from staging. Aborting.")
sys.exit(1)
# ββ Upload βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Skip HF's remote YAML validation β it times out on some networks.
# README.hf.md is already valid so this is safe to skip.
api._validate_yaml = lambda *a, **kw: None
print()
print(f"Uploading to {REPO_ID}...")
api.upload_folder(
folder_path=staging,
repo_id=REPO_ID,
repo_type=REPO_TYPE,
commit_message="Deploy URAAS β African Research Archival & Analytics System",
)
# ββ Done βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
print()
print(_SEP)
print(" Upload complete! Build starting on HF (~5 min).")
print()
print(" Watch build: https://huggingface.co/spaces/Lordkiki/APA-URAAS")
print(" App URL : https://lordkiki-apa-uraas.hf.space")
print()
print(" --- Secrets to set in Space Settings -> Variables & Secrets ---")
secrets = [
("URAAS_ENV", "production"),
("DASHBOARD_SECRET_KEY", "307790fc5aff3fe1e766303f6b94e2fc28c831582bfba5b34802e2c9cbbac0ce"),
("ADMIN_USERNAME", "admin"),
("ADMIN_PASSWORD_HASH", "scrypt:32768:8:1$r2KZFX32rJ2twbfV$16f394a253c2b505a215ff2747f7dafbb098eb0eb8b4e6bb9fb521f0ea38af8ce71f33d2354245d68cc383678257367bf410aa45f835b5e848bff95a746878c8"),
("VIEWER_USERNAME", "viewer"),
("VIEWER_PASSWORD_HASH", "scrypt:32768:8:1$M8OjWxX64B38akos$2803ad5b29508c4d69df115579630b2a4cbdf2c8406157598c088d5511a7b3d79f91399035040660705413c63fdc41aa0d020cbd7b45038c8ed51d20092ea609"),
("SMTP_HOST", "smtp.gmail.com"),
("SMTP_PORT", "587"),
("SMTP_USE_TLS", "true"),
("SMTP_USER", "lawalgiyath200716@gmail.com"),
("SMTP_PASSWORD", "ufwqbdrecpfrzppn"),
("SMTP_FROM", "URAAS UNILAG <lawalgiyath200716@gmail.com>"),
("DASHBOARD_BASE_URL", "https://lordkiki-apa-uraas.hf.space"),
("DASHBOARD_CORS_ORIGINS", "https://lordkiki-apa-uraas.hf.space"),
("ARK_NAAN", "99999"),
("ARK_SHOULDER", "z1"),
("OPENALEX_MAILTO", "lawalgiyath200716@gmail.com"),
("DSPACE_API_URL", "https://api-ir.unilag.edu.ng/server"),
("DSPACE_USERNAME", "<professor email β set as Secret, not Variable>"),
("DSPACE_PASSWORD", "<professor password β set as Secret, not Variable>"),
]
max_k = max(len(k) for k, _ in secrets)
for k, v in secrets:
print(f" {k:<{max_k}} = {v}")
print()
print(" Admin login : admin / URAAS2024demo")
print(" Viewer login: viewer / view2024")
print(_SEP)
if __name__ == "__main__":
main()
|