tao-shen Claude Opus 4.6 commited on
Commit
9c623ef
ยท
1 Parent(s): 92bbd97

fix: restore foreground sync + stdout logging for HF runtime log API

Browse files

- Restore foreground sync (init runs before port 7860 opens) so HF
captures sync progress in its runtime log SSE stream
- Switch all log() functions from stderr to stdout via tee (writes to
both stdout and logfile simultaneously)
- Add startup_duration_timeout: 1h to README.md YAML to prevent HF
from timing out during init/restore
- Remove diagnostic echo/python test lines from entrypoint.sh
- Add scripts/stream_logs.py for JWT-based log streaming
- Update scripts/curl_logs.sh with cached token fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

README.md CHANGED
@@ -7,6 +7,7 @@ sdk: docker
7
  pinned: false
8
  license: mit
9
  app_port: 7860
 
10
  tags:
11
  - docker
12
  - huggingface-spaces
 
7
  pinned: false
8
  license: mit
9
  app_port: 7860
10
+ startup_duration_timeout: 1h
11
  tags:
12
  - docker
13
  - huggingface-spaces
scripts/curl_logs.sh CHANGED
@@ -1,7 +1,7 @@
1
- #!/bin/sh
2
- # ็›ดๆŽฅ็”จไฝ ็ป™็š„ๆŒ‡ไปคๆŸฅ็œ‹่ฟœ็ซฏ๏ผšBearer token + curl run/build ๆ—ฅๅฟ—
3
- # ็”จๆณ•: ./scripts/curl_logs.sh [run|build] ้ป˜่ฎคๆ‹‰ run
4
- # ้œ€่ฆ .env ไธญๆœ‰ HF_TOKEN ๆˆ–็Žฏๅขƒๅ˜้‡ HF_TOKEN
5
 
6
  set -e
7
  cd "$(dirname "$0")/.."
@@ -9,6 +9,11 @@ if [ -f .env ]; then
9
  HF_TOKEN="${HF_TOKEN:-$(awk -F= '/^HF_TOKEN=/ {print $2; exit}' .env | tr -d '\r\n" ')}"
10
  export HF_TOKEN
11
  fi
 
 
 
 
 
12
  if [ -z "$HF_TOKEN" ]; then
13
  echo "HF_TOKEN not set (add to .env or export)" >&2
14
  exit 1
@@ -17,8 +22,12 @@ fi
17
  SPACE_ID="${SPACE_ID:-tao-shen/HuggingRun}"
18
  LOG="${1:-run}"
19
 
20
- # Get container logs (SSE)
21
- # Get build logs (SSE)
22
- curl -N \
 
 
 
 
23
  -H "Authorization: Bearer $HF_TOKEN" \
24
  "https://huggingface.co/api/spaces/$SPACE_ID/logs/$LOG"
 
1
+ #!/bin/bash
2
+ # Stream HF Space runtime/build logs via JWT-based SSE endpoint
3
+ # Usage: ./scripts/curl_logs.sh [run|build] (default: run)
4
+ # Requires HF_TOKEN in .env or environment
5
 
6
  set -e
7
  cd "$(dirname "$0")/.."
 
9
  HF_TOKEN="${HF_TOKEN:-$(awk -F= '/^HF_TOKEN=/ {print $2; exit}' .env | tr -d '\r\n" ')}"
10
  export HF_TOKEN
11
  fi
12
+ # Fallback to cached token
13
+ if [ -z "$HF_TOKEN" ] && [ -f ~/.cache/huggingface/token ]; then
14
+ HF_TOKEN="$(cat ~/.cache/huggingface/token)"
15
+ export HF_TOKEN
16
+ fi
17
  if [ -z "$HF_TOKEN" ]; then
18
  echo "HF_TOKEN not set (add to .env or export)" >&2
19
  exit 1
 
22
  SPACE_ID="${SPACE_ID:-tao-shen/HuggingRun}"
23
  LOG="${1:-run}"
24
 
25
+ echo "=== Streaming ${LOG} logs for ${SPACE_ID} ===" >&2
26
+ echo "=== Method 1: Direct API (huggingface.co) ===" >&2
27
+ echo "=== Press Ctrl+C to stop ===" >&2
28
+ echo "" >&2
29
+
30
+ # Method 1: Direct HF API (works for most cases)
31
+ curl -sN \
32
  -H "Authorization: Bearer $HF_TOKEN" \
33
  "https://huggingface.co/api/spaces/$SPACE_ID/logs/$LOG"
scripts/entrypoint.sh CHANGED
@@ -1,23 +1,17 @@
1
  #!/bin/bash
2
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3
  # HuggingRun Entrypoint
4
- # 1. Start services FIRST (so HF doesn't timeout on port 7860)
5
- # 2. Run init (download + restore) in BACKGROUND
6
- # 3. Start sync-loop daemon in BACKGROUND
7
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
8
  set -e
9
 
