fbi-proxy 1.6.1 → 1.7.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/README.md +75 -5
- package/dist/cli.js +10 -49
- package/package.json +2 -5
- package/release/fbi-proxy-linux-arm64 +0 -0
- package/release/fbi-proxy-linux-x64 +0 -0
- package/release/fbi-proxy-macos-arm64 +0 -0
- package/release/fbi-proxy-macos-x64 +0 -0
- package/release/fbi-proxy-windows-arm64.exe +0 -0
- package/release/fbi-proxy-windows-x64.exe +0 -0
- package/rs/fbi-proxy.rs +67 -44
- package/ts/buildFbiProxy.ts +13 -5
- package/ts/cli.ts +2 -41
- package/Caddyfile +0 -47
- package/ts/downloadCaddy.ts +0 -24
package/README.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
FBI-Proxy provides easy HTTPS access to your local services with intelligent domain routing.
|
|
4
4
|
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### Current Features ✅
|
|
8
|
+
|
|
9
|
+
- **Intelligent Domain Routing**: Multiple routing patterns for flexible service access
|
|
10
|
+
- Port-based routing (e.g., `3000.fbi.com` → `localhost:3000`)
|
|
11
|
+
- Host--Port routing (e.g., `api--3001.fbi.com` → `api:3001`)
|
|
12
|
+
- Subdomain routing with Host headers (e.g., `admin.app.fbi.com` → `app:80`)
|
|
13
|
+
- Direct host forwarding (e.g., `myserver.fbi.com` → `myserver:80`)
|
|
14
|
+
- **WebSocket Support**: Full WebSocket connection support for all routing patterns
|
|
15
|
+
- **High Performance**: Built with Rust for optimal performance and low resource usage
|
|
16
|
+
- **Easy Setup**: Simple one-command installation and startup
|
|
17
|
+
- **Docker Support**: Available as a Docker image for containerized deployments
|
|
18
|
+
- **Flexible Configuration**: Environment variables and CLI options for customization
|
|
19
|
+
- **Cross-Platform**: Works on macOS, Linux, and Windows
|
|
20
|
+
- **Integration Ready**: Compatible with reverse proxies like Caddy for HTTPS
|
|
21
|
+
|
|
22
|
+
## Roadmap
|
|
23
|
+
|
|
24
|
+
### Next Up 🚧
|
|
25
|
+
- [ ] **Configuration File Support** - YAML/JSON config for persistent routing rules
|
|
26
|
+
- [ ] **Access Control** - Domain filtering, host/port whitelisting
|
|
27
|
+
- [ ] **Request Logging** - Basic access logs for debugging
|
|
28
|
+
- [ ] **Health Checks** - Simple upstream service availability monitoring
|
|
29
|
+
|
|
30
|
+
### Future Improvements 🔮
|
|
31
|
+
- [ ] **Load Balancing** - Round-robin between multiple upstream targets
|
|
32
|
+
- [ ] **Metrics** - Basic statistics (requests, response times, errors)
|
|
33
|
+
- [ ] **Hot Reload** - Update configuration without restart
|
|
34
|
+
- [ ] **Custom Headers** - Add/modify headers for specific routes
|
|
35
|
+
|
|
5
36
|
## Routing Examples
|
|
6
37
|
|
|
7
38
|
```bash
|
|
@@ -32,13 +63,53 @@ bunx fbi-proxy
|
|
|
32
63
|
# expose to LAN
|
|
33
64
|
bunx fbi-proxy --host 0.0.0.0 --port=2432
|
|
34
65
|
|
|
35
|
-
# with
|
|
36
|
-
|
|
66
|
+
# run with docker
|
|
67
|
+
docker run --rm --name fbi-proxy --network=host snomiao/fbi-proxy
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Using with Caddy (Optional)
|
|
71
|
+
|
|
72
|
+
FBI-Proxy focuses on the core proxy functionality. For HTTPS and advanced routing, you can use Caddy as a reverse proxy:
|
|
73
|
+
|
|
74
|
+
### Install Caddy
|
|
37
75
|
|
|
38
|
-
|
|
39
|
-
|
|
76
|
+
```bash
|
|
77
|
+
# macOS
|
|
78
|
+
brew install caddy
|
|
79
|
+
|
|
80
|
+
# Ubuntu/Debian
|
|
81
|
+
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
|
|
82
|
+
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
|
83
|
+
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
|
|
84
|
+
sudo apt update
|
|
85
|
+
sudo apt install caddy
|
|
86
|
+
|
|
87
|
+
# Or download from https://caddyserver.com/download
|
|
40
88
|
```
|
|
41
89
|
|
|
90
|
+
### Caddyfile Example
|
|
91
|
+
|
|
92
|
+
Create a `Caddyfile` to route `*.fbi.com` to FBI-Proxy:
|
|
93
|
+
|
|
94
|
+
```caddyfile
|
|
95
|
+
*.fbi.com {
|
|
96
|
+
reverse_proxy localhost:2432
|
|
97
|
+
tls internal
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Run Both Services
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Terminal 1: Start FBI-Proxy
|
|
105
|
+
bunx fbi-proxy
|
|
106
|
+
|
|
107
|
+
# Terminal 2: Start Caddy
|
|
108
|
+
caddy run --config Caddyfile
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Now you can access your services via HTTPS at `https://*.fbi.com`!
|
|
112
|
+
|
|
42
113
|
## Development
|
|
43
114
|
|
|
44
115
|
```bash
|
|
@@ -56,7 +127,6 @@ bun run build && bun run start
|
|
|
56
127
|
|
|
57
128
|
- **Bun**: https://bun.sh/
|
|
58
129
|
- **Rust**: https://rustup.rs/
|
|
59
|
-
- **Caddy**: Auto-downloaded if not found
|
|
60
130
|
|
|
61
131
|
### Configuration
|
|
62
132
|
|
package/dist/cli.js
CHANGED
|
@@ -4989,6 +4989,7 @@ var yargs_default = Yargs;
|
|
|
4989
4989
|
|
|
4990
4990
|
// ts/buildFbiProxy.ts
|
|
4991
4991
|
import { existsSync } from "fs";
|
|
4992
|
+
import { chmod } from "fs/promises";
|
|
4992
4993
|
|
|
4993
4994
|
// ts/getProxyFilename.ts
|
|
4994
4995
|
function getFbiProxyFilename() {
|
|
@@ -5124,13 +5125,18 @@ async function getFbiProxyBinary({ rebuild = false } = {}) {
|
|
|
5124
5125
|
const release = "./release/" + binaryName;
|
|
5125
5126
|
const built = `./target/release/fbi-proxy${isWin ? ".exe" : ""}`;
|
|
5126
5127
|
if (!rebuild && existsSync(built)) {
|
|
5128
|
+
await chmod(built, 493).catch(() => {});
|
|
5127
5129
|
return built;
|
|
5128
5130
|
}
|
|
5129
|
-
if (!rebuild && existsSync(release))
|
|
5131
|
+
if (!rebuild && existsSync(release)) {
|
|
5132
|
+
await chmod(release, 493).catch(() => {});
|
|
5130
5133
|
return release;
|
|
5134
|
+
}
|
|
5131
5135
|
await $`cargo build --release`;
|
|
5132
|
-
if (existsSync(built))
|
|
5136
|
+
if (existsSync(built)) {
|
|
5137
|
+
await chmod(built, 493).catch(() => {});
|
|
5133
5138
|
return built;
|
|
5139
|
+
}
|
|
5134
5140
|
throw new Error("Oops, failed to build fbi-proxy binary. Please check your Rust setup.");
|
|
5135
5141
|
}
|
|
5136
5142
|
|
|
@@ -5217,37 +5223,15 @@ var $2 = Object.assign(dSpawn2(), {
|
|
|
5217
5223
|
cwd: (path) => dSpawn2({ cwd: path })
|
|
5218
5224
|
});
|
|
5219
5225
|
|
|
5220
|
-
// ts/downloadCaddy.ts
|
|
5221
|
-
import { existsSync as existsSync2 } from "fs";
|
|
5222
|
-
if (false) {}
|
|
5223
|
-
async function downloadCaddy() {
|
|
5224
|
-
const pwdCaddy = "./node_modules/.bin/caddy";
|
|
5225
|
-
if (existsSync2(pwdCaddy))
|
|
5226
|
-
return pwdCaddy;
|
|
5227
|
-
if (await $`caddy --version`.catch(() => false)) {
|
|
5228
|
-
return "caddy";
|
|
5229
|
-
}
|
|
5230
|
-
throw new Error("Failed to download Caddy. Please install Caddy manually or check your network connection.");
|
|
5231
|
-
}
|
|
5232
|
-
|
|
5233
5226
|
// ts/cli.ts
|
|
5234
5227
|
process.chdir(path.resolve(import.meta.dir, ".."));
|
|
5235
|
-
|
|
5236
|
-
type: "string",
|
|
5237
|
-
default: "fbi.com",
|
|
5238
|
-
description: "Set the FBI host"
|
|
5239
|
-
}).option("caddy", {
|
|
5240
|
-
type: "boolean",
|
|
5241
|
-
default: false,
|
|
5242
|
-
description: "Start Caddy server"
|
|
5243
|
-
}).option("dev", {
|
|
5228
|
+
await yargs_default(hideBin(process.argv)).option("dev", {
|
|
5244
5229
|
alias: "d",
|
|
5245
5230
|
type: "boolean",
|
|
5246
5231
|
default: false,
|
|
5247
5232
|
description: "Run in development mode"
|
|
5248
5233
|
}).help().argv;
|
|
5249
5234
|
console.log("Preparing Binaries");
|
|
5250
|
-
var FBIHOST = argv.fbihost;
|
|
5251
5235
|
var FBIPROXY_PORT = String(await getPorts({ port: 2432 }));
|
|
5252
5236
|
var proxyProcess = await hotMemo(async () => {
|
|
5253
5237
|
const proxy = await getFbiProxyBinary();
|
|
@@ -5264,35 +5248,12 @@ var proxyProcess = await hotMemo(async () => {
|
|
|
5264
5248
|
});
|
|
5265
5249
|
return p;
|
|
5266
5250
|
});
|
|
5267
|
-
var caddyProcess = null;
|
|
5268
|
-
if (argv.caddy) {
|
|
5269
|
-
const caddy = await downloadCaddy();
|
|
5270
|
-
caddyProcess = await hotMemo(async () => {
|
|
5271
|
-
const p = $2.opt({
|
|
5272
|
-
env: {
|
|
5273
|
-
...process.env,
|
|
5274
|
-
FBIPROXY_PORT,
|
|
5275
|
-
FBIHOST
|
|
5276
|
-
}
|
|
5277
|
-
})`${caddy} run`.process;
|
|
5278
|
-
p.on("exit", (code) => {
|
|
5279
|
-
console.log(`Caddy exited with code ${code}`);
|
|
5280
|
-
process.exit(code || 0);
|
|
5281
|
-
});
|
|
5282
|
-
return p;
|
|
5283
|
-
});
|
|
5284
|
-
}
|
|
5285
5251
|
console.log("All services started successfully!");
|
|
5286
5252
|
console.log(`Proxy server PID: ${proxyProcess.pid}`);
|
|
5287
|
-
|
|
5288
|
-
console.log(`Caddy server PID: ${caddyProcess.pid}`);
|
|
5289
|
-
} else {
|
|
5290
|
-
console.log("Caddy server not started (use --caddy to start it)");
|
|
5291
|
-
}
|
|
5253
|
+
console.log(`Proxy server running on port: ${FBIPROXY_PORT}`);
|
|
5292
5254
|
var exit = () => {
|
|
5293
5255
|
console.log("Shutting down...");
|
|
5294
5256
|
proxyProcess?.kill?.();
|
|
5295
|
-
caddyProcess?.kill?.();
|
|
5296
5257
|
process.exit(0);
|
|
5297
5258
|
};
|
|
5298
5259
|
process.on("SIGINT", exit);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fbi-proxy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "FBI-Proxy provides easy HTTPS access to your local services with intelligent domain routing",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"development-tools",
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"fbi-proxy": "dist/cli.js"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
-
"Caddyfile",
|
|
28
27
|
"dist",
|
|
29
28
|
"release",
|
|
30
29
|
"rs",
|
|
@@ -75,7 +74,5 @@
|
|
|
75
74
|
"bun --bun prettier --write"
|
|
76
75
|
]
|
|
77
76
|
},
|
|
78
|
-
"trustedDependencies": [
|
|
79
|
-
"@radically-straightforward/caddy"
|
|
80
|
-
]
|
|
77
|
+
"trustedDependencies": []
|
|
81
78
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/rs/fbi-proxy.rs
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
use clap::{Arg, Command};
|
|
2
2
|
use futures_util::{SinkExt, StreamExt};
|
|
3
|
+
use http_body_util::{BodyExt, Full};
|
|
4
|
+
use hyper::body::{Bytes, Incoming};
|
|
3
5
|
use hyper::header::{HeaderValue, HOST};
|
|
4
|
-
use hyper::
|
|
5
|
-
use hyper::
|
|
6
|
+
use hyper::server::conn::http1;
|
|
7
|
+
use hyper::service::service_fn;
|
|
8
|
+
use hyper::{Request, Response, StatusCode, Uri};
|
|
6
9
|
use hyper_tungstenite::{HyperWebsocket, WebSocketStream};
|
|
10
|
+
use hyper_util::client::legacy::{Client, connect::HttpConnector};
|
|
11
|
+
use hyper_util::rt::TokioIo;
|
|
7
12
|
use log::{error, info};
|
|
8
13
|
use regex::Regex;
|
|
9
14
|
use std::convert::Infallible;
|
|
10
15
|
use std::net::SocketAddr;
|
|
11
16
|
use std::sync::Arc;
|
|
17
|
+
use tokio::net::TcpListener;
|
|
12
18
|
use tokio_tungstenite::connect_async;
|
|
13
19
|
|
|
14
20
|
type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
|
21
|
+
type BoxBody = http_body_util::combinators::BoxBody<Bytes, hyper::Error>;
|
|
15
22
|
|
|
16
23
|
pub struct FBIProxy {
|
|
17
|
-
client: Client<
|
|
24
|
+
client: Client<HttpConnector, BoxBody>,
|
|
18
25
|
number_regex: Regex,
|
|
19
26
|
domain_filter: Option<String>,
|
|
20
27
|
}
|
|
@@ -51,8 +58,12 @@ for subdomains
|
|
|
51
58
|
*/
|
|
52
59
|
impl FBIProxy {
|
|
53
60
|
pub fn new(domain_filter: Option<String>) -> Self {
|
|
61
|
+
let connector = HttpConnector::new();
|
|
62
|
+
let client = Client::builder(hyper_util::rt::TokioExecutor::new())
|
|
63
|
+
.build(connector);
|
|
64
|
+
|
|
54
65
|
Self {
|
|
55
|
-
client
|
|
66
|
+
client,
|
|
56
67
|
number_regex: Regex::new(r"^\d+$").unwrap(),
|
|
57
68
|
domain_filter,
|
|
58
69
|
}
|
|
@@ -131,7 +142,7 @@ impl FBIProxy {
|
|
|
131
142
|
Some((format!("{}:80", host), host.to_string()))
|
|
132
143
|
}
|
|
133
144
|
|
|
134
|
-
pub async fn handle_request(&self,
|
|
145
|
+
pub async fn handle_request(&self, req: Request<Incoming>) -> Result<Response<BoxBody>, BoxError> {
|
|
135
146
|
// Extract host from headers and process according to rules
|
|
136
147
|
let host_header = req
|
|
137
148
|
.headers()
|
|
@@ -157,7 +168,7 @@ impl FBIProxy {
|
|
|
157
168
|
);
|
|
158
169
|
return Ok(Response::builder()
|
|
159
170
|
.status(StatusCode::BAD_GATEWAY)
|
|
160
|
-
.body(
|
|
171
|
+
.body(Full::new(Bytes::from("Bad Gateway: Host not allowed")).map_err(|e| match e {}).boxed())?);
|
|
161
172
|
}
|
|
162
173
|
};
|
|
163
174
|
|
|
@@ -180,17 +191,22 @@ impl FBIProxy {
|
|
|
180
191
|
);
|
|
181
192
|
let target_uri: Uri = target_url.parse()?;
|
|
182
193
|
|
|
194
|
+
// Convert incoming body to a format the client can use
|
|
195
|
+
let (mut parts, incoming_body) = req.into_parts();
|
|
196
|
+
let body = incoming_body.map_err(|e| e).boxed();
|
|
197
|
+
|
|
183
198
|
// Update request URI and headers
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
199
|
+
parts.uri = target_uri;
|
|
200
|
+
parts.headers.insert(HOST, HeaderValue::from_str(&new_host)?);
|
|
201
|
+
// Preserve content-encoding header to maintain compression
|
|
202
|
+
|
|
203
|
+
// Rebuild the request with the converted body
|
|
204
|
+
let new_req = Request::from_parts(parts, body);
|
|
188
205
|
|
|
189
206
|
// Forward the request
|
|
190
|
-
match self.client.request(
|
|
191
|
-
Ok(
|
|
192
|
-
//
|
|
193
|
-
response.headers_mut().remove("content-encoding");
|
|
207
|
+
match self.client.request(new_req).await {
|
|
208
|
+
Ok(response) => {
|
|
209
|
+
// Preserve content-encoding header in response to maintain compression
|
|
194
210
|
let status = response.status();
|
|
195
211
|
info!(
|
|
196
212
|
"{} {}@{}{} {}",
|
|
@@ -200,7 +216,10 @@ impl FBIProxy {
|
|
|
200
216
|
original_uri,
|
|
201
217
|
status.as_u16()
|
|
202
218
|
);
|
|
203
|
-
|
|
219
|
+
// Convert the response body back to BoxBody
|
|
220
|
+
let (parts, body) = response.into_parts();
|
|
221
|
+
let boxed_body = body.map_err(|e| e).boxed();
|
|
222
|
+
Ok(Response::from_parts(parts, boxed_body))
|
|
204
223
|
}
|
|
205
224
|
Err(e) => {
|
|
206
225
|
error!(
|
|
@@ -213,17 +232,17 @@ impl FBIProxy {
|
|
|
213
232
|
);
|
|
214
233
|
Ok(Response::builder()
|
|
215
234
|
.status(StatusCode::BAD_GATEWAY)
|
|
216
|
-
.body(
|
|
235
|
+
.body(Full::new(Bytes::from("FBIPROXY ERROR")).map_err(|e| match e {}).boxed())?)
|
|
217
236
|
}
|
|
218
237
|
}
|
|
219
238
|
}
|
|
220
239
|
|
|
221
240
|
async fn handle_websocket_upgrade(
|
|
222
241
|
&self,
|
|
223
|
-
req: Request<
|
|
242
|
+
req: Request<Incoming>,
|
|
224
243
|
target_host: &str,
|
|
225
244
|
_new_host: &str, // Currently not used for WebSocket connections, but kept for consistency
|
|
226
|
-
) -> Result<Response<
|
|
245
|
+
) -> Result<Response<BoxBody>, BoxError> {
|
|
227
246
|
let uri = req.uri().clone();
|
|
228
247
|
let ws_url = format!(
|
|
229
248
|
"ws://{}{}",
|
|
@@ -241,7 +260,7 @@ impl FBIProxy {
|
|
|
241
260
|
error!("WS :ws:{} => :ws:{}{} 502 ({})", target_host, target_host, uri, e);
|
|
242
261
|
return Ok(Response::builder()
|
|
243
262
|
.status(StatusCode::BAD_GATEWAY)
|
|
244
|
-
.body(
|
|
263
|
+
.body(Full::new(Bytes::from("WebSocket connection failed")).map_err(|e| match e {}).boxed())?);
|
|
245
264
|
}
|
|
246
265
|
};
|
|
247
266
|
|
|
@@ -254,7 +273,9 @@ impl FBIProxy {
|
|
|
254
273
|
});
|
|
255
274
|
|
|
256
275
|
info!("WS :ws:{} => :ws:{}{} 101", target_host, target_host, uri);
|
|
257
|
-
|
|
276
|
+
let (parts, body) = response.into_parts();
|
|
277
|
+
let boxed_body = body.map_err(|_: std::convert::Infallible| unreachable!()).boxed();
|
|
278
|
+
Ok(Response::from_parts(parts, boxed_body))
|
|
258
279
|
}
|
|
259
280
|
}
|
|
260
281
|
|
|
@@ -305,39 +326,27 @@ async fn handle_websocket_forwarding(
|
|
|
305
326
|
}
|
|
306
327
|
|
|
307
328
|
async fn handle_connection(
|
|
308
|
-
req: Request<
|
|
329
|
+
req: Request<Incoming>,
|
|
309
330
|
proxy: Arc<FBIProxy>,
|
|
310
|
-
) -> Result<Response<
|
|
331
|
+
) -> Result<Response<BoxBody>, Infallible> {
|
|
311
332
|
match proxy.handle_request(req).await {
|
|
312
333
|
Ok(response) => Ok(response),
|
|
313
334
|
Err(e) => {
|
|
314
335
|
error!("Request handling error: {}", e);
|
|
315
336
|
Ok(Response::builder()
|
|
316
337
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
317
|
-
.body(
|
|
338
|
+
.body(Full::new(Bytes::from("Internal Server Error")).map_err(|e| match e {}).boxed())
|
|
318
339
|
.unwrap())
|
|
319
340
|
}
|
|
320
341
|
}
|
|
321
342
|
}
|
|
322
343
|
|
|
323
|
-
pub async fn start_proxy_server(port: u16) -> Result<(), BoxError> {
|
|
324
|
-
|
|
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> {
|
|
344
|
+
pub async fn start_proxy_server(host: Option<&str>, port: u16, domain_filter: Option<String>) -> Result<(), BoxError> {
|
|
345
|
+
let host = host.unwrap_or("127.0.0.1");
|
|
332
346
|
let addr: SocketAddr = format!("{}:{}", host, port).parse()?;
|
|
333
347
|
let proxy = Arc::new(FBIProxy::new(domain_filter.clone()));
|
|
334
348
|
|
|
335
|
-
let
|
|
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);
|
|
349
|
+
let listener = TcpListener::bind(addr).await?;
|
|
341
350
|
|
|
342
351
|
info!("FBI Proxy server running on http://{}", addr);
|
|
343
352
|
println!("FBI Proxy listening on: http://{}", addr);
|
|
@@ -351,17 +360,31 @@ pub async fn start_proxy_server_with_options(host: &str, port: u16, domain_filte
|
|
|
351
360
|
|
|
352
361
|
info!("Features: HTTP proxying + WebSocket forwarding + Port encoding + Domain filtering");
|
|
353
362
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
363
|
+
loop {
|
|
364
|
+
let (stream, _) = listener.accept().await?;
|
|
365
|
+
let io = TokioIo::new(stream);
|
|
366
|
+
let proxy = proxy.clone();
|
|
357
367
|
|
|
358
|
-
|
|
368
|
+
tokio::task::spawn(async move {
|
|
369
|
+
let service = service_fn(move |req| handle_connection(req, proxy.clone()));
|
|
370
|
+
|
|
371
|
+
if let Err(err) = http1::Builder::new()
|
|
372
|
+
.preserve_header_case(true)
|
|
373
|
+
.title_case_headers(true)
|
|
374
|
+
.serve_connection(io, service)
|
|
375
|
+
.with_upgrades()
|
|
376
|
+
.await
|
|
377
|
+
{
|
|
378
|
+
error!("Error serving connection: {:?}", err);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
359
382
|
}
|
|
360
383
|
|
|
361
384
|
fn main() {
|
|
362
385
|
env_logger::init();
|
|
363
386
|
|
|
364
|
-
let matches = Command::new("
|
|
387
|
+
let matches = Command::new(env!("CARGO_CRATE_NAME"))
|
|
365
388
|
.version("0.1.1")
|
|
366
389
|
.about("A fast and flexible proxy server with smart host header parsing and WebSocket support")
|
|
367
390
|
.long_about(
|
|
@@ -454,7 +477,7 @@ TRY RUN:
|
|
|
454
477
|
"Starting FBI-Proxy on {}:{} with domain filter: {:?}",
|
|
455
478
|
host, port, domain_filter
|
|
456
479
|
);
|
|
457
|
-
if let Err(e) =
|
|
480
|
+
if let Err(e) = start_proxy_server(Some(host), port, domain_filter).await {
|
|
458
481
|
error!("Failed to start proxy server: {}", e);
|
|
459
482
|
}
|
|
460
483
|
});
|
package/ts/buildFbiProxy.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import fsp from "fs/promises";
|
|
2
1
|
import { existsSync } from "fs";
|
|
2
|
+
import { chmod } from "fs/promises";
|
|
3
3
|
import { getFbiProxyFilename } from "./getProxyFilename";
|
|
4
|
-
import { copyFile } from "fs/promises";
|
|
5
4
|
import { $ } from "./dSpawn";
|
|
6
|
-
import { mkdir } from "fs/promises";
|
|
7
5
|
|
|
8
6
|
if (import.meta.main) {
|
|
9
7
|
await getFbiProxyBinary();
|
|
@@ -24,15 +22,25 @@ export async function getFbiProxyBinary({ rebuild = false } = {}) {
|
|
|
24
22
|
|
|
25
23
|
// return built if exists
|
|
26
24
|
if (!rebuild && existsSync(built)) {
|
|
25
|
+
// Ensure the binary has execute permissions
|
|
26
|
+
await chmod(built, 0o755).catch(() => {}); // Ignore errors if we can't change permissions
|
|
27
27
|
return built;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// return release if exists
|
|
31
|
-
if (!rebuild && existsSync(release))
|
|
31
|
+
if (!rebuild && existsSync(release)) {
|
|
32
|
+
// Ensure the binary has execute permissions
|
|
33
|
+
await chmod(release, 0o755).catch(() => {}); // Ignore errors if we can't change permissions
|
|
34
|
+
return release;
|
|
35
|
+
}
|
|
32
36
|
|
|
33
37
|
// build and return built target
|
|
34
38
|
await $`cargo build --release`;
|
|
35
|
-
if (existsSync(built))
|
|
39
|
+
if (existsSync(built)) {
|
|
40
|
+
// Ensure the binary has execute permissions
|
|
41
|
+
await chmod(built, 0o755).catch(() => {}); // Ignore errors if we can't change permissions
|
|
42
|
+
return built;
|
|
43
|
+
}
|
|
36
44
|
|
|
37
45
|
throw new Error(
|
|
38
46
|
"Oops, failed to build fbi-proxy binary. Please check your Rust setup.",
|
package/ts/cli.ts
CHANGED
|
@@ -6,22 +6,11 @@ import yargs from "yargs";
|
|
|
6
6
|
import { hideBin } from "yargs/helpers";
|
|
7
7
|
import { getFbiProxyBinary } from "./buildFbiProxy";
|
|
8
8
|
import { $ } from "./dSpawn";
|
|
9
|
-
import { downloadCaddy } from "./downloadCaddy";
|
|
10
9
|
|
|
11
10
|
process.chdir(path.resolve(import.meta.dir, "..")); // Change to project root directory
|
|
12
11
|
|
|
13
12
|
// Parse command line arguments with yargs
|
|
14
|
-
|
|
15
|
-
.option("fbihost", {
|
|
16
|
-
type: "string",
|
|
17
|
-
default: "fbi.com",
|
|
18
|
-
description: "Set the FBI host",
|
|
19
|
-
})
|
|
20
|
-
.option("caddy", {
|
|
21
|
-
type: "boolean",
|
|
22
|
-
default: false,
|
|
23
|
-
description: "Start Caddy server",
|
|
24
|
-
})
|
|
13
|
+
await yargs(hideBin(process.argv))
|
|
25
14
|
.option("dev", {
|
|
26
15
|
alias: "d",
|
|
27
16
|
type: "boolean",
|
|
@@ -32,7 +21,6 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
32
21
|
|
|
33
22
|
console.log("Preparing Binaries");
|
|
34
23
|
|
|
35
|
-
const FBIHOST = argv.fbihost;
|
|
36
24
|
const FBIPROXY_PORT = String(await getPort({ port: 2432 }));
|
|
37
25
|
|
|
38
26
|
const proxyProcess = await hotMemo(async () => {
|
|
@@ -52,40 +40,13 @@ const proxyProcess = await hotMemo(async () => {
|
|
|
52
40
|
return p;
|
|
53
41
|
});
|
|
54
42
|
|
|
55
|
-
let caddyProcess: any = null;
|
|
56
|
-
|
|
57
|
-
// Only start Caddy if --caddy flag is passed
|
|
58
|
-
if (argv.caddy) {
|
|
59
|
-
const caddy = await downloadCaddy();
|
|
60
|
-
caddyProcess = await hotMemo(async () => {
|
|
61
|
-
const p = $.opt({
|
|
62
|
-
env: {
|
|
63
|
-
...process.env,
|
|
64
|
-
FBIPROXY_PORT, // Rust proxy server port
|
|
65
|
-
FBIHOST,
|
|
66
|
-
},
|
|
67
|
-
})`${caddy} run`.process;
|
|
68
|
-
p.on("exit", (code) => {
|
|
69
|
-
console.log(`Caddy exited with code ${code}`);
|
|
70
|
-
process.exit(code || 0);
|
|
71
|
-
});
|
|
72
|
-
return p;
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
43
|
console.log("All services started successfully!");
|
|
77
|
-
// show process pids
|
|
78
44
|
console.log(`Proxy server PID: ${proxyProcess.pid}`);
|
|
79
|
-
|
|
80
|
-
console.log(`Caddy server PID: ${caddyProcess.pid}`);
|
|
81
|
-
} else {
|
|
82
|
-
console.log("Caddy server not started (use --caddy to start it)");
|
|
83
|
-
}
|
|
45
|
+
console.log(`Proxy server running on port: ${FBIPROXY_PORT}`);
|
|
84
46
|
|
|
85
47
|
const exit = () => {
|
|
86
48
|
console.log("Shutting down...");
|
|
87
49
|
proxyProcess?.kill?.();
|
|
88
|
-
caddyProcess?.kill?.();
|
|
89
50
|
process.exit(0);
|
|
90
51
|
};
|
|
91
52
|
process.on("SIGINT", exit);
|
package/Caddyfile
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
admin off
|
|
3
|
-
# TODO: add ondemand tls endpoint to cli.ts
|
|
4
|
-
# - [Global options (Caddyfile) — Caddy Documentation]( https://caddyserver.com/docs/caddyfile/options#on-demand-tls )
|
|
5
|
-
# make a endpoint to handle /CHECK_TLS?domain=..., and return 200 if domain is valid
|
|
6
|
-
#
|
|
7
|
-
# on_demand_tls {
|
|
8
|
-
# ask http://localhost:{$PROX}/CHECK_TLS
|
|
9
|
-
# }
|
|
10
|
-
servers {
|
|
11
|
-
trusted_proxies static private_ranges
|
|
12
|
-
trusted_proxies_strict
|
|
13
|
-
}
|
|
14
|
-
# auto_https disable_redirects
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
# unwrap all https
|
|
18
|
-
http://*, http://*.*, http://*.*.*, http://*.*.*.*, https://*, https://*.*, https://*.*.*, https://*.*.*.* {
|
|
19
|
-
tls internal {
|
|
20
|
-
on_demand
|
|
21
|
-
}
|
|
22
|
-
# match **.fbi.com use regex match against to host
|
|
23
|
-
@subhost {
|
|
24
|
-
header_regexp subhost Host ^((?:(.+)\.)?{$FBIHOST:fbi.com})$
|
|
25
|
-
}
|
|
26
|
-
@fullhost {
|
|
27
|
-
header_regexp fullhost Host ^(.+)$
|
|
28
|
-
}
|
|
29
|
-
# debug
|
|
30
|
-
# handle @subhost {
|
|
31
|
-
# respond "https://{re.subhost.1}{uri} => http://{re.subhost.2}{uri}"
|
|
32
|
-
# }
|
|
33
|
-
|
|
34
|
-
reverse_proxy @subhost :{$FBIPROXY_PORT:24306} {
|
|
35
|
-
# strip the fbi.com part from the host
|
|
36
|
-
header_up Host {re.subhost.2}
|
|
37
|
-
# header_up X-Forwarded-Host {re.subhost.1}
|
|
38
|
-
}
|
|
39
|
-
reverse_proxy @fullhost :{$FBIPROXY_PORT:24306} {
|
|
40
|
-
header_up Host {re.fullhost.1}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
# 3000.amd.fbi.com => *.amd.fbi.com goes to amd.fbi.com with 3000.localhost host
|
|
44
|
-
|
|
45
|
-
# for all other hosts, 404
|
|
46
|
-
# respond 404
|
|
47
|
-
}
|
package/ts/downloadCaddy.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "fs";
|
|
2
|
-
import { $ } from "./dSpawn";
|
|
3
|
-
|
|
4
|
-
if (import.meta.main) {
|
|
5
|
-
// if this file is run directly, download caddy
|
|
6
|
-
const caddy = await downloadCaddy();
|
|
7
|
-
console.log(`Caddy downloaded to: ${caddy}`);
|
|
8
|
-
process.exit(0);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function downloadCaddy() {
|
|
12
|
-
// use pwdCaddy if already downloaded
|
|
13
|
-
const pwdCaddy = "./node_modules/.bin/caddy";
|
|
14
|
-
if (existsSync(pwdCaddy)) return pwdCaddy;
|
|
15
|
-
|
|
16
|
-
// // or use system caddy if installed, run `caddy --version` to check
|
|
17
|
-
if (await $`caddy --version`.catch(() => false)) {
|
|
18
|
-
return "caddy";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
throw new Error(
|
|
22
|
-
"Failed to download Caddy. Please install Caddy manually or check your network connection.",
|
|
23
|
-
);
|
|
24
|
-
}
|