Spaces:
Configuration error
Configuration error
| # push_hf.sh — Push das PX-Repo zu HuggingFace Space (nur Code, keine LFS-Orphans). | |
| # | |
| # Plan 2026-07-07: vermeidet 1 GB Storage-Limit auf HF free tier. | |
| # 1. Erstellt einen frischen Sparse-Branch von ui-styling, der | |
| # scratches/, telemetry/, sessions/, local_debug.log UND alle | |
| # LFS-tracked files (logs/local_debug.log) AUS dem Index nimmt. | |
| # 2. Pusht diesen Branch als "main" zu einem NEUEN HF Space. | |
| # 3. Original-Branch ui-styling und Working-Tree bleiben 100% unangetastet. | |
| # | |
| # Verwendung: | |
| # bash push_hf.sh # nutzt .env + px-explorer-v4 default | |
| # HF_TOKEN=hf_xxx SPACE_NAME=px-explorer-v4 ./push_hf.sh # override aus env | |
| # | |
| # Standard-Space: px-explorer-v4 (User-Instruction 2026-07-09: | |
| # "wir bleiben für immer auf v4. mal merken."). | |
| # HF free-tier lehnt neue gradio-Spaces seit 2026-07 mit 402 ab. | |
| # | |
| # Voraussetzungen: | |
| # - git lfs installiert (git-lfs >= 2.x) | |
| # - HF_TOKEN env var (write-Scope auf User-Account) | |
| # - Lokal: branch 'ui-styling' existiert mit dem Code | |
| # | |
| # Lessons Learned (2026-07-07): | |
| # - HF free tier: 1 GB Storage account-weit. LFS-Orphans zählen mit. | |
| # - DELETE /api/spaces/{id} → 404. Space-Löschung nur via Browser. | |
| # - POST /api/spaces → 404. POST /api/repos/create → 200 (korrekter Endpoint). | |
| # - 'git push --force' ist OK für HF Spaces (kein lokales Tracking). | |
| # - 'git worktree add' scheitert wenn Branch im Hauptverzeichnis gecheckt ist. | |
| set -euo pipefail | |
| # ── .env laden (Plan 2026-07-09) ──────────────────────────────── | |
| # Lokale Credentials liegen in .env (HF_TOKEN), .env ist .gitignored. | |
| # Falls .env nicht existiert, wird HF_TOKEN aus dem Env erwartet. | |
| if [[ -f .env ]]; then | |
| set -a | |
| # shellcheck disable=SC1091 | |
| source .env | |
| set +a | |
| fi | |
| # ── Konfiguration ──────────────────────────────────────────────── | |
| BRANCH_LOCAL="ui-styling" | |
| BRANCH_HF="ui-styling-hf" | |
| HF_USER="neuralworm" | |
| # User-Instruction 2026-07-09: "wir bleiben für immer auf v4. mal merken." | |
| # → v4 ist der permanente Space. Neue Spaces (v5/v6) werden seit 2026-07 | |
| # von HF free-tier mit 402 (PRO-only) abgelehnt. | |
| SPACE_NAME="${SPACE_NAME:-px-explorer-v4}" | |
| REMOTE_URL="https://${HF_USER}:${HF_TOKEN}@huggingface.co/spaces/${HF_USER}/${SPACE_NAME}" | |
| # Was NIE in den Push soll (HF free tier 1 GB, diese Files füllen das Limit) | |
| # ACHTUNG: sessions_bak3/temp_media/ enthält .png-Files die HF pre-receive-hook | |
| # auch dann ablehnt, wenn sie nicht referenziert sind — Hook checkt das WORKING-TREE. | |
| EXCLUDE_PATHS=( | |
| "scratches/" # 575M emergence-Experimente | |
| "telemetry/" # 20M Telemetrie-Snapshots | |
| "sessions/" # 28M User-Chat-Historien (Daten, nicht Code) | |
| "sessions_bak/" # Session-Backup (Daten, nicht Code) | |
| "sessions_bak3/" # Session-Backup mit PNGs in temp_media/ | |
| "local_debug.log" # 1.8M Server-Debug-Log | |
| "logs/local_debug.log" # LFS-pointer (verhindert LFS-Upload komplett) | |
| ) | |
| # ── Sanity-Checks ──────────────────────────────────────────────── | |
| if [ -z "${HF_TOKEN:-}" ]; then | |
| echo "ERROR: HF_TOKEN env var nicht gesetzt. Abbruch." >&2 | |
| exit 1 | |
| fi | |
| if [ ! -d ".git" ]; then | |
| echo "ERROR: nicht in einem Git-Repo ($(pwd)). Abbruch." >&2 | |
| exit 1 | |
| fi | |
| if ! git rev-parse --verify "$BRANCH_LOCAL" >/dev/null 2>&1; then | |
| echo "ERROR: branch '$BRANCH_LOCAL' existiert nicht lokal. Abbruch." >&2 | |
| exit 1 | |
| fi | |
| # Aktuellen Working-Tree-Status checken (nichts darf modified sein) | |
| if [ -n "$(git status --porcelain)" ]; then | |
| echo "WARN: Working-Tree hat uncommitted changes:" | |
| git status --short | |
| if [ -t 0 ]; then | |
| # Interaktiv: frage User | |
| read -p "Trotzdem fortfahren? (j/N) " -n 1 -r | |
| echo | |
| if [[ ! $REPLY =~ ^[Jj]$ ]]; then | |
| echo "Abbruch." | |
| exit 1 | |
| fi | |
| else | |
| # Non-interaktiv (Pipe/Subshell): fortfahren | |
| echo "Non-interactive mode: fortfahren..." | |
| fi | |
| fi | |
| # ── HF Space erstellen (idempotent: skip wenn schon da) ──────── | |
| echo "=== HF Space '$SPACE_NAME' ===" | |
| SPACE_CHECK=$(curl -s -o /dev/null -w "%{http_code}" \ | |
| -H "Authorization: Bearer ${HF_TOKEN}" \ | |
| "https://huggingface.co/api/spaces/${HF_USER}/${SPACE_NAME}") | |
| if [ "$SPACE_CHECK" = "200" ]; then | |
| echo "Space existiert bereits. usedStorage:" | |
| curl -s -H "Authorization: Bearer ${HF_TOKEN}" \ | |
| "https://huggingface.co/api/spaces/${HF_USER}/${SPACE_NAME}" \ | |
| | python3 -c "import sys, json; d=json.load(sys.stdin); print(f' {d.get(\"usedStorage\", 0)/1024/1024:.1f} MB / 1024 MB (1 GB free tier)')" | |
| else | |
| echo "Erstelle Space..." | |
| CREATE_RESP=$(curl -s -w "\n%{http_code}" -X POST \ | |
| -H "Authorization: Bearer ${HF_TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{\"name\":\"${SPACE_NAME}\",\"type\":\"space\",\"sdk\":\"gradio\"}" \ | |
| "https://huggingface.co/api/repos/create") | |
| HTTP_CODE=$(echo "$CREATE_RESP" | tail -n1) | |
| if [ "$HTTP_CODE" != "200" ]; then | |
| echo "ERROR: Space-Erstellung fehlgeschlagen (HTTP $HTTP_CODE):" >&2 | |
| echo "$CREATE_RESP" | head -n1 >&2 | |
| exit 1 | |
| fi | |
| echo "Space erstellt: https://huggingface.co/spaces/${HF_USER}/${SPACE_NAME}" | |
| fi | |
| # ── Sparse-Branch vorbereiten ─────────────────────────────────── | |
| echo "=== Sparse-Branch '$BRANCH_HF' ===" | |
| # Wenn Branch existiert → löschen (sauberer Reset) | |
| # Erst zurück zu BRANCH_LOCAL falls ui-styling-hf ausgecheckt ist | |
| CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) | |
| if [ "$CURRENT_BRANCH" = "$BRANCH_HF" ]; then | |
| echo "Branch '$BRANCH_HF' ist aktuell gecheckt — switche zu '$BRANCH_LOCAL'..." | |
| # -f: force, falls working-tree-files nicht im Ziel-branch-index sind | |
| # (passiert nach `git rm --cached` auf einem Branch, weil die Files nur | |
| # aus dem Index, nicht aus dem Working-Tree, entfernt wurden). | |
| git checkout -f "$BRANCH_LOCAL" >/dev/null | |
| fi | |
| if git rev-parse --verify "$BRANCH_HF" >/dev/null 2>&1; then | |
| echo "Lösche existierenden Branch '$BRANCH_HF'..." | |
| git branch -D "$BRANCH_HF" >/dev/null | |
| fi | |
| # WICHTIG: Wir bauen den Sparse-Branch als ORPHAN (kein Parent), damit | |
| # die komplette History von $BRANCH_LOCAL (die noch alle LFS-tracked files | |
| # in der History referenziert) NICHT mit-gepusht wird. Sonst triggert der | |
| # pre-receive-hook "contains binary files" für ALLE je committeten | |
| # .png/.bin files im Branch — auch wenn wir sie im aktuellen Index nicht | |
| # haben. | |
| echo "Branch '$BRANCH_HF' von '$BRANCH_LOCAL' erstellt (ORPHAN, ohne History)." | |
| git checkout --orphan "$BRANCH_HF" >/dev/null | |
| git checkout "$BRANCH_LOCAL" -- . 2>&1 | head -3 || true | |
| # Excluded paths aus Index entfernen (Working-Tree bleibt unangetastet) | |
| EXCLUDED_COUNT=0 | |
| for path in "${EXCLUDE_PATHS[@]}"; do | |
| # Pfad-spezifische Behandlung | |
| case "$path" in | |
| */) | |
| # Verzeichnis | |
| if [ -d "$path" ]; then | |
| # git rm -r kann bei leeren/non-existent Pfaden failen — wir | |
| # unterdrücken ALLE outputs und zählen nur die echten deletes. | |
| RM_OUT=$(git rm -r --cached "$path" 2>&1 | grep -c "^rm " || true) | |
| EXCLUDED_COUNT=$((EXCLUDED_COUNT + RM_OUT)) | |
| fi | |
| ;; | |
| *) | |
| # Einzelne Datei | |
| if [ -f "$path" ]; then | |
| if git rm --cached "$path" >/dev/null 2>&1; then | |
| EXCLUDED_COUNT=$((EXCLUDED_COUNT + 1)) | |
| fi | |
| fi | |
| ;; | |
| esac | |
| done | |
| echo "Aus Index entfernt: ${EXCLUDED_COUNT} files (${EXCLUDE_PATHS[*]})" | |
| # Falls es keine Änderungen gibt (alles schon exkludiert) — leeren Commit | |
| if [ -z "$(git status --porcelain)" ]; then | |
| echo "Keine Änderungen — Working-Tree war bereits clean." | |
| else | |
| git commit -m "push_hf: sparse-branch für HF-Push (nur Code, 0 LFS)" >/dev/null | |
| echo "Sparse-Commit erstellt." | |
| fi | |
| # Code-Size + LFS-Check (ohne pipefail — xargs+stat kann non-zero exits haben | |
| # bei symlinks oder special files; das ist hier OK) | |
| set +o pipefail | |
| CODE_BYTES=$(git ls-files | xargs -I {} stat -c '%s' {} 2>/dev/null | awk '{s+=$1} END {print s+0}') | |
| set -o pipefail | |
| CODE_MB=$(awk -v b="$CODE_BYTES" 'BEGIN {printf "%.2f", b/1024/1024}') | |
| LFS_COUNT=$(git lfs ls-files 2>/dev/null | wc -l) | |
| echo "Push-Größe: ${CODE_MB} MB Code, ${LFS_COUNT} LFS-Files." | |
| # ── Push ──────────────────────────────────────────────────────── | |
| echo "=== Push zu $REMOTE_URL ===" | |
| # Remote-URL setzen/updaten | |
| git remote set-url hf "$REMOTE_URL" 2>/dev/null || git remote add hf "$REMOTE_URL" | |
| # Force-Push (HF Spaces haben initial-commit, force ist OK) | |
| # GIT_LFS_SKIP_PUSH=true verhindert dass git-lfs alle 238 LFS-Objekte im | |
| # Local-Cache hochlädt (auch wenn der sparse-branch 0 LFS-Referenzen hat). | |
| # Bei sparse-branches mit 0 LFS-files wollen wir NUR plain git push. | |
| GIT_LFS_SKIP_PUSH=true PUSH_OUTPUT=$(GIT_LFS_SKIP_PUSH=true git push --force hf "$BRANCH_HF:main" 2>&1) || PUSH_EXIT=$? | |
| PUSH_EXIT=${PUSH_EXIT:-0} | |
| if [ "$PUSH_EXIT" = "0" ]; then | |
| echo "" | |
| echo "✓ Push erfolgreich!" | |
| echo " Space: https://huggingface.co/spaces/${HF_USER}/${SPACE_NAME}" | |
| echo " Code: ${CODE_MB} MB (kein LFS, keine Scratches)" | |
| # Verify final storage | |
| sleep 3 | |
| FINAL_STORAGE=$(curl -s -H "Authorization: Bearer ${HF_TOKEN}" \ | |
| "https://huggingface.co/api/spaces/${HF_USER}/${SPACE_NAME}" \ | |
| | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('usedStorage', 0)/1024/1024)" 2>/dev/null) | |
| echo " Storage: ${FINAL_STORAGE:-?} MB / 1024 MB" | |
| else | |
| echo "✗ Push fehlgeschlagen (exit $PUSH_EXIT):" >&2 | |
| echo "$PUSH_OUTPUT" | tail -10 >&2 | |
| echo " Mögliche Ursache: HF-Account-Storage-Limit erreicht." >&2 | |
| echo " Cleanup: alten Space im Browser löschen, dann SPACE_NAME ändern." >&2 | |
| # Cleanup trotzdem | |
| git checkout -f "$BRANCH_LOCAL" >/dev/null 2>&1 || true | |
| git branch -D "$BRANCH_HF" >/dev/null 2>&1 || true | |
| exit 1 | |
| fi | |
| # ── Zurück zu ui-styling ─────────────────────────────────────── | |
| echo "" | |
| echo "=== Cleanup ===" | |
| # -f: force, weil unversionierte Files (local_debug.log etc.) im Working-Tree | |
| # den Checkout sonst blocken — sie existieren auf ui-styling als getrackt, | |
| # wurden aber im Sparse-Branch aus dem Index entfernt. Working-Tree bleibt | |
| # unangetastet (git checkout -f überschreibt nur Index-Mismatches). | |
| git checkout -f "$BRANCH_LOCAL" >/dev/null | |
| git branch -D "$BRANCH_HF" >/dev/null | |
| echo "Zurück auf '$BRANCH_LOCAL'. Sparse-Branch gelöscht." | |
| echo "Working-Tree ist 100% identisch zu vorher (Index-Cleanup war --cached-only)." | |