1
0
forked from GRIN/grim

Build 64: embed the Nym sidecar into the Linux binary (single-file AppImage)

The Linux release no longer ships a loose nym-socks5-client beside AppRun.
It's baked into the goblin binary the same way the Windows build bakes it
into goblin.exe, and extracted to ~/.local/share/Goblin at first launch
(chmod +x on Unix). The AppImage is now one self-contained file with nothing
loose to misplace.

- build.rs: generalised the Windows-only GOBLIN_NYM_WIN_BIN embed to a
  cross-platform path. GOBLIN_NYM_UNIX_BIN embeds the Linux/macOS sidecar;
  Android never embeds (its sidecar rides in the APK's jniLibs).
- src/nym/sidecar.rs: the embedded const, extract_embedded_sidecar, and the
  binary_path extract branch now cover all non-Android targets, with a Unix
  chmod +x on the freshly written file.
- linux/build_release.sh: rewritten to set GOBLIN_NYM_UNIX_BIN, apply the
  glibc-2.17 zigbuild + CRoaring-AVX512/vendored-OpenSSL fixes, and assemble
  an AppDir with no loose sidecar.
This commit is contained in:
2ro
2026-06-14 02:23:10 -04:00
parent bfed0a1cb9
commit 8c48d2f5ce
3 changed files with 95 additions and 38 deletions
+17 -8
View File
@@ -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);
}
+49 -20
View File
@@ -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
# 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}"
+29 -10
View File
@@ -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<PathBuf> {
let dir = dirs::data_local_dir()?.join("Goblin");
let _ = std::fs::create_dir_all(&dir);
@@ -160,6 +169,16 @@ fn extract_embedded_sidecar() -> Option<PathBuf> {
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)
}