feat: colab notebook - cloudflare tunnel for external dashboard access

Server cell sets LUKESCRIBE_TUNNEL=cloudflare so the app built-in CloudflareTunnel (trycloudflare quick tunnel) starts alongside the server. After health+auth checks the cell polls server.log for the trycloudflare URL, prints it with /docs dashboard and /health links, and verifies external reachability.

cloudflared binary is downloaded in the install cell (graceful skip on failure). Troubleshooting section documents the temporary/public nature of the link.
This commit is contained in:
2026-08-12 19:41:16 +09:00
parent 457c67df2c
commit 0ed2ea6160
2 changed files with 42 additions and 3 deletions
+39
View File
@@ -133,6 +133,10 @@ def cells() -> list[dict]:
"os.environ['LD_LIBRARY_PATH'] = ':'.join(lib_dirs) + ':' + os.environ.get('LD_LIBRARY_PATH', '')\n"
"print('LD_LIBRARY_PATH:', os.environ['LD_LIBRARY_PATH'])\n"
"print('설치 완료')\n"
"\n"
"# Cloudflare 터널용 cloudflared 바이너리 (실패해도 진행 — 터널 없이 로컬 사용 가능)\n"
"!wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -O /usr/local/bin/cloudflared || echo 'cloudflared 다운로드 실패 — 터널 없이 계속합니다'\n"
"!chmod +x /usr/local/bin/cloudflared 2>/dev/null; cloudflared --version 2>&1 | head -1 || echo 'cloudflared 미설치 — 터널 생략'\n"
"!which luke-scribe && luke-scribe --help 2>&1 | head -8\n"
),
code(
@@ -222,6 +226,8 @@ def cells() -> list[dict]:
"os.environ['LUKESCRIBE_QUEUE_BACKEND'] = 'inproc'\n"
"# in-proc 서버가 자체 워커 스레드로 큐를 소비 (업로드 → 완료까지 API 단독 처리)\n"
"os.environ['LUKESCRIBE_AUTO_WORKER'] = 'true'\n"
"# Cloudflare Quick Tunnel — 외부 접속 링크 발급 (cloudflared는 셀 3에서 설치)\n"
"os.environ['LUKESCRIBE_TUNNEL'] = 'cloudflare'\n"
"\n"
"!mkdir -p /content/logs\n"
"!nohup luke-scribe serve --port 8000 > /content/logs/server.log 2>&1 &\n"
@@ -248,6 +254,32 @@ def cells() -> list[dict]:
" print('키 인증 실패 (HTTP', e.code, ') — 서버 로그:')\n"
" print(open('/content/logs/server.log').read()[-1500:])\n"
" raise SystemExit('키 인증 실패 — RAW_KEY 캡처/키 파일 확인')\n"
"\n"
"# ── Cloudflare 터널 URL 캡처 + 외부 접속 검증 (서버 로그의 trycloudflare URL) ──\n"
"import re\n"
"TUNNEL_URL = None\n"
"for _ in range(60):\n"
" log = open('/content/logs/server.log', encoding='utf-8', errors='replace').read()\n"
" m = re.search(r'https://[a-z0-9-]+\\.trycloudflare\\.com', log)\n"
" if m:\n"
" TUNNEL_URL = m.group(0)\n"
" break\n"
" time.sleep(2)\n"
"\n"
"if TUNNEL_URL:\n"
" print('🌐 Cloudflare 터널 (외부 접속):', TUNNEL_URL)\n"
" print(' 대시보드 (API Docs):', TUNNEL_URL + '/docs')\n"
" print(' 상태 (health):', TUNNEL_URL + '/health')\n"
" try:\n"
" ext = urllib.request.urlopen(TUNNEL_URL + '/health', timeout=15)\n"
" print(' 외부 접속 검증 OK (HTTP', ext.status, ')')\n"
" except Exception as exc:\n"
" print(' 외부 접속 검증 실패:', exc)\n"
" print(' ※ 임시 링크 — Colab 세션 종료 시 닫힘. API 호출에는 RAW_KEY(X-API-Key) 필요.')\n"
"else:\n"
" print('터널 URL 미감지 (cloudflared 미설치/연결 실패) — 서버 로그:')\n"
" print(open('/content/logs/server.log').read()[-1200:])\n"
" print('로컬(8000)에서 계속 사용할 수 있습니다.')\n"
),
code(
"# 11) 업로드 → poll → 결과 (RAW_KEY는 셀 9에서 캡처된 값)\n"
@@ -383,6 +415,13 @@ def cells() -> list[dict]:
"in-proc 백엔드는 별도 워커 프로세스가 없으면 큐를 소비하지 못합니다.\n"
"셀 10이 `LUKESCRIBE_AUTO_WORKER=true`로 서버를 띄우면 서버 내부 워커 스레드가\n"
"업로드 → 완료까지 처리합니다 (v0.1에서 추가된 옵트인 기능).\n\n"
"### Cloudflare 터널로 외부에서 접속 (선택)\n\n"
"서버 셀 10이 완료되면 `https://xxx.trycloudflare.com` 외부 접속 링크가 출력됩니다.\n"
"- **임시 링크** — Colab 세션(서버 프로세스)이 살아 있는 동안만 유효하며, 세션 종료/재시작 시 닫힙니다.\n"
"- `/docs`(Swagger 대시보드)와 `/health`는 별도 키 없이 열리지만, **실제 API 호출은 `X-API-Key`(RAW_KEY)가 필요**합니다.\n"
"- cloudflared는 셀 3에서 설치됩니다. 다운로드 실패 시 터널 없이 로컬(8000)에서 계속 사용할 수 있습니다.\n"
"- 터널은 `http://localhost:8000`을 고정으로 바라봅니다. 다른 포트로 서버를 띄우면 터널이 연결되지 않습니다.\n"
"- trycloudflare는 **인증 없는 Quick Tunnel**이라 URL만 알면 누구나 접근 가능합니다. 민감 데이터는 올리지 마세요.\n\n"
"### 모델 다운로드 실패\n\n"
"- Hugging Face 연결 필요. 재시도: `LUKESCRIBE_MODEL_DOWNLOAD_RETRIES=3`\n"
"- 특정 모델만: `--model large-v3-turbo` (기본) / `--model large-v3`\n"