10
- # Diagnostic: test if HF captures ANY container output
11
- echo "=== [stdout] ENTRYPOINT PID=$$ $(date -u) ==="
12
- echo "=== [stderr] ENTRYPOINT PID=$$ $(date -u) ===" >&2
13
- python3 -c "import sys; print('=== [py-stdout] PYTHON TEST ===', flush=True); print('=== [py-stderr] PYTHON TEST ===', file=sys.stderr, flush=True)"
14
-
15
  LOGFILE="/var/log/huggingrun.log"
16
  touch "$LOGFILE"
17
 
18
  log() {
19
- echo "$@" >> "$LOGFILE"
20
- echo "$@" >&2
21
  }
22
 
23
  log "========================================"
@@ -77,20 +71,20 @@ export PERSIST_PATH="${PERSIST_PATH:-/data}"
77
  ENVEOF
78
  log "[entrypoint] Wrote /etc/huggingrun.env"
79
 
80
- # Background: init (download + restore) then start sync-loop
81
- # This runs AFTER services start so HF doesn't timeout
82
- # NO redirects here โ€” stderr must flow to container stderr for HF log API
83
- (
84
- echo "[entrypoint:bg] Starting init in background ..." >&2
85
- python3 -u /opt/git_sync_daemon.py init
86
- echo "[entrypoint:bg] Init done, starting sync-loop ..." >&2
87
- exec python3 -u /opt/git_sync_daemon.py sync-loop
88
- ) &
89
- BG_PID=$!
90
- log "[entrypoint] Background init+sync PID=${BG_PID}"
91
 
92
- # Start services immediately (so HF sees port 7860)
93
  CMD="${RUN_CMD:-python3 /app/demo_app.py}"
94
- log "[entrypoint] Starting services: ${CMD}"
 
95
  log "========================================"
96
  exec $CMD
 
1
  #!/bin/bash
2
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3
  # HuggingRun Entrypoint
4
+ # 1. Init: download dataset + restore filesystem (foreground, logs visible)
5
+ # 2. Start sync-loop daemon in background
6
+ # 3. exec user command (start-server.sh opens port 7860)
7
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
8
  set -e
9
 
 
 
 
 
 
10
  LOGFILE="/var/log/huggingrun.log"
11
  touch "$LOGFILE"
12
 
13
  log() {
14
+ echo "$@" | tee -a "$LOGFILE"
 
15
  }
16
 
17
  log "========================================"
 
71
  ENVEOF
72
  log "[entrypoint] Wrote /etc/huggingrun.env"
73
 
74
+ # Step 1: Init โ€” download dataset + restore filesystem (foreground so logs are visible)
75
+ log "[entrypoint] โ”€โ”€ Step 1: Init (download + restore) โ”€โ”€"
76
+ python3 -u /opt/git_sync_daemon.py init
77
+ log "[entrypoint] โ”€โ”€ Init done โ”€โ”€"
78
+
79
+ # Step 2: Start background sync-loop daemon
80
+ log "[entrypoint] โ”€โ”€ Step 2: Starting sync-loop daemon โ”€โ”€"
81
+ python3 -u /opt/git_sync_daemon.py sync-loop &
82
+ SYNC_PID=$!
83
+ log "[entrypoint] Sync daemon PID=${SYNC_PID}"
 
84
 
85
+ # Step 3: Run user command (opens port 7860)
86
  CMD="${RUN_CMD:-python3 /app/demo_app.py}"
87
+ log "[entrypoint] โ”€โ”€ Step 3: exec user command โ”€โ”€"
88
+ log "[entrypoint] CMD=${CMD}"
89
  log "========================================"
90
  exec $CMD
