fbi-proxy 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,66 +1,71 @@
1
1
  {
2
2
  "name": "fbi-proxy",
3
- "module": "ts/cli.ts",
4
- "type": "module",
5
- "devDependencies": {
6
- "@semantic-release/changelog": "^6.0.3",
7
- "@semantic-release/git": "^10.0.1",
8
- "@semantic-release/github": "^9.2.0",
9
- "@types/bun": "latest",
10
- "@types/minimist": "^1.2.5",
11
- "semantic-release-cargo": "^2.4.0",
12
- "husky": "^9.1.7",
13
- "lint-staged": "^16.1.2",
14
- "prettier": "^3.6.2",
15
- "semantic-release": "^22.0.0"
3
+ "version": "1.6.0",
4
+ "description": "FBI-Proxy provides easy HTTPS access to your local services with intelligent domain routing",
5
+ "keywords": [
6
+ "development-tools",
7
+ "http-proxy",
8
+ "proxy",
9
+ "reverse-proxy",
10
+ "websocket"
11
+ ],
12
+ "homepage": "https://github.com/snomiao/fbi-proxy#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/snomiao/fbi-proxy/issues"
16
15
  },
17
- "peerDependencies": {
18
- "@snomiao/caddy-baron": "^3.0.4",
19
- "typescript": "^5"
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/snomiao/fbi-proxy.git"
19
+ },
20
+ "license": "MIT",
21
+ "author": "snomiao",
22
+ "type": "module",
23
+ "bin": {
24
+ "fbi-proxy": "dist/cli.js"
20
25
  },
21
26
  "files": [
27
+ "Caddyfile",
28
+ "dist",
22
29
  "release",
23
30
  "rs",
24
- "ts",
25
- "dist",
26
- "Caddyfile"
27
- ],
28
- "trustedDependencies": [
29
- "@radically-straightforward/caddy"
31
+ "ts"
30
32
  ],
33
+ "scripts": {
34
+ "build": "bun run build:rs && bun run build:js",
35
+ "build:js": "bun build ./ts/cli.ts --outdir dist --target node",
36
+ "build:ts": "tsc --noEmit",
37
+ "build:rs": "cargo build --release",
38
+ "dev": "bun --hot ts/cli.ts --dev",
39
+ "dev:rs": "cargo watch -x run",
40
+ "format": "prettier --write .",
41
+ "lint": "prettier --check .",
42
+ "prepare": "husky",
43
+ "start": "bun ts/cli.ts",
44
+ "start:rs": "./target/release/fbi-proxy"
45
+ },
31
46
  "dependencies": {
32
- "@radically-straightforward/caddy": "^2.0.6",
33
- "bun": "^1.2.19",
34
47
  "execa": "^9.6.0",
35
48
  "from-node-stream": "^0.1.0",
36
49
  "get-port": "^7.1.0",
37
50
  "hot-memo": "^1.1.1",
38
- "minimist": "^1.2.8",
39
- "npm-run-all": "^4.1.5",
40
- "phpdie": "^1.7.0",
41
- "promise-all-properties": "^5.0.0",
42
- "sflow": "^1.23.0",
43
- "ts-toolbelt": "^9.6.0",
51
+ "sflow": "^1.24.5",
44
52
  "tsa-composer": "^1.0.0",
45
- "yargs": "^17.7.2",
46
- "zx": "^8.7.2"
53
+ "yargs": "^17.7.2"
47
54
  },
48
- "scripts": {
49
- "build-cli": "bun build ts/cli.ts --outdir dist",
50
- "build": "run-p build:rs build:ts build:js",
51
- "build:ts": "tsc",
52
- "build:js": "bun build ./ts/cli.ts --outdir dist --target node",
53
- "build:rs": "cargo build --release",
54
- "dev": "run-p dev:caddy dev:rs",
55
- "dev:caddy": "./caddy run --watch",
56
- "dev:ts": "bun --hot ts/cli.ts --dev",
57
- "dev:rs": "cd rs && bacon run",
58
- "dev:rs-watch": "cd rs && cargo watch -x run",
59
- "start": "bun ts/cli.ts",
60
- "start:js": "node dist/cli.js",
61
- "start:caddy": "./caddy run",
62
- "start:rs": "./target/release/fbi-proxy",
63
- "prepare": "husky"
55
+ "devDependencies": {
56
+ "@semantic-release/changelog": "^6.0.3",
57
+ "@semantic-release/git": "^10.0.1",
58
+ "@semantic-release/github": "^9.2.0",
59
+ "@types/bun": "latest",
60
+ "@types/minimist": "^1.2.5",
61
+ "husky": "^9.1.7",
62
+ "lint-staged": "^16.1.2",
63
+ "prettier": "^3.6.2",
64
+ "semantic-release": "^22.0.0",
65
+ "semantic-release-cargo": "^2.4.0"
66
+ },
67
+ "engines": {
68
+ "node": ">=18.0.0"
64
69
  },
65
70
  "lint-staged": {
66
71
  "*.{ts,js}": [
@@ -70,8 +75,7 @@
70
75
  "bun --bun prettier --write"
71
76
  ]
72
77
  },
73
- "bin": {
74
- "fbi-proxy": "dist/cli.js"
75
- },
76
- "version": "1.5.0"
78
+ "trustedDependencies": [
79
+ "@radically-straightforward/caddy"
80
+ ]
77
81
  }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,461 @@
1
+ use clap::{Arg, Command};
2
+ use futures_util::{SinkExt, StreamExt};
3
+ use hyper::header::{HeaderValue, HOST};
4
+ use hyper::service::{make_service_fn, service_fn};
5
+ use hyper::{Body, Client, Request, Response, Server, StatusCode, Uri};
6
+ use hyper_tungstenite::{HyperWebsocket, WebSocketStream};
7
+ use log::{error, info};
8
+ use regex::Regex;
9
+ use std::convert::Infallible;
10
+ use std::net::SocketAddr;
11
+ use std::sync::Arc;
12
+ use tokio_tungstenite::connect_async;
13
+
14
+ type BoxError = Box<dyn std::error::Error + Send + Sync>;
15
+
16
+ pub struct FBIProxy {
17
+ client: Client<hyper::client::HttpConnector>,
18
+ number_regex: Regex,
19
+ domain_filter: Option<String>,
20
+ }
21
+
22
+ /*
23
+ FBIProxy is a simple HTTP and WebSocket proxy server that supports port encoding in the Host header.
24
+
25
+ parse incoming Host headers and convert them to a target URL format:
26
+
27
+ for localhost, it uses "localhost"
28
+
29
+ rule1: number host goes to local port
30
+ - Host="3000" => localhost:3000
31
+
32
+ rule1.2 host--port goes to host:port
33
+ - Host="localhost--3000" => localhost:3000
34
+ - Host="sur--3000" => sur:3000
35
+
36
+ rule2: other host goes to that host:80
37
+ - localhost => proxy to http://localhost
38
+ - amd => proxy to http://amd
39
+
40
+ rule3: subdomains are hoisted
41
+ - 3000.localhost => proxies to http://localhost:80, with host: 3000
42
+ - 3000.amd => proxies to http://amd:80, with host: 3000
43
+ - sur.amd => proxies to http://amd:80, with host: sur
44
+ - amd.sur.amd => proxies to http://amd:80, with host: amd.sur
45
+ - if sur also runs fbi-proxy, it will proxies to http://amd:80, with host: amd
46
+ - 3000.sur.amd => proxies to http://amd:80, with host: 3000.sur
47
+
48
+ for subdomains
49
+ *.amd => localhost:amd
50
+
51
+ */
52
+ impl FBIProxy {
53
+ pub fn new(domain_filter: Option<String>) -> Self {
54
+ Self {
55
+ client: Client::new(),
56
+ number_regex: Regex::new(r"^\d+$").unwrap(),
57
+ domain_filter,
58
+ }
59
+ }
60
+
61
+ fn parse_host(&self, host_header: &str, domain_filter: &Option<String>) -> Option<(String, String)> {
62
+ // Remove port if present (e.g., "localhost:8080" -> "localhost")
63
+ let host_without_port = if let Some(colon_pos) = host_header.find(':') {
64
+ &host_header[..colon_pos]
65
+ } else {
66
+ host_header
67
+ };
68
+
69
+ // Apply domain filter if specified
70
+ let host = if let Some(domain) = domain_filter {
71
+ if !domain.is_empty() {
72
+ // Check if host ends with the domain filter
73
+ if host_without_port.ends_with(domain) {
74
+ // Strip the domain suffix (including the dot)
75
+ let prefix_len = host_without_port.len() - domain.len();
76
+ if prefix_len > 0 && host_without_port.chars().nth(prefix_len - 1) == Some('.') {
77
+ // Remove the domain and the dot before it
78
+ &host_without_port[..prefix_len - 1]
79
+ } else if prefix_len == 0 {
80
+ // The host is exactly the domain, treat as root
81
+ "@"
82
+ } else {
83
+ // No dot separator, invalid format
84
+ return None;
85
+ }
86
+ } else {
87
+ // Host doesn't match domain filter
88
+ return None;
89
+ }
90
+ } else {
91
+ // Empty domain filter, accept all
92
+ host_without_port
93
+ }
94
+ } else {
95
+ // No domain filter, accept all
96
+ host_without_port
97
+ };
98
+
99
+ // Handle special case: @ means root domain was accessed
100
+ if host == "@" {
101
+ return Some(("localhost:80".to_string(), "localhost".to_string()));
102
+ }
103
+
104
+ // Rule 1: number host goes to local port (e.g., "3000" => "localhost:3000")
105
+ if self.number_regex.is_match(host) {
106
+ return Some((format!("localhost:{}", host), "localhost".to_string()));
107
+ }
108
+
109
+ // Rule 1.2: host--port goes to host:port (e.g., "localhost--3000" => "localhost:3000")
110
+ if let Some(double_dash_pos) = host.find("--") {
111
+ let hostname = &host[..double_dash_pos];
112
+ let port = &host[double_dash_pos + 2..];
113
+ return Some((format!("{}:{}", hostname, port), hostname.to_string()));
114
+ }
115
+
116
+ // Rule 3: subdomains are hoisted
117
+ let parts: Vec<&str> = host.split('.').collect();
118
+ if parts.len() > 1 {
119
+ // The last part is the main domain, everything before is subdomain
120
+ let main_domain = parts.last().unwrap();
121
+ let subdomain_parts = &parts[..parts.len() - 1];
122
+ let subdomain = subdomain_parts.join(".");
123
+
124
+ // Target is the main domain on port 80
125
+ let target_host = format!("{}:80", main_domain);
126
+ // New host header is the subdomain
127
+ return Some((target_host, subdomain.to_string()));
128
+ }
129
+
130
+ // Rule 2: other host goes to that host:80 (e.g., "localhost" => "localhost:80")
131
+ Some((format!("{}:80", host), host.to_string()))
132
+ }
133
+
134
+ pub async fn handle_request(&self, mut req: Request<Body>) -> Result<Response<Body>, BoxError> {
135
+ // Extract host from headers and process according to rules
136
+ let host_header = req
137
+ .headers()
138
+ .get(HOST)
139
+ .and_then(|h| h.to_str().ok())
140
+ .unwrap_or("localhost")
141
+ .to_string();
142
+
143
+ // Parse host with domain filtering
144
+ let parsed_host = self.parse_host(&host_header, &self.domain_filter);
145
+
146
+ // If domain filter rejects the host, return 502 Bad Gateway
147
+ let (target_host, new_host) = match parsed_host {
148
+ Some(hosts) => hosts,
149
+ None => {
150
+ let method = req.method();
151
+ let uri = req.uri();
152
+ info!(
153
+ "{} {} => REJECTED{} 502",
154
+ method,
155
+ host_header,
156
+ uri
157
+ );
158
+ return Ok(Response::builder()
159
+ .status(StatusCode::BAD_GATEWAY)
160
+ .body(Body::from("Bad Gateway: Host not allowed"))?);
161
+ }
162
+ };
163
+
164
+ let method = req.method().clone();
165
+ let original_uri = req.uri().clone();
166
+
167
+ // Handle WebSocket upgrade requests
168
+ if hyper_tungstenite::is_upgrade_request(&req) {
169
+ return self
170
+ .handle_websocket_upgrade(req, &target_host, &new_host)
171
+ .await;
172
+ }
173
+
174
+ // Build target URL for HTTP requests
175
+ let uri = req.uri();
176
+ let target_url = format!(
177
+ "http://{}{}",
178
+ target_host,
179
+ uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("/")
180
+ );
181
+ let target_uri: Uri = target_url.parse()?;
182
+
183
+ // Update request URI and headers
184
+ *req.uri_mut() = target_uri;
185
+ req.headers_mut()
186
+ .insert(HOST, HeaderValue::from_str(&new_host)?);
187
+ req.headers_mut().remove("content-encoding");
188
+
189
+ // Forward the request
190
+ match self.client.request(req).await {
191
+ Ok(mut response) => {
192
+ // Remove content-encoding header from response
193
+ response.headers_mut().remove("content-encoding");
194
+ let status = response.status();
195
+ info!(
196
+ "{} {}@{}{} {}",
197
+ method,
198
+ host_header,
199
+ target_host,
200
+ original_uri,
201
+ status.as_u16()
202
+ );
203
+ Ok(response)
204
+ }
205
+ Err(e) => {
206
+ error!(
207
+ "{} {}@{}{} 502 ({})",
208
+ method,
209
+ host_header,
210
+ target_host,
211
+ original_uri,
212
+ e
213
+ );
214
+ Ok(Response::builder()
215
+ .status(StatusCode::BAD_GATEWAY)
216
+ .body(Body::from("FBIPROXY ERROR"))?)
217
+ }
218
+ }
219
+ }
220
+
221
+ async fn handle_websocket_upgrade(
222
+ &self,
223
+ req: Request<Body>,
224
+ target_host: &str,
225
+ _new_host: &str, // Currently not used for WebSocket connections, but kept for consistency
226
+ ) -> Result<Response<Body>, BoxError> {
227
+ let uri = req.uri().clone();
228
+ let ws_url = format!(
229
+ "ws://{}{}",
230
+ target_host,
231
+ uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("/")
232
+ );
233
+
234
+ // Upgrade the HTTP connection to WebSocket
235
+ let (response, websocket) = hyper_tungstenite::upgrade(req, None)?;
236
+
237
+ // Connect to upstream WebSocket
238
+ let (upstream_ws, _) = match connect_async(&ws_url).await {
239
+ Ok(ws) => ws,
240
+ Err(e) => {
241
+ error!("WS :ws:{} => :ws:{}{} 502 ({})", target_host, target_host, uri, e);
242
+ return Ok(Response::builder()
243
+ .status(StatusCode::BAD_GATEWAY)
244
+ .body(Body::from("WebSocket connection failed"))?);
245
+ }
246
+ };
247
+
248
+ // Spawn task to handle WebSocket forwarding
249
+ // let ws_url_clone = ws_url.clone();
250
+ tokio::spawn(async move {
251
+ if let Err(e) = handle_websocket_forwarding(websocket, upstream_ws).await {
252
+ error!("WebSocket forwarding error: {}", e);
253
+ }
254
+ });
255
+
256
+ info!("WS :ws:{} => :ws:{}{} 101", target_host, target_host, uri);
257
+ Ok(response)
258
+ }
259
+ }
260
+
261
+ async fn handle_websocket_forwarding(
262
+ websocket: HyperWebsocket,
263
+ upstream_ws: WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>,
264
+ ) -> Result<(), BoxError> {
265
+ // Get the client WebSocket stream
266
+ let client_ws = websocket.await?;
267
+
268
+ let (mut client_sink, mut client_stream) = client_ws.split();
269
+ let (mut upstream_sink, mut upstream_stream) = upstream_ws.split();
270
+
271
+ // Forward messages from client to upstream
272
+ let client_to_upstream = async {
273
+ while let Some(msg) = client_stream.next().await {
274
+ match msg {
275
+ Ok(msg) => {
276
+ if let Err(_) = upstream_sink.send(msg).await {
277
+ break;
278
+ }
279
+ }
280
+ Err(_) => break,
281
+ }
282
+ }
283
+ };
284
+
285
+ // Forward messages from upstream to client
286
+ let upstream_to_client = async {
287
+ while let Some(msg) = upstream_stream.next().await {
288
+ match msg {
289
+ Ok(msg) => {
290
+ if let Err(_) = client_sink.send(msg).await {
291
+ break;
292
+ }
293
+ }
294
+ Err(_) => break,
295
+ }
296
+ }
297
+ };
298
+
299
+ // Run both forwarding tasks concurrently
300
+ tokio::select! {
301
+ _ = client_to_upstream => {},
302
+ _ = upstream_to_client => {},
303
+ }
304
+ Ok(())
305
+ }
306
+
307
+ async fn handle_connection(
308
+ req: Request<Body>,
309
+ proxy: Arc<FBIProxy>,
310
+ ) -> Result<Response<Body>, Infallible> {
311
+ match proxy.handle_request(req).await {
312
+ Ok(response) => Ok(response),
313
+ Err(e) => {
314
+ error!("Request handling error: {}", e);
315
+ Ok(Response::builder()
316
+ .status(StatusCode::INTERNAL_SERVER_ERROR)
317
+ .body(Body::from("Internal Server Error"))
318
+ .unwrap())
319
+ }
320
+ }
321
+ }
322
+
323
+ pub async fn start_proxy_server(port: u16) -> Result<(), BoxError> {
324
+ start_proxy_server_with_options("127.0.0.1", port, None).await
325
+ }
326
+
327
+ pub async fn start_proxy_server_with_host(host: &str, port: u16) -> Result<(), BoxError> {
328
+ start_proxy_server_with_options(host, port, None).await
329
+ }
330
+
331
+ pub async fn start_proxy_server_with_options(host: &str, port: u16, domain_filter: Option<String>) -> Result<(), BoxError> {
332
+ let addr: SocketAddr = format!("{}:{}", host, port).parse()?;
333
+ let proxy = Arc::new(FBIProxy::new(domain_filter.clone()));
334
+
335
+ let make_svc = make_service_fn(move |_conn: &hyper::server::conn::AddrStream| {
336
+ let proxy = proxy.clone();
337
+ async move { Ok::<_, Infallible>(service_fn(move |req| handle_connection(req, proxy.clone()))) }
338
+ });
339
+
340
+ let server = Server::bind(&addr).serve(make_svc);
341
+
342
+ info!("FBI Proxy server running on http://{}", addr);
343
+ println!("FBI Proxy listening on: http://{}", addr);
344
+ if let Some(ref domain) = domain_filter {
345
+ if !domain.is_empty() {
346
+ println!("Domain filter: Only accepting requests for *.{}", domain);
347
+ }
348
+ }
349
+ println!("⚠️ FBI-Proxy WARNING: ENSURE YOU KNOW WHAT YOU'RE DOING and be sure to set up an auth gateway before exposing to the internet");
350
+ println!(" This proxy is production ready but requires proper security measures.");
351
+
352
+ info!("Features: HTTP proxying + WebSocket forwarding + Port encoding + Domain filtering");
353
+
354
+ if let Err(e) = server.await {
355
+ error!("Server error: {}", e);
356
+ }
357
+
358
+ Ok(())
359
+ }
360
+
361
+ fn main() {
362
+ env_logger::init();
363
+
364
+ let matches = Command::new("fbi-proxy")
365
+ .version("0.1.1")
366
+ .about("A fast and flexible proxy server with smart host header parsing and WebSocket support")
367
+ .long_about(
368
+ "FBI Proxy - A any-host-port reverse-proxy server with intelligent host header parsing
369
+
370
+ FEATURES:
371
+ • HTTP and WebSocket proxying with bidirectional forwarding
372
+ • Smart host header parsing with multiple routing rules
373
+ • Port encoding support for easy local development
374
+ • Subdomain hoisting for multi-service architectures
375
+
376
+ HOST PARSING RULES:
377
+ 1. Number host → local port: '3000' → localhost:3000
378
+ 2. Host--port syntax: 'api--3000' → api:3000
379
+ 3. Subdomain hoisting: 'api.service' → service:80 (host: api)
380
+ 4. Default routing: 'localhost' → localhost:80
381
+
382
+ ENVIRONMENT VARIABLES:
383
+ FBI_PROXY_PORT Port to listen on (default: 2432)
384
+ FBI_PROXY_HOST Host/IP address to bind to (default: 127.0.0.1)
385
+ FBI_PROXY_DOMAIN Domain filter (only accept *.domain requests)
386
+ RUST_LOG Log level (error, warn, info, debug, trace)
387
+
388
+ EXAMPLES:
389
+ fbi-proxy # Start on 127.0.0.1:2432, accept all
390
+ fbi-proxy -p 8080 # Custom port
391
+ fbi-proxy -h 0.0.0.0 -p 3000 # Bind to all interfaces
392
+ fbi-proxy -d example.com # Only accept *.example.com requests
393
+ FBI_PROXY_PORT=8080 fbi-proxy # Use environment variable
394
+
395
+ TRY RUN:
396
+ # HOST_A:
397
+ npx serve --port 3000
398
+ fbi-proxy -h 0.0.0.0 -p 2432
399
+
400
+ # HOST_B:
401
+ curl http://HOST_A:2432 -H 'Host: localhost--3000'
402
+ "
403
+ )
404
+ .arg(
405
+ Arg::new("port")
406
+ .short('p')
407
+ .long("port")
408
+ .value_name("PORT")
409
+ .help("Port to listen on (env: FBI_PROXY_PORT, default: 2432)")
410
+ .env("FBI_PROXY_PORT")
411
+ .default_value("2432")
412
+ )
413
+ .arg(
414
+ Arg::new("host")
415
+ .short('h')
416
+ .long("host")
417
+ .value_name("HOST")
418
+ .help("Host/IP address to bind to (env: FBI_PROXY_HOST, default: 127.0.0.1)")
419
+ .env("FBI_PROXY_HOST")
420
+ .default_value("127.0.0.1")
421
+ )
422
+ .arg(
423
+ Arg::new("domain")
424
+ .short('d')
425
+ .long("domain")
426
+ .value_name("DOMAIN")
427
+ .help("Domain filter - only accept requests for *.domain (env: FBI_PROXY_DOMAIN)")
428
+ .env("FBI_PROXY_DOMAIN")
429
+ .default_value("")
430
+ )
431
+ .get_matches();
432
+
433
+ let port = matches
434
+ .get_one::<String>("port")
435
+ .unwrap()
436
+ .parse::<u16>()
437
+ .unwrap_or_else(|_| {
438
+ error!("Invalid port value, using default 2432");
439
+ 2432
440
+ });
441
+
442
+ let host = matches.get_one::<String>("host").unwrap();
443
+ let domain = matches.get_one::<String>("domain").unwrap();
444
+
445
+ let domain_filter = if domain.is_empty() {
446
+ None
447
+ } else {
448
+ Some(domain.clone())
449
+ };
450
+
451
+ let rt = tokio::runtime::Runtime::new().unwrap();
452
+ rt.block_on(async {
453
+ info!(
454
+ "Starting FBI-Proxy on {}:{} with domain filter: {:?}",
455
+ host, port, domain_filter
456
+ );
457
+ if let Err(e) = start_proxy_server_with_options(host, port, domain_filter).await {
458
+ error!("Failed to start proxy server: {}", e);
459
+ }
460
+ });
461
+ }
@@ -1,17 +1,23 @@
1
1
  import fsp from "fs/promises";
2
2
  import { existsSync } from "fs";
3
- import { getProxyFilename } from "./getProxyFilename";
3
+ import { getFbiProxyFilename } from "./getProxyFilename";
4
4
  import { copyFile } from "fs/promises";
5
5
  import { $ } from "./dSpawn";
6
6
  import { mkdir } from "fs/promises";
7
7
 
8
8
  if (import.meta.main) {
9
- await buildFbiProxy();
9
+ await getFbiProxyBinary();
10
10
  }
11
11
 
12
- export async function buildFbiProxy({ rebuild = false } = {}) {
12
+ export async function getFbiProxyBinary({ rebuild = false } = {}) {
13
13
  const isWin = process.platform === "win32";
14
- const binaryName = getProxyFilename();
14
+ const binaryName = getFbiProxyFilename();
15
+
16
+ // Check for pre-built binary in Docker container
17
+ const dockerBinary = "/app/bin/fbi-proxy";
18
+ if (!rebuild && existsSync(dockerBinary)) {
19
+ return dockerBinary;
20
+ }
15
21
 
16
22
  const release = "./release/" + binaryName;
17
23
  const built = `./target/release/fbi-proxy${isWin ? ".exe" : ""}`;
package/ts/cli.ts CHANGED
@@ -4,7 +4,7 @@ import hotMemo from "hot-memo";
4
4
  import path from "path";
5
5
  import yargs from "yargs";
6
6
  import { hideBin } from "yargs/helpers";
7
- import { buildFbiProxy } from "./buildFbiProxy";
7
+ import { getFbiProxyBinary } from "./buildFbiProxy";
8
8
  import { $ } from "./dSpawn";
9
9
  import { downloadCaddy } from "./downloadCaddy";
10
10
  import { execa } from "execa";
@@ -37,7 +37,7 @@ const FBIHOST = argv.fbihost;
37
37
  const FBIPROXY_PORT = String(await getPort({ port: 2432 }));
38
38
 
39
39
  const proxyProcess = await hotMemo(async () => {
40
- const proxy = await buildFbiProxy();
40
+ const proxy = await getFbiProxyBinary();
41
41
  console.log("Starting Rust proxy server");
42
42
  const p = $.opt({
43
43
  env: {
@@ -74,7 +74,7 @@ if (argv.caddy) {
74
74
  });
75
75
  }
76
76
 
77
- console.log("all done");
77
+ console.log("All services started successfully!");
78
78
  // show process pids
79
79
  console.log(`Proxy server PID: ${proxyProcess.pid}`);
80
80
  if (caddyProcess) {
@@ -1,4 +1,4 @@
1
- export function getProxyFilename() {
1
+ export function getFbiProxyFilename() {
2
2
  return {
3
3
  "darwin-arm64": "fbi-proxy-darwin",
4
4
  "darwin-x64": "fbi-proxy-darwin",