diff --git a/build.rs b/build.rs index 2bc571c..f2d704b 100644 --- a/build.rs +++ b/build.rs @@ -60,18 +60,27 @@ fn main() { // `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. + // Single-file desktop builds: embed the matching nym-socks5-client into the + // goblin binary so the release ships as ONE self-contained file with no loose + // sidecar beside it. At startup the app extracts it to a per-user data dir and + // runs it (src/nym/sidecar.rs). Windows reads GOBLIN_NYM_WIN_BIN (a .exe); + // Linux/macOS read GOBLIN_NYM_UNIX_BIN. Android does NOT embed — its sidecar + // rides in the APK's jniLibs (the only exec-allowed location). println!("cargo:rerun-if-env-changed=GOBLIN_NYM_WIN_BIN"); + println!("cargo:rerun-if-env-changed=GOBLIN_NYM_UNIX_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") { + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + let embed = match target_os.as_str() { + "windows" => Some(("GOBLIN_NYM_WIN_BIN", "nym-socks5-client.exe")), + "android" => None, + _ => Some(("GOBLIN_NYM_UNIX_BIN", "nym-socks5-client")), + }; + if let Some((var, out_name)) = embed { + if let Ok(src) = env::var(var) { if !src.is_empty() { - let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join("nym-socks5-client.exe"); + let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join(out_name); std::fs::copy(&src, &out) - .expect("copy GOBLIN_NYM_WIN_BIN into OUT_DIR for embedding"); + .unwrap_or_else(|e| panic!("copy {var} into OUT_DIR for embedding: {e}")); println!("cargo:rustc-cfg=goblin_embed_nym"); println!("cargo:rerun-if-changed={}", src); } diff --git a/linux/build_release.sh b/linux/build_release.sh index 7654cbe..04b1d13 100755 --- a/linux/build_release.sh +++ b/linux/build_release.sh @@ -1,27 +1,56 @@ #!/bin/bash +# +# Build a portable, single-file Goblin AppImage. +# +# Usage: linux/build_release.sh [platform] +# platform: 'x86_64' (default) or 'arm' +# +# The nym-socks5-client sidecar is EMBEDDED into the goblin binary (build.rs + +# src/nym/sidecar.rs, via GOBLIN_NYM_UNIX_BIN), so the AppImage ships as one +# self-contained file with no loose sidecar beside AppRun — matching the +# single-file Windows build. Point GOBLIN_NYM_UNIX_BIN at a glibc-2.17 sidecar +# (the portable one staged under the project root at nym-dist/) so the embedded +# copy is as portable as the host binary. -case $2 in - x86_64|arm) - ;; - *) - echo "Usage: release_linux.sh [platform] [version]\n - platform: 'x86_64', 'arm'" >&2 - exit 1 +set -euo pipefail + +platform="${1:-x86_64}" +case "${platform}" in + x86_64) arch="x86_64-unknown-linux-gnu" ;; + arm) arch="aarch64-unknown-linux-gnu" ;; + *) echo "Usage: build_release.sh [platform] (platform: 'x86_64' | 'arm')" >&2; exit 1 ;; esac -# Setup build directory -BASEDIR=$(cd $(dirname $0) && pwd) -cd ${BASEDIR} -cd .. +# Repo root (this script lives in linux/). +BASEDIR=$(cd "$(dirname "$0")" && pwd) +cd "${BASEDIR}/.." -# Setup platform argument -[[ $2 == "x86_64" ]] && arch+=(x86_64-unknown-linux-gnu) -[[ $2 == "arm" ]] && arch+=(aarch64-unknown-linux-gnu) +# Portable, glibc-2.17 sidecar to embed (override with GOBLIN_NYM_UNIX_BIN). +: "${GOBLIN_NYM_UNIX_BIN:=$(cd .. && pwd)/nym-dist/nym-socks5-client}" +if [[ ! -x "${GOBLIN_NYM_UNIX_BIN}" ]]; then + echo "error: sidecar to embed not found/executable: ${GOBLIN_NYM_UNIX_BIN}" >&2 + echo " set GOBLIN_NYM_UNIX_BIN to a built nym-socks5-client" >&2 + exit 1 +fi +export GOBLIN_NYM_UNIX_BIN -rustup target add ${arch} -cargo install cargo-zigbuild -cargo zigbuild --release --target ${arch} +rustup target add "${arch}" +command -v cargo-zigbuild >/dev/null || cargo install cargo-zigbuild -# Create AppImage with https://github.com/AppImage/appimagetool -cp target/${arch}/release/goblin linux/Goblin.AppDir/AppRun -rm target/${arch}/release/*.AppImage -appimagetool linux/Goblin.AppDir target/${arch}/release/goblin-v$2-linux-$1.AppImage \ No newline at end of file +# Portable cross-build to glibc 2.17. Two zig-specific fixes: +# - CRoaring's AVX512 path won't compile under zig's clang (evex512 error). +# - OpenSSL is vendored in Cargo.toml, so no system libssl is needed. +export CFLAGS_x86_64_unknown_linux_gnu="-DCROARING_COMPILER_SUPPORTS_AVX512=0" +export CXXFLAGS_x86_64_unknown_linux_gnu="-DCROARING_COMPILER_SUPPORTS_AVX512=0" +cargo zigbuild --release --target "${arch}.2.17" + +# Assemble the AppDir: AppRun IS the goblin binary (sidecar baked in), plus the +# icon + desktop entry. No loose sidecar file. +appdir="linux/Goblin.AppDir" +cp "target/${arch}/release/goblin" "${appdir}/AppRun" +chmod +x "${appdir}/AppRun" + +out="target/${arch}/release/Goblin-${platform}.AppImage" +rm -f "target/${arch}/release/"*.AppImage +ARCH=x86_64 appimagetool "${appdir}" "${out}" +echo "built: ${out}" diff --git a/src/nym/sidecar.rs b/src/nym/sidecar.rs index 2f6ef50..1e84f9c 100644 --- a/src/nym/sidecar.rs +++ b/src/nym/sidecar.rs @@ -39,10 +39,18 @@ use super::{SOCKS5_HOST, SOCKS5_PORT}; #[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). +/// The sidecar embedded into the goblin binary for single-file distribution. +/// Present only when build.rs was given `GOBLIN_NYM_WIN_BIN` (Windows) or +/// `GOBLIN_NYM_UNIX_BIN` (Linux/macOS) — i.e. release builds. Android never +/// embeds (its sidecar rides in the APK's jniLibs). #[cfg(all(target_os = "windows", goblin_embed_nym))] const EMBEDDED_SIDECAR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/nym-socks5-client.exe")); +#[cfg(all( + not(target_os = "windows"), + not(target_os = "android"), + goblin_embed_nym +))] +const EMBEDDED_SIDECAR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/nym-socks5-client")); /// Bundled SOCKS5 client binary name. Windows release archives ship the `.exe`; /// `Command`/`current_exe().parent().join(..)` need the suffix to find it. On @@ -117,10 +125,10 @@ 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))] + // Single-file desktop build: the sidecar is baked into the goblin binary — + // extract it once to the per-user data dir and run that. Falls through to a + // sibling binary when not embedded (a plain `cargo build`). + #[cfg(all(not(target_os = "android"), goblin_embed_nym))] if let Some(p) = extract_embedded_sidecar() { return p; } @@ -143,10 +151,11 @@ 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))] +/// Write the embedded sidecar to the per-user data dir (`%LOCALAPPDATA%\Goblin` +/// on Windows, `~/.local/share/Goblin` on Linux) once, or when the bundled copy +/// changed, and return its path. Keeps the release a single self-contained +/// binary with no loose helper to misplace. +#[cfg(all(not(target_os = "android"), goblin_embed_nym))] fn extract_embedded_sidecar() -> Option { let dir = dirs::data_local_dir()?.join("Goblin"); let _ = std::fs::create_dir_all(&dir); @@ -160,6 +169,16 @@ fn extract_embedded_sidecar() -> Option { warn!("nym: could not extract embedded sidecar: {e}"); return None; } + // Unix: a freshly written file isn't executable; the sidecar must be. + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + if let Err(e) = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)) + { + warn!("nym: could not mark embedded sidecar executable: {e}"); + return None; + } + } } Some(path) }