Switch from unix socket to TCP proxy
Proxy to Go server on 127.0.0.1:8000 instead of unix socket. Go sees localhost connections as trusted for auto-login. Removes all the unix socket, IP forwarding, and socket path plumbing complexity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
24b9629f0f
commit
383f6a8143
|
|
@ -1,8 +1,9 @@
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { accessSync, chmodSync, constants, mkdirSync, unlinkSync } from 'fs'
|
import { accessSync, chmodSync, constants, mkdirSync } from 'fs'
|
||||||
import type { Subprocess } from 'bun'
|
import type { Subprocess } from 'bun'
|
||||||
|
|
||||||
const BIN_DIR = join(import.meta.dir, '..', 'bin')
|
const BIN_DIR = join(import.meta.dir, '..', 'bin')
|
||||||
|
const GO_PORT = 8000
|
||||||
|
|
||||||
let healthy = false
|
let healthy = false
|
||||||
let proc: Subprocess | undefined
|
let proc: Subprocess | undefined
|
||||||
|
|
@ -16,10 +17,10 @@ function getBinaryName(): string {
|
||||||
return `tronbyt-server-${platform}-${arch}`
|
return `tronbyt-server-${platform}-${arch}`
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForHealthy(socketPath: string, maxAttempts = 60): Promise<boolean> {
|
async function waitForHealthy(maxAttempts = 60): Promise<boolean> {
|
||||||
for (let i = 0; i < maxAttempts; i++) {
|
for (let i = 0; i < maxAttempts; i++) {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('http://localhost/health', { unix: socketPath })
|
const resp = await fetch(`http://127.0.0.1:${GO_PORT}/health`)
|
||||||
if (resp.ok) return true
|
if (resp.ok) return true
|
||||||
} catch {}
|
} catch {}
|
||||||
await Bun.sleep(1000)
|
await Bun.sleep(1000)
|
||||||
|
|
@ -63,7 +64,7 @@ function validate(dataDir: string): string | undefined {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function spawn(dataDir: string, socketPath: string) {
|
export async function spawn(dataDir: string) {
|
||||||
const binPath = join(BIN_DIR, getBinaryName())
|
const binPath = join(BIN_DIR, getBinaryName())
|
||||||
|
|
||||||
if (!(await Bun.file(binPath).exists())) {
|
if (!(await Bun.file(binPath).exists())) {
|
||||||
|
|
@ -76,14 +77,11 @@ export async function spawn(dataDir: string, socketPath: string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try { unlinkSync(socketPath) } catch {}
|
|
||||||
|
|
||||||
console.log('Starting tronbyt server...')
|
console.log('Starting tronbyt server...')
|
||||||
|
|
||||||
proc = Bun.spawn([binPath], {
|
proc = Bun.spawn([binPath], {
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
TRONBYT_UNIX_SOCKET: socketPath,
|
|
||||||
DATA_DIR: dataDir,
|
DATA_DIR: dataDir,
|
||||||
DB_DSN: join(dataDir, 'tronbyt.db'),
|
DB_DSN: join(dataDir, 'tronbyt.db'),
|
||||||
PRODUCTION: process.env.PRODUCTION ?? 'true',
|
PRODUCTION: process.env.PRODUCTION ?? 'true',
|
||||||
|
|
@ -100,7 +98,7 @@ export async function spawn(dataDir: string, socketPath: string) {
|
||||||
proc = undefined
|
proc = undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
if (await waitForHealthy(socketPath)) {
|
if (await waitForHealthy()) {
|
||||||
healthy = true
|
healthy = true
|
||||||
console.log('Tronbyt server is healthy')
|
console.log('Tronbyt server is healthy')
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
20
src/proxy.ts
20
src/proxy.ts
|
|
@ -5,31 +5,29 @@ export interface WsData {
|
||||||
protocols: string[]
|
protocols: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GO_PORT = 8000
|
||||||
|
const GO_BASE = `http://127.0.0.1:${GO_PORT}`
|
||||||
|
|
||||||
const upstreams = new Map<ServerWebSocket<WsData>, WebSocket>()
|
const upstreams = new Map<ServerWebSocket<WsData>, WebSocket>()
|
||||||
|
|
||||||
export function createProxy(socketPath: string, isHealthy: () => boolean, isRunning: () => boolean) {
|
export function createProxy(isHealthy: () => boolean, isRunning: () => boolean) {
|
||||||
async function proxyFetch(req: Request, clientIP?: string): Promise<Response> {
|
async function proxyFetch(req: Request): Promise<Response> {
|
||||||
const url = new URL(req.url)
|
const url = new URL(req.url)
|
||||||
|
|
||||||
if (url.pathname === '/ok') {
|
if (url.pathname === '/ok') {
|
||||||
if (!isHealthy()) return new Response('starting', { status: isRunning() ? 200 : 503 })
|
if (!isHealthy()) return new Response('starting', { status: isRunning() ? 200 : 503 })
|
||||||
return fetch('http://localhost/health', { unix: socketPath })
|
return fetch(`${GO_BASE}/health`)
|
||||||
.then((r) => (r.ok ? new Response('ok') : new Response('unhealthy', { status: 503 })))
|
.then((r) => (r.ok ? new Response('ok') : new Response('unhealthy', { status: 503 })))
|
||||||
.catch(() => new Response('unhealthy', { status: 503 }))
|
.catch(() => new Response('unhealthy', { status: 503 }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasBody = req.method !== 'GET' && req.method !== 'HEAD'
|
const hasBody = req.method !== 'GET' && req.method !== 'HEAD'
|
||||||
const body = hasBody ? await req.arrayBuffer() : undefined
|
const body = hasBody ? await req.arrayBuffer() : undefined
|
||||||
const headers = new Headers(req.headers)
|
|
||||||
const forwardedFor = req.headers.get('x-forwarded-for')
|
|
||||||
headers.set('x-forwarded-for', forwardedFor ? `${forwardedFor}, ${clientIP}` : (clientIP ?? ''))
|
|
||||||
headers.set('x-real-ip', clientIP ?? '')
|
|
||||||
|
|
||||||
return fetch(`http://localhost${url.pathname}${url.search}`, {
|
return fetch(`${GO_BASE}${url.pathname}${url.search}`, {
|
||||||
method: req.method,
|
method: req.method,
|
||||||
headers,
|
headers: req.headers,
|
||||||
body,
|
body,
|
||||||
unix: socketPath,
|
|
||||||
}).then((r) => {
|
}).then((r) => {
|
||||||
// Bun auto-decompresses gzip but leaves content-encoding header.
|
// Bun auto-decompresses gzip but leaves content-encoding header.
|
||||||
// Strip it so the next proxy layer doesn't try to decompress again.
|
// Strip it so the next proxy layer doesn't try to decompress again.
|
||||||
|
|
@ -45,7 +43,7 @@ export function createProxy(socketPath: string, isHealthy: () => boolean, isRunn
|
||||||
|
|
||||||
const websocket = {
|
const websocket = {
|
||||||
open(ws: ServerWebSocket<WsData>) {
|
open(ws: ServerWebSocket<WsData>) {
|
||||||
const upstream = new WebSocket(`ws+unix://${socketPath}:${ws.data.path}`, ws.data.protocols)
|
const upstream = new WebSocket(`ws://127.0.0.1:${GO_PORT}${ws.data.path}`, ws.data.protocols)
|
||||||
upstream.binaryType = 'arraybuffer'
|
upstream.binaryType = 'arraybuffer'
|
||||||
upstreams.set(ws, upstream)
|
upstreams.set(ws, upstream)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { join } from 'path'
|
|
||||||
import { createProxy, type WsData } from './proxy'
|
import { createProxy, type WsData } from './proxy'
|
||||||
import { isHealthy, isRunning, shutdown, spawn } from './binary'
|
import { isHealthy, isRunning, shutdown, spawn } from './binary'
|
||||||
|
|
||||||
const DATA_DIR = process.env.DATA_DIR!
|
const DATA_DIR = process.env.DATA_DIR!
|
||||||
const PORT = Number(process.env.PORT) || 3000
|
const PORT = Number(process.env.PORT) || 3000
|
||||||
const SOCKET_PATH = join(DATA_DIR, 'tronbyt.sock')
|
|
||||||
|
|
||||||
const { proxyFetch, websocket } = createProxy(SOCKET_PATH, isHealthy, isRunning)
|
const { proxyFetch, websocket } = createProxy(isHealthy, isRunning)
|
||||||
|
|
||||||
const server = Bun.serve({
|
const server = Bun.serve({
|
||||||
port: PORT,
|
port: PORT,
|
||||||
|
|
@ -25,9 +23,7 @@ const server = Bun.serve({
|
||||||
return new Response('WebSocket upgrade failed', { status: 500 })
|
return new Response('WebSocket upgrade failed', { status: 500 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientIP = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim()
|
return proxyFetch(req)
|
||||||
|| server.requestIP(req)?.address
|
|
||||||
return proxyFetch(req, clientIP)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
websocket,
|
websocket,
|
||||||
|
|
@ -38,4 +34,4 @@ console.log(`Listening on port ${server.port}`)
|
||||||
process.on('SIGTERM', shutdown)
|
process.on('SIGTERM', shutdown)
|
||||||
process.on('SIGINT', shutdown)
|
process.on('SIGINT', shutdown)
|
||||||
|
|
||||||
spawn(DATA_DIR, SOCKET_PATH)
|
spawn(DATA_DIR)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user