From 93f8c7bb33577e93824fbf04241ab09e388cc4df Mon Sep 17 00:00:00 2001 From: Rokojori Date: Thu, 16 Jul 2026 07:27:48 +0200 Subject: [PATCH] tunneling: brainstorm, Phase 1 relay server built + tested, boards + history updated Co-Authored-By: Claude Sonnet 4.6 --- workspace/_assets_/nav-data.js | 2 + workspace/boards/backlog.html | 10 - workspace/boards/tasks.html | 16 + .../2026/07-July/16-Wednesday/index.html | 126 +++++ workspace/history/index.html | 5 + workspace/outline/index.html | 17 + workspace/outline/tunneling.html | 435 ++++++++++++++++++ 7 files changed, 601 insertions(+), 10 deletions(-) create mode 100644 workspace/history/2026/07-July/16-Wednesday/index.html create mode 100644 workspace/outline/tunneling.html diff --git a/workspace/_assets_/nav-data.js b/workspace/_assets_/nav-data.js index fff2afe..5314f3b 100644 --- a/workspace/_assets_/nav-data.js +++ b/workspace/_assets_/nav-data.js @@ -16,6 +16,7 @@ var NAV_DATA = { path: 'outline/index.html', children: [ { title: 'rokojori-auth Restructure', path: 'outline/auth-restructure.html' }, + { title: 'User-Based Local Tunneling', path: 'outline/tunneling.html' }, ] }, { @@ -49,6 +50,7 @@ var NAV_DATA = { title: 'History', path: 'history/index.html', children: [ + { title: 'Wednesday, 16 July 2026', path: 'history/2026/07-July/16-Wednesday/index.html' }, { title: 'Tuesday, 15 July 2026', path: 'history/2026/07-July/15-Tuesday/index.html' }, { title: 'Monday, 14 July 2026', path: 'history/2026/07-July/14-Monday/index.html' }, { title: 'Sunday, 13 July 2026', path: 'history/2026/07-July/13-Sunday/index.html' }, diff --git a/workspace/boards/backlog.html b/workspace/boards/backlog.html index 29414d7..acfd0f3 100644 --- a/workspace/boards/backlog.html +++ b/workspace/boards/backlog.html @@ -37,16 +37,6 @@
Nice To Have
- - Internet Tunnel / Port Relay - - Allow local devices — a Stable Diffusion instance, a local LLM, a GDScript language - server — to be accessible to authorised Roject users over the internet, routed through - the Roject server via a persistent WebSocket. Primary use case: phone as a thin client - while the main machine handles all heavy processing. - - - Real-Time Multi-User Collaboration diff --git a/workspace/boards/tasks.html b/workspace/boards/tasks.html index 2fc759f..ed63d22 100644 --- a/workspace/boards/tasks.html +++ b/workspace/boards/tasks.html @@ -113,6 +113,22 @@
In Progress
+ + rokojori-tunnel — Phase 2 + + Phase 1 complete: relay server, CRUD tunnel API, WebSocket agent endpoint, + HTTP proxy, test agent script. Tested end-to-end with local LLM (gemma4-coding + on port 8900) — request relayed and response returned correctly. + Service lives at C:\rokojori\projects\web-projects\tunnel. + + Phase 2 remaining: + — Allowed users list enforcement (multi-user private access) + — GET /api/tunnels/available with ?purpose= filter + — Public access mode (no auth required on proxy route) + — Roject LLM provider picker integrating the discovery API + + + styles.rokojori.com — complete deployment diff --git a/workspace/history/2026/07-July/16-Wednesday/index.html b/workspace/history/2026/07-July/16-Wednesday/index.html new file mode 100644 index 0000000..f4e9557 --- /dev/null +++ b/workspace/history/2026/07-July/16-Wednesday/index.html @@ -0,0 +1,126 @@ + + + + + + Wednesday, 16 July 2026 — Roject + + + + +
+ +
+

Wednesday, 16 July 2026

+

Session History

+

Brainstormed and scaffolded rokojori-tunnel — user-based local tunneling service.

+
+ +
+

What we built

+ +
+

rokojori-tunnel — Phase 1 (relay server)

+

+ New standalone service at C:\rokojori\projects\web-projects\tunnel, + same stack as the rest of the ecosystem (Node.js, Express, ts-node, JSON file + storage, shared JWT verification). +

+
    +
  • source/server/db.ts — JSON file storage for tunnel configs + (build/data/db/tunnels.json)
  • +
  • source/server/middleware/requireAuth.ts — JWT verification, + same pattern as rokojori-auth
  • +
  • source/server/relay/TunnelRegistry.ts — in-memory + Map<tunnelId, WebSocket> of active agent connections
  • +
  • source/server/relay/pending.ts — pending request callbacks + keyed by reqId for matching responses to waiting HTTP connections
  • +
  • source/server/routes/tunnels.ts — full CRUD plus + GET /api/tunnels/available?purpose= discovery endpoint
  • +
  • source/server/routes/agent.ts — WebSocket upgrade handler; + verifies JWT, confirms ownership, registers socket in registry
  • +
  • source/server/routes/proxy.tsALL /t/:tunnelId/* + relay; soft auth check for access mode, raw body forwarding, 30s timeout
  • +
  • source/server/index.ts — Express + HTTP server with manual + WebSocket upgrade routing; JSON middleware applied only to /api + routes so proxy receives raw body streams
  • +
  • scripts/test-agent.ts — standalone Node.js agent for + testing before the Electron app exists; connects via WebSocket and forwards + inbound relay requests to a local port
  • +
+
+ +
+

End-to-end test — local LLM over tunnel

+

+ Registered a tunnel via POST /api/tunnels, started the test agent + forwarding to port 8900 (gemma4-coding-Q4_K_M.gguf running locally), + and sent an OpenAI-compatible /v1/chat/completions request through + the relay. Full round-trip succeeded — request forwarded, response relayed back, + streaming token count confirmed in the response. +

+
+ +
+

tunneling.html outline document

+

+ New plan document at workspace/outline/tunneling.html covering: + the relay mechanic (three-leg model, minimal inspection, raw byte forwarding), + access modes (private / public / password-protected), tunnel metadata shape and + purpose tags, all three components (relay server, Electron agent app, rokojori-auth + permission), API endpoint reference, Roject LLM provider integration example, + file structure, and phased implementation plan. +

+
+ +
+ +
+

Key decisions

+ +
+

+ Separate service, not part of rokojori-auth. + Auth stays focused on identity. tunnel.rokojori.com is its own Express process + that verifies the shared JWT but owns all relay logic independently. +

+
+ +
+

+ HTTP-only for Phase 1. Stable Diffusion (AUTOMATIC1111) and + OpenAI-compatible LLMs (Ollama, LM Studio) all speak HTTP with SSE streaming — + no WebSocket from the app side needed. Language servers use raw TCP and are out + of scope for now. +

+
+ +
+

+ JSON envelope protocol for Phase 1. Requests and responses are + wrapped as { reqId, method, path, headers, body (base64) } JSON + messages over the agent WebSocket. No raw byte framing needed at this stage; + the JSON envelope is simple enough and sidesteps binary WebSocket complexity. +

+
+ +
+

+ No express.json() on proxy routes. The JSON middleware is applied + only to /api/tunnels so the proxy handler always receives a raw + readable body stream, regardless of content type. +

+
+ +
+ +
+ Roject — session history +
+ +
+ + + + + diff --git a/workspace/history/index.html b/workspace/history/index.html index 1010315..5ce74e6 100644 --- a/workspace/history/index.html +++ b/workspace/history/index.html @@ -19,6 +19,11 @@

2026 — July

+
+

Wednesday, 16 July 2026

+

Brainstormed and scaffolded rokojori-tunnel — user-based local tunneling service; Phase 1 relay server built and tested end-to-end with local LLM (gemma4-coding on port 8900).

+
+

Tuesday, 15 July 2026

styles.rokojori.com built and deployed (font hosting, /get-font public CSS endpoint, CORS allowlist, auth-gated management pages); Roject Barlow font migrated from Google Fonts; old dashboard/groups/projects pages removed; add-subdomain deployment guide with live variable inputs.

diff --git a/workspace/outline/index.html b/workspace/outline/index.html index 9193937..4fdd161 100644 --- a/workspace/outline/index.html +++ b/workspace/outline/index.html @@ -81,6 +81,23 @@

+
+

Ecosystem — tunnel.rokojori.com

+

+ A user-based local tunneling service (in development at + C:\rokojori\projects\web-projects\tunnel, planned to live at + tunnel.rokojori.com). Exposes local services — LLMs, Stable + Diffusion, language servers — to authorised rokojori users over the internet, + routed through a relay server via a persistent WebSocket agent connection. + Each tunnel is owned by a rokojori account and carries metadata (name, purpose + tag, description, access mode, allowed users) so apps can discover and select + tunnels via GET /api/tunnels/available?purpose=. + Phase 1 (relay server, CRUD API, HTTP proxy, test agent) is complete and + tested end-to-end. Phase 2 (multi-user access, discovery API, public mode) + is in progress. +

+
+

Ecosystem — styles.rokojori.com

diff --git a/workspace/outline/tunneling.html b/workspace/outline/tunneling.html new file mode 100644 index 0000000..c05b2b0 --- /dev/null +++ b/workspace/outline/tunneling.html @@ -0,0 +1,435 @@ + + + + + + User-Based Local Tunneling — Roject + + + + +

+ +
+

Plan — In Progress

+

User-Based Local Tunneling

+

+ A generic rokojori-ecosystem service at tunnel.rokojori.com that lets + authenticated users expose local services to the internet — with access control, + metadata, and discovery built in from the start. +

+
+ +
+

What it is

+ +
+

+ Local tunneling solves one problem: your local machine is running something useful + (a local LLM, a Stable Diffusion instance, a language server) but it is only + reachable on localhost. The tunnel service makes it reachable over + the internet by routing traffic through a relay server. +

+

+ What makes this different from existing tools (ngrok, localtunnel, pinggy) is + identity. Every tunnel is owned by a rokojori user account. Access is controlled + per-user — you can keep a tunnel private, share it with specific people, or open + it to anyone with the link. Apps in the rokojori ecosystem can discover and use + tunnels via a standard API, so a user can point Roject's AI agent at their own + local LLM without configuring endpoints manually. +

+

+ The service lives at tunnel.rokojori.com — a standalone Express app, + same stack as the rest of the ecosystem, verified via the shared JWT from + account.rokojori.com. +

+
+
+ +
+

How the Relay Works

+ +
+

The three-leg model

+

+ Traffic flows through three legs: browser/app → relay server → local agent → local service. +

+
App (Roject on phone)
+  → HTTPS  →  tunnel.rokojori.com/t/<tunnelId>/v1/chat/completions
+  → WebSocket envelope  →  Electron agent (on the home machine)
+  → HTTP  →  localhost:11434/v1/chat/completions
+

+ The relay server is a consensual man-in-the-middle. Both sides connect to it + knowingly. The server's job is to forward traffic as transparently as possible — + inspect only what is required for routing, pass everything else as raw bytes. +

+
+ +
+

What must be inspected vs. what is forwarded raw

+

+ The server must read the HTTP request line (method, path) to strip the + /t/<tunnelId> prefix, and must rewrite the Host + header. Everything else — body bytes, SSE chunks, binary image data — is piped + through opaque without parsing. +

+

+ The WebSocket connection between the server and the agent requires a minimal + envelope to multiplex concurrent requests over one socket: +

+
[4 bytes: requestId] [raw HTTP bytes...]
+

+ The agent strips the 4-byte prefix, forwards the remaining bytes to + localhost:<port>, and prepends the same requestId to the + response bytes before sending back. The server matches the response to the + waiting HTTP connection by requestId and flushes it. +

+
+ +
+

Streaming (SSE / chunked responses)

+

+ Local LLMs stream tokens via Server-Sent Events or chunked transfer encoding. + The agent forwards response bytes as they arrive — the relay server keeps the + HTTP response to the app open and flushes each chunk immediately. No buffering, + no full-response collection needed. +

+

+ This means the app receives the stream in real time, exactly as it would if it + were calling the local service directly. +

+
+ +
+

WebSocket-based services — out of scope for v1

+

+ The primary target services (Stable Diffusion AUTOMATIC1111, Ollama, LM Studio, + OpenAI-compatible APIs) all speak HTTP with optional SSE streaming. None require + WebSocket from the app side. Language servers (C# OmniSharp, Godot LSP) use raw + TCP — a separate and harder problem, not planned for v1. +

+
+
+ +
+

Access Modes

+ +
+

Private

+

+ Only authenticated rokojori users in the tunnel's allowedUserIds + list (plus the owner) can send requests through it. The relay server verifies the + JWT on every inbound request. This is the default for new tunnels. +

+
+ +
+

Public (link-based)

+

+ Anyone with the tunnel URL can send requests — no authentication required. + Equivalent to what ngrok and localtunnel provide out of the box. Useful for quick + demos or sharing with clients who have no rokojori account. +

+
+ +
+

Password-protected (v2)

+

+ A single shared secret is required in an X-Tunnel-Key header or as + a query parameter. Simpler than full account management for trusted-but-anonymous + recipients. Not planned for v1. +

+
+
+ +
+

Tunnel Metadata

+ +
+

+ Each tunnel carries metadata so that apps and users can discover, filter, and + select tunnels without prior knowledge of what is running behind them. +

+
{
+  "id": "uuid",
+  "name": "Josef's Ollama",
+  "description": "Local Ollama instance, llama3.2 and mistral available",
+  "purpose": "llm-openai-compatible",
+  "ownerId": "uuid",
+  "access": "private",
+  "allowedUserIds": ["uuid-alice", "uuid-bob"],
+  "localPort": 11434,
+  "active": true,
+  "createdAt": "2026-07-16T00:00:00Z"
+}
+
+ +
+

Purpose tags

+

+ The purpose field is a well-known tag that apps use to filter + relevant tunnels. Defined as a fixed set in the service: +

+
    +
  • llm-openai-compatible — drop-in replacement for the OpenAI + /v1/chat/completions endpoint; works with any OpenAI-compatible API + (Ollama, LM Studio, etc.)
  • +
  • stable-diffusion — AUTOMATIC1111 or ComfyUI image generation
  • +
  • general — generic HTTP relay, no specific integration contract
  • +
+

+ More purpose tags are added as concrete integrations are built. +

+
+ +
+

Active vs. registered

+

+ A tunnel can be registered (config saved, metadata available for discovery) while + the agent is offline. The active flag reflects whether the agent + WebSocket is currently connected. Apps should check this before attempting to + send requests through a tunnel. +

+
+
+ +
+

Components

+ +
+

1 — tunnel.rokojori.com (relay server)

+

+ A standalone Express + Node.js service. Owns tunnel configs (JSON file storage), + the WebSocket server that agents connect to, and the HTTP proxy routes that apps + call. +

+
    +
  • Verifies JWTs from account.rokojori.com (shared JWT_SECRET)
  • +
  • Checks tunnel:create permission before allowing tunnel registration
  • +
  • Maintains an in-memory map of active agent WebSocket connections
  • +
  • Proxies inbound HTTP requests to the correct agent, streams responses back
  • +
  • Stores tunnel configs in build/data/db/tunnels.json
  • +
+
+ Node.js + Express + ts-node + ws (WebSocket) + tunnel.rokojori.com +
+
+ +
+

2 — Electron agent app (local machine)

+

+ A small Electron desktop app that runs in the system tray. The user logs in once + via account.rokojori.com (same direct API call pattern as the Roject + Electron app). Tokens are stored in userData/tokens.json. +

+

+ The user configures one or more tunnels — each with a name, purpose tag, + description, and local port. The app opens a persistent WebSocket to + wss://tunnel.rokojori.com/api/agent/:tunnelId for each active tunnel + and forwards inbound byte envelopes to the local port. +

+
    +
  • System tray icon — green dot when at least one tunnel is active
  • +
  • Main window: list of configured tunnels with active/inactive status
  • +
  • Add tunnel: name, purpose, description, local port, access mode, allowed users
  • +
  • Auto-reconnect on disconnect with exponential backoff
  • +
  • Start on boot option
  • +
+
+ Electron + system tray + WebSocket agent +
+
+ +
+

3 — rokojori-auth (permission only)

+

+ No tunnel logic lives in rokojori-auth. The only addition is a + tunnel:create permission added to the roles map. Users without this + permission are rejected by the relay server when attempting to register a tunnel. +

+
+
+ +
+

API Endpoints — tunnel.rokojori.com

+ +
+

Tunnel management (authenticated)

+
POST   /api/tunnels                    — register a new tunnel config (requires tunnel:create)
+GET    /api/tunnels                    — list tunnels owned by the requesting user
+GET    /api/tunnels/available          — list tunnels the user has access to (owned + allowed)
+GET    /api/tunnels/available?purpose= — same, filtered by purpose tag
+GET    /api/tunnels/:id                — get one tunnel's metadata
+PATCH  /api/tunnels/:id                — update name, description, access, allowedUserIds
+DELETE /api/tunnels/:id                — remove tunnel config (owner only)
+
+ +
+

Agent connection (authenticated, WebSocket upgrade)

+
GET    /api/agent/:tunnelId            — WebSocket; agent connects here, stays open
+

+ On connection the server verifies the JWT, confirms the connecting user owns the + tunnel, and registers the socket in the in-memory map. On disconnect the tunnel + is marked inactive. +

+
+ +
+

Proxy route (access mode enforced)

+
ALL    /t/:tunnelId/*                  — relay any HTTP method to the agent
+

+ For private tunnels the JWT is verified and the requesting user must be the owner + or appear in allowedUserIds. For public tunnels no auth is required. + If the tunnel is registered but the agent is offline, the server returns + 503 Service Unavailable. +

+
+
+ +
+

Integration in Apps — Roject Example

+ +
+

The pattern

+

+ Any app in the rokojori ecosystem can query the tunnel service for tunnels + available to the current user, filtered by purpose. The app then uses the tunnel + URL as a standard HTTP endpoint — no special tunnel SDK required on the + app side. +

+
+ +
+

LLM provider selection

+

+ When a user configures an AI agent or task in Roject, they choose an LLM + provider. Providers come in two kinds: +

+
    +
  • External — Anthropic, OpenAI, or any OpenAI-compatible + endpoint with an API key and base URL configured manually.
  • +
  • Tunneled — a local service exposed via + tunnel.rokojori.com. The user clicks "Browse tunnels", Roject + fetches GET /api/tunnels/available?purpose=llm-openai-compatible + and shows a picker with each tunnel's name, description, owner, and active + status.
  • +
+

+ Once selected, Roject stores the tunnelId and constructs the + endpoint at runtime: +

+
https://tunnel.rokojori.com/t/<tunnelId>/v1/chat/completions
+

+ From Roject's perspective this is a standard OpenAI-compatible HTTP endpoint. + Streaming works identically — SSE chunks flow from the local LLM through + the relay to the Roject UI in real time. No code in Roject knows or cares that + a tunnel is involved. +

+
+ +
+

Family / team sharing

+

+ If a tunnel's allowedUserIds includes other rokojori accounts, + those users see the tunnel in their /api/tunnels/available response + too — even though they do not own it. A household can share one Stable + Diffusion machine; a small team can share one GPU server. The owner adds user IDs + via the Electron app or a future web UI on tunnel.rokojori.com. +

+
+
+ +
+

File Structure — rokojori-tunnel

+ +
+
C:\rokojori\projects\web-projects\tunnel\
+  source/
+    server/
+      routes/
+        tunnels.ts           — CRUD for tunnel configs
+        proxy.ts             — ALL /t/:tunnelId/* relay handler
+        agent.ts             — WebSocket upgrade endpoint for agents
+      middleware/
+        requireAuth.ts       — JWT verification (shared pattern)
+      relay/
+        TunnelRegistry.ts    — in-memory Map<tunnelId, AgentSocket>
+      db.ts                  — tunnel config storage (JSON files)
+      index.ts               — Express entry point
+  electron-agent/
+    main.ts                  — Electron main process, tray setup
+    login-window.ts          — direct API login, token storage
+    tray.ts                  — system tray icon and menu
+    agent/
+      TunnelAgent.ts         — WebSocket connection + HTTP forwarding loop
+      AgentConfig.ts         — local tunnel config (name, port, tunnelId)
+  build/
+    data/
+      db/
+        tunnels.json         — registered tunnel configs
+  package.json
+  tsconfig.json
+  tsconfig.ts-node.json
+  .env
+
+
+ +
+

Environment Variables

+ +
+
JWT_SECRET=...          — same shared value as all other rokojori services
+PORT=3002               — or whichever port nginx proxies to
+
+
+ +
+

Phased Implementation

+ +
+

Phase 1 — single user, HTTP only — Complete

+
    +
  • Relay server built at C:\rokojori\projects\web-projects\tunnel
  • +
  • Tunnel CRUD API, WebSocket agent endpoint, HTTP proxy route
  • +
  • Standalone test agent script (scripts/test-agent.ts)
  • +
  • Tested end-to-end: request relayed through tunnel to local gemma4-coding LLM on port 8900 and response returned correctly
  • +
+
+ +
+

Phase 2 — multi-user and discovery — Next

+
    +
  • Allowed users list on tunnel config
  • +
  • GET /api/tunnels/available endpoint with purpose filtering
  • +
  • Public access mode
  • +
  • Roject LLM provider picker integrating the discovery API
  • +
+
+ +
+

Phase 3 — polish

+
    +
  • Electron app: multiple tunnel configs, start on boot, tray status per tunnel
  • +
  • Web UI at tunnel.rokojori.com for managing tunnels without the desktop app
  • +
  • Per-tunnel request logs (count, last active timestamp)
  • +
  • Password-protected access mode
  • +
+
+
+ +
+ Roject — user-based local tunneling plan +
+ +
+ + + + +