これが俺のとっておき — 3層の「自宅要塞」
クラウド推論は便利だが、自分の LoRA・ログ・GPU は自宅に置きたい。一方でスマホや外出先から触りたい。Render だけでは足りない。
俺のとっておきは Home Nerve という FastAPI ゲートウェイだ。
| 層 | 役割 | 典型ポート |
|---|---|---|
| Ollama | 推論本体 | :11434 |
| Home Nerve | 認証・レート制限・WebSocket 中継 | :11436 |
| cloudflared | 自宅 → 公開 hostname へ Tunnel | — |
Ollama を 0.0.0.0 で晒さない。Nerve が API キーを要求し、必要なら IP 許可リストとレート制限をかける。Tunnel は 11436 だけ を外に出す。自宅回線の IP 変動は Tunnel 側が吸収するので、DynDNS より運用が楽なことが多い。
# cloudflared config.yml(抜粋)
tunnel: xeno-home-nerve
ingress:
- hostname: nerve.yourdomain.com
service: http://127.0.0.1:11436
- service: http_status:404
症状 — HTTP は 200 なのに WebSocket だけ死ぬ
Mobile IDE やストリームクライアントから wss://nerve.yourdomain.com/ws/xeno-stream に繋ぐと、UI は開くのにリアルタイムだけ止まる。
WebSocket connection failed
Error 1033: Cloudflare Tunnel error
HTTP 530 Origin connection refused
ブラウザ Console では 530、Cloudflare 画面では 1033 が出ることが多い。フロントの JS バグではなく、Tunnel 層 + オリジン層 の複合障害が大半だ。
切り分け表 — 1033 / 530 / 401
| コード | 意味 | 最初に見る場所 |
|---|---|---|
| 1033 | Tunnel 未起動 / 設定ミス / 認証切れ |
cloudflared プロセス、config.yml
|
| 530 | Origin ダウン / ポート未リッスン |
uvicorn / Home Nerve |
| 401 / 1008 | API キー不一致 | クライアントとサーバーの環境変数 |
切り分けの順序は固定する。1033 → 530 → 401。この順を崩すと、キーを直しても Tunnel が死んでいるので永遠に繋がらない。
Home Nerve の最小設計
xeno_home_nerve_server.py は Ollama への 認証付きプロキシ 兼 Mobile IDE WebSocket ハブ だ。
@app.middleware("http")
async def _guard(request: Request, call_next):
_check_ip(request)
_rate_limit(request)
key = _extract_key(request, request.headers.get("X-Xeno-Home-Key"), request.headers.get("Authorization"))
if request.url.path not in {"/health", "/mobile-ide"}:
_verify_key(key)
return await call_next(request)
WebSocket は Query か Header でキーを渡す。
@app.websocket("/ws/xeno-stream")
async def mobile_ws(websocket: WebSocket):
key = websocket.query_params.get("api_key") or websocket.headers.get("X-Xeno-Home-Key")
if not _verify_ws_key(key):
await websocket.close(code=1008)
return
await websocket.accept()
# code_edit / ping / job_done を非同期で処理
スマホから code_edit を送ると、自宅 PC 上の IDE + Ollama が動く。HTTP 同期でやるとタイムアウトするので WebSocket + asyncio.create_task が必須。
起動手順(自宅側)
# 1. API キー生成(32byte hex など)
export XENO_HOME_NERVE_API_KEY="$(openssl rand -hex 32)"
export OLLAMA_BASE_URL=http://127.0.0.1:11434
# 2. Nerve 起動
./start_home_nerve.sh
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:11436/health
# 期待: 200
# 3. Tunnel 起動
./tools/start_nerve_tunnel.sh
# または systemd: systemctl --user restart cloudflared-su-desk
Render 側(クラウド Worker)から叩くときは環境変数を揃える。
XENO_HOME_NERVE_URL=https://nerve.yourdomain.com
XENO_HOME_NERVE_API_KEY=<自宅と同一>
XENO_OLLAMA_TRANSPORT=remote
検証手順
-
ローカル HTTP —
curl http://127.0.0.1:11436/healthが 200、ollama_ok: true -
Tunnel 経由 HTTP —
curl https://nerve.yourdomain.com/healthが 200(1033 なら cloudflared を見る) -
WebSocket Upgrade —
curl -i -N -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' 'https://nerve.yourdomain.com/ws/xeno-stream?api_key=YOUR_KEY'で101 Switching Protocols -
意図的障害 —
cloudflaredを止めて 1033 を再現 → 再起動で解消 -
キー不一致 — わざと wrong key →
1008/401を確認してから本番キーに戻す
ログに MOBILE_IDE_WS_CONNECTED / MOBILE_IDE_JOB_OK が出れば、Tunnel + Nerve + IDE の3点が通っている。
よくある穴
- Ollama だけ 0.0.0.0 公開 — Nerve をバイパスすると API キーが効かない。必ず 11436 経由
- Tunnel だけ再起動 — Origin(uvicorn)が古い PID のまま 530 が残る。Nerve → Tunnel の順で再起動
-
LAN 用
ws://192.168.x.xを本番に混在 — PWA が LAN URL をキャッシュすると外出先で必ず死ぬ。本番はwss://固定 - API キーを Query と Header で二重定義し片方だけ更新 — クライアント実装ごとに渡し方が違う。ドキュメント化しないと必ずズレる
-
WebSocket を同期処理 —
code_editを await したまま次の ping を待つと切断。asyncio.create_taskでジョブを逃がす - in-memory レート制限だけ — 複数 Worker プロセスだと穴が開く。単一プロセス前提なら OK、増やすなら Redis 等
Render × 自宅のハイブリッド運用
Web サービス(Shop / API)は Render。推論は XENO_OLLAMA_TRANSPORT=remote で Home Nerve に逃がす。Tunnel が落ちるとクラoud 側は 502 になるが、自宅 PC が再起動すれば復旧する。完全 SaaS ではないが、GPU とデータ主権を手元に残せる。
Worker の wakeup loop(note / Zenn / Qiita 定時投稿)と Nerve は別プロセスだが、同じ .env 思想 — 秘密は環境変数、状態は JSON、投稿系統は分離 — で運用している。
まとめ
- Home Nerve = 自宅 Ollama の門番 + Mobile WebSocket ハブ
- 1033 は Tunnel、530 は Origin、401/1008 はキー — この順で潰す
-
wss://+ API キー + 非同期ジョブが、スマホから自宅 GPU を触る最小セット - 商品リンクではなく インフラ実録。Tunnel が通った瞬間、自宅マシンが「自分専用の推論リージョン」になる