Build 54: Windows single-file + silent, logged sidecar
Address Windows feedback (visible console window; no diagnostics when the mixnet stalled): - Hide the sidecar console: spawn nym-socks5-client.exe with CREATE_NO_WINDOW so launching it no longer flashes a terminal. - All-in-one: embed the Windows sidecar into goblin.exe (build.rs, gated on GOBLIN_NYM_WIN_BIN) and extract it to %LOCALAPPDATA%\Goblin at first run, so the release is a single self-contained .exe with no loose helper to misplace. - Log the sidecar to ~/.goblin/nym-sidecar.log (all platforms) instead of a null sink, so a stalled bootstrap is diagnosable. Verified under wine: goblin.exe extracts the embedded sidecar, launches it, and it opens the SOCKS5 proxy on 127.0.0.1:1080.
This commit is contained in:
@@ -59,4 +59,22 @@ fn main() {
|
||||
// Goblin routes all traffic over the Nym mixnet via a bundled
|
||||
// `nym-socks5-client` sidecar (see src/nym/); there is no embedded Tor and
|
||||
// thus no webtunnel pluggable-transport binary to build here anymore.
|
||||
|
||||
// Single-file Windows: when GOBLIN_NYM_WIN_BIN points at the Windows
|
||||
// nym-socks5-client.exe, embed it into goblin.exe. At startup the app
|
||||
// extracts it to %LOCALAPPDATA%\Goblin and runs it hidden (src/nym/sidecar.rs),
|
||||
// so the release ships as one self-contained .exe with no loose sidecar file.
|
||||
println!("cargo:rerun-if-env-changed=GOBLIN_NYM_WIN_BIN");
|
||||
println!("cargo:rustc-check-cfg=cfg(goblin_embed_nym)");
|
||||
if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
|
||||
if let Ok(src) = env::var("GOBLIN_NYM_WIN_BIN") {
|
||||
if !src.is_empty() {
|
||||
let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join("nym-socks5-client.exe");
|
||||
std::fs::copy(&src, &out)
|
||||
.expect("copy GOBLIN_NYM_WIN_BIN into OUT_DIR for embedding");
|
||||
println!("cargo:rustc-cfg=goblin_embed_nym");
|
||||
println!("cargo:rerun-if-changed={}", src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+78
-15
@@ -29,8 +29,21 @@ use std::time::{Duration, Instant};
|
||||
use lazy_static::lazy_static;
|
||||
use log::{error, info, warn};
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
use super::{SOCKS5_HOST, SOCKS5_PORT};
|
||||
|
||||
/// CreateProcess flag (`CREATE_NO_WINDOW`): run the console-mode sidecar
|
||||
/// silently so launching it doesn't flash a terminal window on Windows.
|
||||
#[cfg(windows)]
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
|
||||
/// The Windows sidecar embedded into goblin.exe for single-file distribution.
|
||||
/// Present only when build.rs was given `GOBLIN_NYM_WIN_BIN` (release builds).
|
||||
#[cfg(all(target_os = "windows", goblin_embed_nym))]
|
||||
const EMBEDDED_SIDECAR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/nym-socks5-client.exe"));
|
||||
|
||||
/// Bundled SOCKS5 client binary name. Windows release archives ship the `.exe`;
|
||||
/// `Command`/`current_exe().parent().join(..)` need the suffix to find it. On
|
||||
/// Android the sidecar is shipped inside the APK's `jniLibs` as a `lib*.so` (the
|
||||
@@ -104,6 +117,13 @@ fn binary_path() -> PathBuf {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
// Windows single-file build: the sidecar is baked into goblin.exe — extract
|
||||
// it once to %LOCALAPPDATA%\Goblin and run that. Falls through to a sibling
|
||||
// .exe when not embedded (a plain `cargo build`).
|
||||
#[cfg(all(target_os = "windows", goblin_embed_nym))]
|
||||
if let Some(p) = extract_embedded_sidecar() {
|
||||
return p;
|
||||
}
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(dir) = exe.parent() {
|
||||
let sibling = dir.join(BIN_NAME);
|
||||
@@ -123,6 +143,55 @@ fn provider() -> String {
|
||||
.unwrap_or_else(|| NETWORK_REQUESTER.to_string())
|
||||
}
|
||||
|
||||
/// Write the embedded Windows sidecar to `%LOCALAPPDATA%\Goblin` (once, or when
|
||||
/// the bundled copy changed) and return its path. Keeps the release a single
|
||||
/// `goblin.exe` with no loose helper to misplace.
|
||||
#[cfg(all(target_os = "windows", goblin_embed_nym))]
|
||||
fn extract_embedded_sidecar() -> Option<PathBuf> {
|
||||
let dir = dirs::data_local_dir()?.join("Goblin");
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
let path = dir.join(BIN_NAME);
|
||||
let stale = match std::fs::metadata(&path) {
|
||||
Ok(m) => m.len() != EMBEDDED_SIDECAR.len() as u64,
|
||||
Err(_) => true,
|
||||
};
|
||||
if stale {
|
||||
if let Err(e) = std::fs::write(&path, EMBEDDED_SIDECAR) {
|
||||
warn!("nym: could not extract embedded sidecar: {e}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(path)
|
||||
}
|
||||
|
||||
/// A fresh handle to `<home>/.goblin/nym-sidecar.log` for the sidecar's output,
|
||||
/// so a failed bootstrap leaves a trace instead of vanishing into a null sink.
|
||||
/// Falls back to discarding output if the log can't be opened.
|
||||
fn log_sink() -> Stdio {
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
let dir = home.join(".goblin");
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
if let Ok(f) = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(dir.join("nym-sidecar.log"))
|
||||
{
|
||||
return Stdio::from(f);
|
||||
}
|
||||
}
|
||||
Stdio::null()
|
||||
}
|
||||
|
||||
/// Apply the cross-platform sidecar spawn settings: log its output to a file and,
|
||||
/// on Windows, suppress the console window.
|
||||
fn quiet_logged(cmd: &mut Command) {
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(log_sink())
|
||||
.stderr(log_sink());
|
||||
#[cfg(windows)]
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
/// `~/.nym/socks5-clients/<id>/config/config.toml` — present once initialized.
|
||||
fn config_marker() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|h| {
|
||||
@@ -148,14 +217,10 @@ fn launch() -> std::io::Result<()> {
|
||||
ensure_initialized(&bin);
|
||||
|
||||
info!("nym: launching SOCKS5 sidecar ({})", bin.display());
|
||||
let child = Command::new(&bin)
|
||||
.arg("run")
|
||||
.arg("--id")
|
||||
.arg(CLIENT_ID)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()?;
|
||||
let mut cmd = Command::new(&bin);
|
||||
cmd.arg("run").arg("--id").arg(CLIENT_ID);
|
||||
quiet_logged(&mut cmd);
|
||||
let child = cmd.spawn()?;
|
||||
*CHILD.lock().unwrap() = Some(child);
|
||||
|
||||
// The mixnet bootstraps in ~2s; give it generous headroom on cold start.
|
||||
@@ -179,16 +244,14 @@ fn ensure_initialized(bin: &PathBuf) {
|
||||
return;
|
||||
}
|
||||
info!("nym: initializing SOCKS5 client '{CLIENT_ID}'");
|
||||
let res = Command::new(bin)
|
||||
.arg("init")
|
||||
let mut cmd = Command::new(bin);
|
||||
cmd.arg("init")
|
||||
.arg("--id")
|
||||
.arg(CLIENT_ID)
|
||||
.arg("--provider")
|
||||
.arg(provider())
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status();
|
||||
.arg(provider());
|
||||
quiet_logged(&mut cmd);
|
||||
let res = cmd.status();
|
||||
match res {
|
||||
Ok(s) if s.success() => info!("nym: SOCKS5 client initialized"),
|
||||
Ok(s) => warn!("nym: SOCKS5 client init exited with {s}"),
|
||||
|
||||
Reference in New Issue
Block a user