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 @@
Wednesday, 16 July 2026
+Brainstormed and scaffolded rokojori-tunnel — user-based local tunneling service.
+
+ 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-authsource/server/relay/TunnelRegistry.ts — in-memory
+ Map<tunnelId, WebSocket> of active agent connectionssource/server/relay/pending.ts — pending request callbacks
+ keyed by reqId for matching responses to waiting HTTP connectionssource/server/routes/tunnels.ts — full CRUD plus
+ GET /api/tunnels/available?purpose= discovery endpointsource/server/routes/agent.ts — WebSocket upgrade handler;
+ verifies JWT, confirms ownership, registers socket in registrysource/server/routes/proxy.ts — ALL /t/:tunnelId/*
+ relay; soft auth check for access mode, raw body forwarding, 30s timeoutsource/server/index.ts — Express + HTTP server with manual
+ WebSocket upgrade routing; JSON middleware applied only to /api
+ routes so proxy receives raw body streamsscripts/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
+ 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.
+
+ 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.
+
+ 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.
+
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).
+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 @@
+ 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.
+
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 @@ + + +
+ + +Plan — In Progress
+
+ 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.
+
+ 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.
+
+ 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. +
+
+ 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.
+
+ 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. +
++ 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. +
+
+ 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.
+
+ 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. +
+
+ 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.
+
+ 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"
+}
+
+ 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 generationgeneral — generic HTTP relay, no specific integration contract+ More purpose tags are added as concrete integrations are built. +
+
+ 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.
+
+ 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. +
+account.rokojori.com (shared JWT_SECRET)tunnel:create permission before allowing tunnel registrationbuild/data/db/tunnels.json
+ 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.
+
+ 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.
+
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)
+ 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. +
+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.
+
+ 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. +
++ When a user configures an AI agent or task in Roject, they choose an LLM + provider. Providers come in two kinds: +
+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. +
+
+ 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.
+
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
+ JWT_SECRET=... — same shared value as all other rokojori services
+PORT=3002 — or whichever port nginx proxies to
+ C:\rokojori\projects\web-projects\tunnelscripts/test-agent.ts)gemma4-coding LLM on port 8900 and response returned correctlyGET /api/tunnels/available endpoint with purpose filteringtunnel.rokojori.com for managing tunnels without the desktop app