scripts/stream_logs.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Stream HF Space runtime logs via JWT-authenticated SSE endpoint.
3
+
4
+ Usage:
5
+ python3 scripts/stream_logs.py [run|build]
6
+
7
+ This uses the same endpoint as the HF web UI's "Container logs" tab:
8
+ 1. Fetch JWT from https://huggingface.co/api/spaces/{id}/jwt
9
+ 2. Stream SSE from https://api.hf.space/v1/{id}/logs/{level}
10
+ """
11
+ import json
12
+ import os
13
+ import sys
14
+
15
+ def get_token():
16
+ """Get HF token from env, .env file, or cached token."""
17
+ token = os.environ.get("HF_TOKEN", "")
18
+ if not token:
19
+ env_file = os.path.join(os.path.dirname(__file__), "..", ".env")
20
+ if os.path.exists(env_file):
21
+ for line in open(env_file):
22
+ if line.startswith("HF_TOKEN="):
23
+ token = line.split("=", 1)[1].strip().strip('"').strip("'")
24
+ break
25
+ if not token:
26
+ cached = os.path.expanduser("~/.cache/huggingface/token")
27
+ if os.path.exists(cached):
28
+ token = open(cached).read().strip()
29
+ return token
30
+
31
+ def stream_logs(space_id, level="run"):
32
+ import requests
33
+
34
+ token = get_token()
35
+ if not token:
36
+ print("ERROR: HF_TOKEN not found", file=sys.stderr)
37
+ sys.exit(1)
38
+
39
+ print(f"=== Streaming {level} logs for {space_id} ===", file=sys.stderr)
40
+
41
+ # Step 1: Get JWT token
42
+ print("Fetching JWT token...", file=sys.stderr)
43
+ resp = requests.get(
44
+ f"https://huggingface.co/api/spaces/{space_id}/jwt",
45
+ headers={"Authorization": f"Bearer {token}"},
46
+ )
47
+ if resp.status_code != 200:
48
+ print(f"JWT fetch failed ({resp.status_code}): {resp.text}", file=sys.stderr)
49
+ print("Falling back to direct API...", file=sys.stderr)
50
+ # Fallback to direct API
51
+ resp = requests.get(
52
+ f"https://huggingface.co/api/spaces/{space_id}/logs/{level}",
53
+ headers={"Authorization": f"Bearer {token}"},
54
+ stream=True,
55
+ )
56
+ for line in resp.iter_lines():
57
+ if line:
58
+ line = line.decode("utf-8", errors="replace")
59
+ if line.startswith("data: "):
60
+ try:
61
+ event = json.loads(line[6:])
62
+ ts = event.get("timestamp", "")
63
+ data = event.get("data", "")
64
+ print(f"[{ts}] {data}", end="", flush=True)
65
+ except json.JSONDecodeError:
66
+ print(line, flush=True)
67
+ else:
68
+ print(line, flush=True)
69
+ return
70
+
71
+ jwt = resp.json()["token"]
72
+ print(f"JWT obtained (expires in token payload)", file=sys.stderr)
73
+
74
+ # Step 2: Stream logs via SSE
75
+ print(f"Connecting to api.hf.space SSE stream...", file=sys.stderr)
76
+ print("---", file=sys.stderr)
77
+
78
+ resp = requests.get(
79
+ f"https://api.hf.space/v1/{space_id}/logs/{level}",
80
+ headers={"Authorization": f"Bearer {jwt}"},
81
+ stream=True,
82
+ )
83
+
84
+ if resp.status_code != 200:
85
+ print(f"SSE stream failed ({resp.status_code}): {resp.text}", file=sys.stderr)
86
+ sys.exit(1)
87
+
88
+ for line in resp.iter_lines():
89
+ if line:
90
+ line = line.decode("utf-8", errors="replace")
91
+ if line.startswith("data: "):
92
+ try:
93
+ event = json.loads(line[6:])
94
+ ts = event.get("timestamp", "")
95
+ data = event.get("data", "")
96
+ print(f"[{ts}] {data}", end="", flush=True)
97
+ except json.JSONDecodeError:
98
+ print(line, flush=True)
99
+ else:
100
+ print(line, flush=True)
101
+
102
+ if __name__ == "__main__":
103
+ space_id = os.environ.get("SPACE_ID", "tao-shen/HuggingRun")
104
+ level = sys.argv[1] if len(sys.argv) > 1 else "run"
105
+ stream_logs(space_id, level)
ubuntu-server/git_sync_daemon.py CHANGED
@@ -46,7 +46,7 @@ LOGFILE = "/var/log/huggingrun.log"
46
  def log(msg):
47
  ts = time.strftime("%H:%M:%S", time.gmtime())
48
  line = f"[sync {ts}] {msg}"
49
- print(line, file=sys.stderr, flush=True)
50
  try:
51
  with open(LOGFILE, "a") as f:
52
  f.write(line + "\n")
 
46
  def log(msg):
47
  ts = time.strftime("%H:%M:%S", time.gmtime())
48
  line = f"[sync {ts}] {msg}"
49
+ print(line, flush=True)
50
  try:
51
  with open(LOGFILE, "a") as f:
52
  f.write(line + "\n")
ubuntu-server/start-server.sh CHANGED
@@ -10,11 +10,10 @@ LOGFILE="/var/log/huggingrun.log"
10
  export SSH_PORT="${SSH_PORT:-2222}"
11
  export TTYD_PORT="${TTYD_PORT:-7681}"
12
 
13
- # Log to file + stderr (HF may or may not capture stderr)
14
  log() {
15
  local msg="$*"
16
- echo "$msg" >> "$LOGFILE"
17
- echo "$msg" >&2
18
  }
19
 
20
  # โ”€โ”€ Start nginx FIRST so HF sees port 7860 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
10
  export SSH_PORT="${SSH_PORT:-2222}"
11
  export TTYD_PORT="${TTYD_PORT:-7681}"
12
 
13
+ # Log to file + stdout (HF captures container stdout/stderr)
14
  log() {
15
  local msg="$*"
16
+ echo "$msg" | tee -a "$LOGFILE"
 
17
  }
18
 
19
  # โ”€โ”€ Start nginx FIRST so HF sees port 7860 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€