fbi-proxy 1.0.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/.gitattributes +31 -0
- package/.github/workflows/release.yml +112 -0
- package/.releaserc.json +37 -0
- package/CHANGELOG.md +17 -0
- package/Caddyfile +19 -0
- package/Dockerfile +38 -0
- package/INSTALL.md +161 -0
- package/LICENSE +21 -0
- package/PINGORA_PROXY.md +127 -0
- package/README.md +158 -0
- package/RUST_PROXY.md +85 -0
- package/build-proxy.bat +15 -0
- package/build-proxy.sh +15 -0
- package/bun.lock +1238 -0
- package/docker-compose.yml +9 -0
- package/package.json +36 -0
- package/release/proxy-linux-arm64/proxy-linux-arm64 +0 -0
- package/release/proxy-linux-x64/proxy-linux-x64 +0 -0
- package/release/proxy-macos-arm64/proxy-macos-arm64 +0 -0
- package/release/proxy-macos-x64/proxy-macos-x64 +0 -0
- package/release/proxy-windows-arm64.exe/proxy-windows-arm64.exe +0 -0
- package/release/proxy-windows-x64.exe/proxy-windows-x64.exe +0 -0
- package/release-flat/proxy-linux-arm64 +0 -0
- package/release-flat/proxy-linux-x64 +0 -0
- package/release-flat/proxy-macos-arm64 +0 -0
- package/release-flat/proxy-macos-x64 +0 -0
- package/release-flat/proxy-windows-arm64.exe +0 -0
- package/release-flat/proxy-windows-x64.exe +0 -0
- package/rs/.cargo/config.toml +5 -0
- package/rs/Cargo.lock +1220 -0
- package/rs/Cargo.toml +19 -0
- package/rs/build-windows.bat +60 -0
- package/rs/proxy.rs +248 -0
- package/ts/cli.ts +148 -0
- package/tsconfig.json +22 -0
package/rs/Cargo.toml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "fbi-proxy"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
|
|
6
|
+
[[bin]]
|
|
7
|
+
name = "proxy"
|
|
8
|
+
path = "proxy.rs"
|
|
9
|
+
|
|
10
|
+
[dependencies]
|
|
11
|
+
# Using simpler dependencies that build reliably on Windows
|
|
12
|
+
hyper = { version = "0.14", features = ["full"] }
|
|
13
|
+
hyper-tungstenite = "0.11"
|
|
14
|
+
tokio = { version = "1.0", features = ["full"] }
|
|
15
|
+
tokio-tungstenite = "0.20"
|
|
16
|
+
futures-util = "0.3"
|
|
17
|
+
regex = "1.0"
|
|
18
|
+
env_logger = "0.10"
|
|
19
|
+
log = "0.4"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
echo Installing required dependencies for Windows build...
|
|
3
|
+
|
|
4
|
+
echo.
|
|
5
|
+
echo Checking for required tools...
|
|
6
|
+
|
|
7
|
+
where cmake >nul 2>&1
|
|
8
|
+
if %errorlevel% neq 0 (
|
|
9
|
+
echo ❌ CMake not found. Installing via chocolatey...
|
|
10
|
+
where choco >nul 2>&1
|
|
11
|
+
if %errorlevel% neq 0 (
|
|
12
|
+
echo Please install Chocolatey first: https://chocolatey.org/install
|
|
13
|
+
echo Or install CMake manually: https://cmake.org/download/
|
|
14
|
+
pause
|
|
15
|
+
exit /b 1
|
|
16
|
+
)
|
|
17
|
+
choco install cmake -y
|
|
18
|
+
) else (
|
|
19
|
+
echo ✅ CMake found
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
where perl >nul 2>&1
|
|
23
|
+
if %errorlevel% neq 0 (
|
|
24
|
+
echo ❌ Perl not found. Installing Strawberry Perl...
|
|
25
|
+
where choco >nul 2>&1
|
|
26
|
+
if %errorlevel% neq 0 (
|
|
27
|
+
echo Please install Chocolatey first or install Strawberry Perl manually
|
|
28
|
+
echo Strawberry Perl: https://strawberryperl.com/
|
|
29
|
+
pause
|
|
30
|
+
exit /b 1
|
|
31
|
+
)
|
|
32
|
+
choco install strawberryperl -y
|
|
33
|
+
) else (
|
|
34
|
+
echo ✅ Perl found
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
echo.
|
|
38
|
+
echo Setting environment variables for Windows build...
|
|
39
|
+
set OPENSSL_NO_VENDOR=1
|
|
40
|
+
set CMAKE_GENERATOR=Visual Studio 17 2022
|
|
41
|
+
|
|
42
|
+
echo.
|
|
43
|
+
echo Building Rust proxy with Pingora...
|
|
44
|
+
cargo build --release
|
|
45
|
+
|
|
46
|
+
if %errorlevel% equ 0 (
|
|
47
|
+
echo.
|
|
48
|
+
echo ✅ Build successful!
|
|
49
|
+
echo Binary location: target\release\proxy.exe
|
|
50
|
+
) else (
|
|
51
|
+
echo.
|
|
52
|
+
echo ❌ Build failed. Check the error messages above.
|
|
53
|
+
echo.
|
|
54
|
+
echo Common solutions:
|
|
55
|
+
echo 1. Restart your terminal after installing dependencies
|
|
56
|
+
echo 2. Make sure Visual Studio Build Tools are installed
|
|
57
|
+
echo 3. Try: cargo clean && cargo build --release
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
pause
|
package/rs/proxy.rs
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
use futures_util::{SinkExt, StreamExt};
|
|
2
|
+
use hyper::header::{HeaderValue, HOST};
|
|
3
|
+
use hyper::service::{make_service_fn, service_fn};
|
|
4
|
+
use hyper::{Body, Client, Request, Response, Server, StatusCode, Uri};
|
|
5
|
+
use hyper_tungstenite::{HyperWebsocket, WebSocketStream};
|
|
6
|
+
use log::{error, info};
|
|
7
|
+
use regex::Regex;
|
|
8
|
+
use std::convert::Infallible;
|
|
9
|
+
use std::net::SocketAddr;
|
|
10
|
+
use std::sync::Arc;
|
|
11
|
+
use tokio_tungstenite::connect_async;
|
|
12
|
+
|
|
13
|
+
type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
|
14
|
+
|
|
15
|
+
pub struct FBIProxy {
|
|
16
|
+
client: Client<hyper::client::HttpConnector>,
|
|
17
|
+
port_regex: Regex,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl FBIProxy {
|
|
21
|
+
pub fn new() -> Self {
|
|
22
|
+
Self {
|
|
23
|
+
client: Client::new(),
|
|
24
|
+
port_regex: Regex::new(r"--(\d+).*$").unwrap(),
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn extract_target_host(&self, host_header: &str) -> String {
|
|
29
|
+
self.port_regex.replace(host_header, ":$1").to_string()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub async fn handle_request(&self, mut req: Request<Body>) -> Result<Response<Body>, BoxError> {
|
|
33
|
+
// Extract host from headers and process port encoding
|
|
34
|
+
let host_header = req
|
|
35
|
+
.headers()
|
|
36
|
+
.get(HOST)
|
|
37
|
+
.and_then(|h| h.to_str().ok())
|
|
38
|
+
.unwrap_or("localhost");
|
|
39
|
+
|
|
40
|
+
let target_host = self.extract_target_host(host_header);
|
|
41
|
+
info!("Proxying {} {} -> {}", req.method(), req.uri(), target_host);
|
|
42
|
+
|
|
43
|
+
// Handle WebSocket upgrade requests
|
|
44
|
+
if hyper_tungstenite::is_upgrade_request(&req) {
|
|
45
|
+
return self.handle_websocket_upgrade(req, &target_host).await;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Build target URL for HTTP requests
|
|
49
|
+
let uri = req.uri();
|
|
50
|
+
let target_url = format!(
|
|
51
|
+
"http://{}{}",
|
|
52
|
+
target_host,
|
|
53
|
+
uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("/")
|
|
54
|
+
);
|
|
55
|
+
let target_uri: Uri = target_url.parse()?;
|
|
56
|
+
|
|
57
|
+
// Update request URI and headers
|
|
58
|
+
*req.uri_mut() = target_uri;
|
|
59
|
+
req.headers_mut()
|
|
60
|
+
.insert(HOST, HeaderValue::from_str("localhost")?);
|
|
61
|
+
req.headers_mut().remove("content-encoding");
|
|
62
|
+
|
|
63
|
+
// Forward the request
|
|
64
|
+
match self.client.request(req).await {
|
|
65
|
+
Ok(mut response) => {
|
|
66
|
+
// Remove content-encoding header from response
|
|
67
|
+
response.headers_mut().remove("content-encoding");
|
|
68
|
+
info!("HTTP {} -> {}", target_url, response.status());
|
|
69
|
+
Ok(response)
|
|
70
|
+
}
|
|
71
|
+
Err(e) => {
|
|
72
|
+
error!("Proxy error for {}: {}", target_url, e);
|
|
73
|
+
Ok(Response::builder()
|
|
74
|
+
.status(StatusCode::BAD_GATEWAY)
|
|
75
|
+
.body(Body::from("Gateway Error"))?)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async fn handle_websocket_upgrade(
|
|
81
|
+
&self,
|
|
82
|
+
req: Request<Body>,
|
|
83
|
+
target_host: &str,
|
|
84
|
+
) -> Result<Response<Body>, BoxError> {
|
|
85
|
+
let uri = req.uri();
|
|
86
|
+
let ws_url = format!(
|
|
87
|
+
"ws://{}{}",
|
|
88
|
+
target_host,
|
|
89
|
+
uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("/")
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
info!("WebSocket upgrade to: {}", ws_url);
|
|
93
|
+
|
|
94
|
+
// Upgrade the HTTP connection to WebSocket
|
|
95
|
+
let (response, websocket) = hyper_tungstenite::upgrade(req, None)?;
|
|
96
|
+
|
|
97
|
+
// Connect to upstream WebSocket
|
|
98
|
+
info!("Connecting to upstream WebSocket: {}", ws_url);
|
|
99
|
+
let (upstream_ws, _) = match connect_async(&ws_url).await {
|
|
100
|
+
Ok(ws) => ws,
|
|
101
|
+
Err(e) => {
|
|
102
|
+
error!("Failed to connect to upstream WebSocket {}: {}", ws_url, e);
|
|
103
|
+
return Ok(Response::builder()
|
|
104
|
+
.status(StatusCode::BAD_GATEWAY)
|
|
105
|
+
.body(Body::from("WebSocket connection failed"))?);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Spawn task to handle WebSocket forwarding
|
|
110
|
+
info!("Starting WebSocket forwarding for: {}", ws_url);
|
|
111
|
+
let ws_url_clone = ws_url.clone();
|
|
112
|
+
tokio::spawn(async move {
|
|
113
|
+
if let Err(e) = handle_websocket_forwarding(websocket, upstream_ws).await {
|
|
114
|
+
error!("WebSocket forwarding error for {}: {}", ws_url_clone, e);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
info!("WebSocket upgrade successful for: {}", ws_url);
|
|
119
|
+
Ok(response)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async fn handle_websocket_forwarding(
|
|
124
|
+
websocket: HyperWebsocket,
|
|
125
|
+
upstream_ws: WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>,
|
|
126
|
+
) -> Result<(), BoxError> {
|
|
127
|
+
// Get the client WebSocket stream
|
|
128
|
+
let client_ws = websocket.await?;
|
|
129
|
+
|
|
130
|
+
let (mut client_sink, mut client_stream) = client_ws.split();
|
|
131
|
+
let (mut upstream_sink, mut upstream_stream) = upstream_ws.split();
|
|
132
|
+
|
|
133
|
+
info!("WebSocket connection established, starting bidirectional forwarding");
|
|
134
|
+
|
|
135
|
+
// Forward messages from client to upstream
|
|
136
|
+
let client_to_upstream = async {
|
|
137
|
+
while let Some(msg) = client_stream.next().await {
|
|
138
|
+
match msg {
|
|
139
|
+
Ok(msg) => {
|
|
140
|
+
info!("Client -> Upstream: {:?}", msg);
|
|
141
|
+
if let Err(e) = upstream_sink.send(msg).await {
|
|
142
|
+
error!("Failed to forward message to upstream: {}", e);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
Err(e) => {
|
|
147
|
+
error!("Error receiving from client: {}", e);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
info!("Client-to-upstream forwarding ended");
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Forward messages from upstream to client
|
|
156
|
+
let upstream_to_client = async {
|
|
157
|
+
while let Some(msg) = upstream_stream.next().await {
|
|
158
|
+
match msg {
|
|
159
|
+
Ok(msg) => {
|
|
160
|
+
info!("Upstream -> Client: {:?}", msg);
|
|
161
|
+
if let Err(e) = client_sink.send(msg).await {
|
|
162
|
+
error!("Failed to forward message to client: {}", e);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
Err(e) => {
|
|
167
|
+
error!("Error receiving from upstream: {}", e);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
info!("Upstream-to-client forwarding ended");
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Run both forwarding tasks concurrently
|
|
176
|
+
tokio::select! {
|
|
177
|
+
_ = client_to_upstream => {
|
|
178
|
+
info!("Client disconnected");
|
|
179
|
+
}
|
|
180
|
+
_ = upstream_to_client => {
|
|
181
|
+
info!("Upstream disconnected");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
info!("WebSocket forwarding session ended");
|
|
186
|
+
Ok(())
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async fn handle_connection(
|
|
190
|
+
req: Request<Body>,
|
|
191
|
+
proxy: Arc<FBIProxy>,
|
|
192
|
+
) -> Result<Response<Body>, Infallible> {
|
|
193
|
+
match proxy.handle_request(req).await {
|
|
194
|
+
Ok(response) => Ok(response),
|
|
195
|
+
Err(e) => {
|
|
196
|
+
error!("Request handling error: {}", e);
|
|
197
|
+
Ok(Response::builder()
|
|
198
|
+
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
199
|
+
.body(Body::from("Internal Server Error"))
|
|
200
|
+
.unwrap())
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
pub async fn start_proxy_server(port: u16) -> Result<(), BoxError> {
|
|
206
|
+
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
|
207
|
+
let proxy = Arc::new(FBIProxy::new());
|
|
208
|
+
|
|
209
|
+
let make_svc = make_service_fn(move |_conn| {
|
|
210
|
+
let proxy = proxy.clone();
|
|
211
|
+
async move { Ok::<_, Infallible>(service_fn(move |req| handle_connection(req, proxy.clone()))) }
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
let server = Server::bind(&addr).serve(make_svc);
|
|
215
|
+
|
|
216
|
+
info!("FBI Proxy server running on http://{}", addr);
|
|
217
|
+
info!("Features: HTTP proxying + WebSocket forwarding + Port encoding");
|
|
218
|
+
|
|
219
|
+
if let Err(e) = server.await {
|
|
220
|
+
error!("Server error: {}", e);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
Ok(())
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
fn main() {
|
|
227
|
+
env_logger::init();
|
|
228
|
+
|
|
229
|
+
// Read port from environment variable, default to 24306
|
|
230
|
+
let port = std::env::var("PROXY_PORT")
|
|
231
|
+
.unwrap_or_else(|_| "24306".to_string())
|
|
232
|
+
.parse::<u16>()
|
|
233
|
+
.unwrap_or_else(|_| {
|
|
234
|
+
error!("Invalid PROXY_PORT value, using default 24306");
|
|
235
|
+
24306
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
239
|
+
rt.block_on(async {
|
|
240
|
+
info!(
|
|
241
|
+
"Starting FBI Proxy with Hyper + proper WebSocket forwarding on port {}",
|
|
242
|
+
port
|
|
243
|
+
);
|
|
244
|
+
if let Err(e) = start_proxy_server(port).await {
|
|
245
|
+
error!("Failed to start proxy server: {}", e);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
package/ts/cli.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import getPort from "get-port";
|
|
3
|
+
import minimist from "minimist";
|
|
4
|
+
import hotMemo from "hot-memo";
|
|
5
|
+
import { exec } from "child_process";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { exists } from "fs/promises";
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
|
|
10
|
+
// guide to install caddy
|
|
11
|
+
if (!(await Bun.$`caddy --version`.text().catch(() => ""))) {
|
|
12
|
+
console.error("Caddy is not installed. Please install Caddy first");
|
|
13
|
+
console.error(`For windows, try running:\n choco install caddy\n`);
|
|
14
|
+
console.error(`For linux, try running:\n sudo apt install caddy\n`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const getProxyPath = () => {
|
|
19
|
+
const root = Bun.fileURLToPath(import.meta.url) + "../";
|
|
20
|
+
const filename =
|
|
21
|
+
{
|
|
22
|
+
"darwin-arm64": "fbi-proxy-darwin",
|
|
23
|
+
"darwin-x64": "fbi-proxy-darwin",
|
|
24
|
+
"linux-arm64": "fbi-proxy-linux-arm64",
|
|
25
|
+
"linux-x64": "fbi-proxy-linux-x64",
|
|
26
|
+
"linux-x86_64": "fbi-proxy-linux-x64",
|
|
27
|
+
"win32-arm64": "fbi-proxy-windows-arm64.exe",
|
|
28
|
+
"win32-x64": "fbi-proxy-windows-x64.exe",
|
|
29
|
+
}[process.platform + "-" + process.arch] || "fbi-proxy-linux-x64";
|
|
30
|
+
|
|
31
|
+
return [path.join(root, "rs/target/release", filename)].find((e) =>
|
|
32
|
+
existsSync(e)
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// assume caddy is installed, launch proxy server now
|
|
37
|
+
const argv = minimist(process.argv.slice(2), {
|
|
38
|
+
default: {
|
|
39
|
+
dev: false,
|
|
40
|
+
d: false,
|
|
41
|
+
tls: "internal", // default to internal TLS
|
|
42
|
+
},
|
|
43
|
+
alias: {
|
|
44
|
+
dev: "d",
|
|
45
|
+
tls: "t",
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
console.log(argv);
|
|
49
|
+
if (argv.help) {
|
|
50
|
+
console.log(`Usage: fbi-proxy [options]
|
|
51
|
+
Options:
|
|
52
|
+
--dev, -d Enable development mode
|
|
53
|
+
--tls, -t Set TLS mode (internal|external)
|
|
54
|
+
--help Show this help message
|
|
55
|
+
`);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const isDev = argv.dev || argv.d || false;
|
|
60
|
+
const PROXY_PORT = String(await getPort({ port: 24306 }));
|
|
61
|
+
const proxyProcess = await hotMemo(async () => {
|
|
62
|
+
console.log("Starting Rust proxy server");
|
|
63
|
+
|
|
64
|
+
// TODO: in production, build and start the Rust proxy server
|
|
65
|
+
// using `cargo build --release` and then run the binary
|
|
66
|
+
const p = await (async () => {
|
|
67
|
+
if (isDev) {
|
|
68
|
+
// TODO: consider switch to bacon, cargo install bacon
|
|
69
|
+
// in dev mode, use cargo watch to run the Rust proxy server
|
|
70
|
+
const p = exec(`cargo watch -x "run --bin proxy"`, {
|
|
71
|
+
env: {
|
|
72
|
+
...process.env,
|
|
73
|
+
PROXY_PORT,
|
|
74
|
+
},
|
|
75
|
+
cwd: path.join(__dirname, "../rs"),
|
|
76
|
+
});
|
|
77
|
+
return p;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const rsTargetDir = path.join(__dirname, "../rs", "target", "release");
|
|
81
|
+
const proxyBinary = process.platform === "win32" ? "proxy.exe" : "proxy";
|
|
82
|
+
const proxyPath = path.join(rsTargetDir, proxyBinary);
|
|
83
|
+
if (!(await exists(proxyPath).catch(() => false))) {
|
|
84
|
+
console.error("Proxy binary not found at " + proxyPath);
|
|
85
|
+
console.error(
|
|
86
|
+
"Please build the Rust proxy server first using `cargo build --release`"
|
|
87
|
+
);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
const p = exec(proxyPath, {
|
|
91
|
+
env: {
|
|
92
|
+
...process.env,
|
|
93
|
+
PROXY_PORT, // Rust proxy server port
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
return p;
|
|
97
|
+
})();
|
|
98
|
+
|
|
99
|
+
p.stdout?.pipe(process.stdout, { end: false });
|
|
100
|
+
p.stderr?.pipe(process.stderr, { end: false });
|
|
101
|
+
p.on("exit", (code) => {
|
|
102
|
+
console.log(`Proxy server exited with code ${code}`);
|
|
103
|
+
process.exit(code || 0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
console.log(`Rust proxy server started on port ${PROXY_PORT}`);
|
|
107
|
+
return p;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const caddyProcess = await hotMemo(async () => {
|
|
111
|
+
const Caddyfile = path.join(__dirname, "../Caddyfile");
|
|
112
|
+
if (!(await exists(Caddyfile).catch(() => false))) {
|
|
113
|
+
console.error("Caddyfile not found at " + Caddyfile);
|
|
114
|
+
console.error(
|
|
115
|
+
"Please create a Caddyfile in the root directory of the project."
|
|
116
|
+
);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
console.log("Starting Caddy");
|
|
120
|
+
const p = exec(`caddy run ${isDev ? "--watch" : ""} --config ${Caddyfile}`, {
|
|
121
|
+
env: {
|
|
122
|
+
...process.env,
|
|
123
|
+
PROXY_PORT, // Rust proxy server port
|
|
124
|
+
TLS: argv.tls || "internal", // Use internal TLS by default, or set via command line argument
|
|
125
|
+
},
|
|
126
|
+
cwd: path.dirname(Caddyfile),
|
|
127
|
+
});
|
|
128
|
+
// p.stdout?.pipe(process.stdout, { end: false });
|
|
129
|
+
// p.stderr?.pipe(process.stderr, { end: false });
|
|
130
|
+
p.on("exit", (code) => process.exit(code || 0));
|
|
131
|
+
console.log("Caddy started with config at " + Caddyfile);
|
|
132
|
+
return p;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
console.log("all done");
|
|
136
|
+
|
|
137
|
+
const exit = () => {
|
|
138
|
+
console.log("Shutting down...");
|
|
139
|
+
proxyProcess?.kill?.();
|
|
140
|
+
caddyProcess?.kill?.();
|
|
141
|
+
process.exit(0);
|
|
142
|
+
};
|
|
143
|
+
process.on("SIGINT", exit);
|
|
144
|
+
process.on("SIGTERM", exit);
|
|
145
|
+
process.on("uncaughtException", (err) => {
|
|
146
|
+
console.error("Uncaught exception:", err);
|
|
147
|
+
exit();
|
|
148
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Enable latest features
|
|
4
|
+
"lib": ["ESNext", "DOM"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true
|
|
21
|
+
}
|
|
22
|
+
}
|