From 84ce472c87e54a26ebe8b6e7d523ab39923820a3 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Tue, 10 Mar 2026 17:16:05 -0700 Subject: [PATCH] Auto-download Go binary on startup and validate setup The postinstall script doesn't run on toes (package.json is transformed during deploy), so the binary was never downloaded. Now the server downloads it from GitHub releases if missing. Added validate() to catch missing DATA_DIR and non-executable binary with clear error messages. Co-Authored-By: Claude Opus 4.6 --- src/server.ts | 53 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/src/server.ts b/src/server.ts index a9f6ae8..dfa15cf 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,10 +1,10 @@ import { join } from 'path' -import { unlinkSync } from 'fs' +import { accessSync, chmodSync, constants, mkdirSync, unlinkSync } from 'fs' import type { ServerWebSocket, Subprocess } from 'bun' -const DATA_DIR = process.env.DATA_DIR! +const DATA_DIR = process.env.DATA_DIR const PORT = Number(process.env.PORT) || 3000 -const SOCKET_PATH = join(DATA_DIR, 'tronbyt.sock') +const SOCKET_PATH = DATA_DIR ? join(DATA_DIR, 'tronbyt.sock') : '' const BIN_DIR = join(import.meta.dir, '..', 'bin') let goProcess: Subprocess | undefined @@ -107,13 +107,52 @@ async function waitForHealthy(maxAttempts = 60): Promise { return false } +async function downloadBinary(binPath: string): Promise { + const name = getBinaryName() + const url = `https://github.com/tronbyt/server/releases/latest/download/${name}` + + console.log(`Downloading ${name}...`) + try { + const resp = await fetch(url, { redirect: 'follow' }) + if (!resp.ok) { + console.error(`Download failed: ${resp.status} ${resp.statusText}`) + return false + } + + mkdirSync(BIN_DIR, { recursive: true }) + await Bun.write(binPath, resp) + + chmodSync(binPath, 0o755) + + console.log('Download complete') + return true + } catch (e) { + console.error('Download failed:', e) + return false + } +} + +function validate(): string | undefined { + if (!DATA_DIR) return 'DATA_DIR env var is not set — toes should provide this automatically' + + const binPath = join(BIN_DIR, getBinaryName()) + try { + accessSync(binPath, constants.X_OK) + } catch { + return `Binary not found or not executable: ${binPath}` + } +} + async function spawnGoServer() { const binPath = join(BIN_DIR, getBinaryName()) if (!(await Bun.file(binPath).exists())) { - console.error(`Binary not found: ${binPath}`) - console.error(`Download from https://github.com/tronbyt/server/releases`) - console.error(`Expected: ${getBinaryName()}`) + if (!(await downloadBinary(binPath))) return + } + + const error = validate() + if (error) { + console.error(`Setup error: ${error}`) return } @@ -126,7 +165,7 @@ async function spawnGoServer() { ...process.env, TRONBYT_UNIX_SOCKET: SOCKET_PATH, DATA_DIR, - DB_DSN: join(DATA_DIR, 'tronbyt.db'), + DB_DSN: join(DATA_DIR!, 'tronbyt.db'), PRODUCTION: process.env.PRODUCTION ?? 'false', SINGLE_USER_AUTO_LOGIN: process.env.SINGLE_USER_AUTO_LOGIN ?? 'true', SYSTEM_APPS_AUTO_REFRESH: process.env.SYSTEM_APPS_AUTO_REFRESH ?? 'true',