#!/bin/bash # DragonX ImGui Wallet - Unified Build Script # Copyright 2024-2026 The Hush Developers # Released under the GPLv3 # # Usage: # ./build.sh # Dev build (Linux, debug-friendly) # ./build.sh --linux-release # Linux release + AppImage # ./build.sh --win-release # Windows cross-compile (mingw-w64) # ./build.sh --mac-release # macOS .app bundle + DMG # ./build.sh --linux-release --win-release # Multiple targets # ./build.sh --clean --win-release # Clean first, then build # # Prerequisites: # Linux: cmake, g++, libsdl3-dev (or fetched), libsodium-dev # Windows: mingw-w64 (posix threads), cmake # macOS (native): Xcode CLT, cmake, create-dmg (brew install create-dmg) # macOS (cross from Linux): osxcross + macOS SDK, genisoimage, icnsutils set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" VERSION="1.0.0" # ── Colours ────────────────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' info() { echo -e "${GREEN}[*]${NC} $1"; } warn() { echo -e "${YELLOW}[!]${NC} $1"; } err() { echo -e "${RED}[ERROR]${NC} $1"; } header(){ echo -e "\n${CYAN}══════════════════════════════════════════════════════════${NC}"; echo -e "${CYAN} $1${NC}"; echo -e "${CYAN}══════════════════════════════════════════════════════════${NC}"; } JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) # ── Defaults ───────────────────────────────────────────────────────────────── DO_DEV=false DO_LINUX=false DO_WIN=false DO_MAC=false CLEAN=false BUILD_TYPE="Release" usage() { cat < release/linux/ --win-release Windows cross-compile (mingw-w64) -> release/windows/ --mac-release macOS .app bundle + DMG -> release/mac/ Build trees are stored under build/{linux,windows,mac}/ Options: -c, --clean Remove build artifacts before building -d, --debug Use Debug build type instead of Release -j N Parallel jobs (default: $JOBS) -h, --help Show this help Cross-compiling from Linux: Windows: sudo apt install mingw-w64 macOS: Requires osxcross (https://github.com/tpoechtrager/osxcross) export OSXCROSS=/path/to/osxcross sudo apt install genisoimage icnsutils # DMG + icon tools Examples: $0 # Quick dev build (Linux) $0 --linux-release # Linux release + AppImage $0 --win-release # Windows cross-compile $0 --mac-release # macOS bundle + DMG (native or osxcross) $0 --clean --linux-release --win-release # Clean + both EOF exit 0 } # ── Parse args ─────────────────────────────────────────────────────────────── while [[ $# -gt 0 ]]; do case $1 in --linux-release) DO_LINUX=true; shift ;; --win-release) DO_WIN=true; shift ;; --mac-release) DO_MAC=true; shift ;; -c|--clean) CLEAN=true; shift ;; -d|--debug) BUILD_TYPE="Debug"; shift ;; -j) JOBS="$2"; shift 2 ;; -h|--help) usage ;; *) err "Unknown option: $1"; usage ;; esac done # No release target → dev build (native, no packaging) if ! $DO_LINUX && ! $DO_WIN && ! $DO_MAC; then DO_DEV=true fi # ── Helper: find resource files ────────────────────────────────────────────── find_sapling_params() { local dirs=( "$HOME/.zcash-params" "$HOME/.hush-params" "$SCRIPT_DIR/../SilentDragonX" "$SCRIPT_DIR/prebuilt-binaries/dragonxd-win" "$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux" "$SCRIPT_DIR" ) for d in "${dirs[@]}"; do if [[ -f "$d/sapling-spend.params" && -f "$d/sapling-output.params" ]]; then SAPLING_SPEND="$d/sapling-spend.params" SAPLING_OUTPUT="$d/sapling-output.params" info "Found Sapling params in $d" return 0 fi done warn "Sapling params not found — embedded resources will be unavailable" return 1 } find_asmap() { local paths=( "$SCRIPT_DIR/external/hush3/asmap.dat" "$SCRIPT_DIR/external/hush3/contrib/asmap/asmap.dat" "$HOME/hush3/asmap.dat" "$HOME/hush3/contrib/asmap/asmap.dat" "$SCRIPT_DIR/../asmap.dat" "$SCRIPT_DIR/asmap.dat" "$SCRIPT_DIR/../SilentDragonX/asmap.dat" "$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/asmap.dat" "$SCRIPT_DIR/prebuilt-binaries/dragonxd-win/asmap.dat" ) for p in "${paths[@]}"; do if [[ -f "$p" ]]; then ASMAP_DAT="$p" info "Found asmap.dat at $p" return 0 fi done return 1 } # ── Helper: bundle daemon binaries into a target dir ───────────────────────── bundle_linux_daemon() { local dest="$1" local found=0 local launcher_paths=( "$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/hush-arrakis-chain" "$SCRIPT_DIR/../hush-arrakis-chain" "$SCRIPT_DIR/external/hush3/src/hush-arrakis-chain" "$HOME/hush3/src/hush-arrakis-chain" ) for p in "${launcher_paths[@]}"; do if [[ -f "$p" ]]; then cp "$p" "$dest/hush-arrakis-chain"; chmod +x "$dest/hush-arrakis-chain" info " Bundled hush-arrakis-chain"; found=1; break fi done local hushd_paths=( "$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/hushd" "$SCRIPT_DIR/../hushd" "$SCRIPT_DIR/external/hush3/src/hushd" "$HOME/hush3/src/hushd" ) for p in "${hushd_paths[@]}"; do if [[ -f "$p" ]]; then cp "$p" "$dest/hushd"; chmod +x "$dest/hushd" info " Bundled hushd"; break fi done local dragonxd_paths=( "$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/dragonxd" "$SCRIPT_DIR/../dragonxd" "$SCRIPT_DIR/external/hush3/src/dragonxd" "$HOME/hush3/src/dragonxd" ) for p in "${dragonxd_paths[@]}"; do if [[ -f "$p" ]]; then cp "$p" "$dest/dragonxd"; chmod +x "$dest/dragonxd" info " Bundled dragonxd"; break fi done # asmap.dat find_asmap && cp "$ASMAP_DAT" "$dest/asmap.dat" && info " Bundled asmap.dat" return $found } # ═══════════════════════════════════════════════════════════════════════════════ # DEV BUILD (native, no packaging) # ═══════════════════════════════════════════════════════════════════════════════ build_dev() { header "Dev Build ($(uname -s) / $BUILD_TYPE)" local bd="$SCRIPT_DIR/build/linux" if $CLEAN; then info "Cleaning $bd ..."; rm -rf "$bd" fi mkdir -p "$bd" && cd "$bd" info "Configuring ..." cmake "$SCRIPT_DIR" \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \ -DDRAGONX_USE_SYSTEM_SDL3=ON info "Building with $JOBS jobs ..." cmake --build . -j "$JOBS" [[ -f "bin/ObsidianDragon" ]] || { err "Build failed"; exit 1; } info "Dev binary: $bd/bin/ObsidianDragon ($(du -h bin/ObsidianDragon | cut -f1))" } # ═══════════════════════════════════════════════════════════════════════════════ # RELEASE: LINUX — build + strip + bundle daemon + AppImage # ═══════════════════════════════════════════════════════════════════════════════ build_release_linux() { header "Release: Linux x86_64" local bd="$SCRIPT_DIR/build/linux" local out="$SCRIPT_DIR/release/linux" if $CLEAN; then info "Cleaning $bd ..."; rm -rf "$bd" fi mkdir -p "$bd" && cd "$bd" # ── Compile ────────────────────────────────────────────────────────────── info "Configuring ..." cmake "$SCRIPT_DIR" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \ -DDRAGONX_USE_SYSTEM_SDL3=ON info "Building with $JOBS jobs ..." cmake --build . -j "$JOBS" [[ -f "bin/ObsidianDragon" ]] || { err "Linux build failed"; exit 1; } info "Stripping ..." strip bin/ObsidianDragon info "Binary: $(du -h bin/ObsidianDragon | cut -f1)" # ── Bundle daemon ──────────────────────────────────────────────────────── bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build" # ── Package: release/linux/ ────────────────────────────────────────────── rm -rf "$out" mkdir -p "$out" cp bin/ObsidianDragon "$out/" [[ -f bin/hush-arrakis-chain ]] && cp bin/hush-arrakis-chain "$out/" [[ -f bin/hushd ]] && cp bin/hushd "$out/" [[ -f bin/dragonxd ]] && cp bin/dragonxd "$out/" [[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$out/" cp -r bin/res "$out/" 2>/dev/null || true # ── AppImage ───────────────────────────────────────────────────────────── info "Creating AppImage ..." local APPDIR="$bd/AppDir" rm -rf "$APPDIR" mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/lib" \ "$APPDIR/usr/share/applications" \ "$APPDIR/usr/share/icons/hicolor/256x256/apps" \ "$APPDIR/usr/share/ObsidianDragon/res" cp bin/ObsidianDragon "$APPDIR/usr/bin/" cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true # Daemon inside AppImage [[ -f bin/hush-arrakis-chain ]] && cp bin/hush-arrakis-chain "$APPDIR/usr/bin/" [[ -f bin/hushd ]] && cp bin/hushd "$APPDIR/usr/bin/" [[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/" [[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$APPDIR/usr/share/ObsidianDragon/" # Desktop entry cat > "$APPDIR/usr/share/applications/ObsidianDragon.desktop" <<'DESK' [Desktop Entry] Type=Application Name=DragonX Wallet Comment=DragonX Cryptocurrency Wallet Exec=ObsidianDragon Icon=ObsidianDragon Categories=Finance;Network; Terminal=false StartupNotify=true DESK cp "$APPDIR/usr/share/applications/ObsidianDragon.desktop" "$APPDIR/" # Icon if [[ -f "$SCRIPT_DIR/res/icons/dragonx-256.png" ]]; then cp "$SCRIPT_DIR/res/icons/dragonx-256.png" \ "$APPDIR/usr/share/icons/hicolor/256x256/apps/ObsidianDragon.png" else cat > "$APPDIR/ObsidianDragon.svg" <<'SVG' DX SVG if command -v rsvg-convert &>/dev/null; then rsvg-convert -w 256 -h 256 "$APPDIR/ObsidianDragon.svg" > \ "$APPDIR/usr/share/icons/hicolor/256x256/apps/ObsidianDragon.png" fi fi cp "$APPDIR/usr/share/icons/hicolor/256x256/apps/ObsidianDragon.png" "$APPDIR/" 2>/dev/null || \ cp "$APPDIR/ObsidianDragon.svg" "$APPDIR/ObsidianDragon.png" 2>/dev/null || true # AppRun cat > "$APPDIR/AppRun" <<'APPRUN' #!/bin/bash SELF=$(readlink -f "$0") HERE=${SELF%/*} export DRAGONX_RES_PATH="${HERE}/usr/share/ObsidianDragon/res" export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}" cd "${HERE}/usr/share/ObsidianDragon" exec "${HERE}/usr/bin/ObsidianDragon" "$@" APPRUN chmod +x "$APPDIR/AppRun" # Bundle SDL3 for lib in libSDL3.so; do local lp lp=$(ldconfig -p 2>/dev/null | grep "$lib" | head -1 | awk '{print $NF}') [[ -n "$lp" && -f "$lp" ]] && cp "$lp" "$APPDIR/usr/lib/" 2>/dev/null || true done [[ -f "$bd/_deps/sdl3-build/libSDL3.so" ]] && cp "$bd/_deps/sdl3-build/libSDL3.so"* "$APPDIR/usr/lib/" 2>/dev/null || true # appimagetool local APPIMAGETOOL="" if command -v appimagetool &>/dev/null; then APPIMAGETOOL="appimagetool" elif [[ -f "$bd/appimagetool-x86_64.AppImage" ]]; then APPIMAGETOOL="$bd/appimagetool-x86_64.AppImage" else info "Downloading appimagetool ..." wget -q -O "$bd/appimagetool-x86_64.AppImage" \ "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" chmod +x "$bd/appimagetool-x86_64.AppImage" APPIMAGETOOL="$bd/appimagetool-x86_64.AppImage" fi local ARCH ARCH=$(uname -m) local IMG_NAME="DragonX_Wallet-${VERSION}-${ARCH}.AppImage" cd "$bd" ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "$IMG_NAME" 2>/dev/null && { cp "$IMG_NAME" "$out/" info "AppImage: $out/$IMG_NAME ($(du -h "$IMG_NAME" | cut -f1))" } || warn "AppImage creation failed (appimagetool issue) — raw binary still in release/linux/" # Clean up: keep only AppImage + raw binary in release/linux/ if ls "$out"/*.AppImage 1>/dev/null 2>&1; then # AppImage succeeded — remove everything except AppImage and the binary find "$out" -maxdepth 1 -type f ! -name '*.AppImage' ! -name 'ObsidianDragon' -delete rm -rf "$out/res" 2>/dev/null fi info "Linux release artifacts: $out/" ls -lh "$out/" } # ═══════════════════════════════════════════════════════════════════════════════ # RELEASE: WINDOWS — mingw-w64 cross-compile + INCBIN embedding # ═══════════════════════════════════════════════════════════════════════════════ build_release_win() { header "Release: Windows x86_64 (cross-compile)" local bd="$SCRIPT_DIR/build/windows" local out="$SCRIPT_DIR/release/windows" if $CLEAN; then info "Cleaning $bd ..."; rm -rf "$bd" fi mkdir -p "$bd" && cd "$bd" # ── Find MinGW ─────────────────────────────────────────────────────────── local MINGW_GCC="" MINGW_GXX="" if command -v x86_64-w64-mingw32-gcc-posix &>/dev/null; then MINGW_GCC="x86_64-w64-mingw32-gcc-posix" MINGW_GXX="x86_64-w64-mingw32-g++-posix" info "Using POSIX thread model" elif command -v x86_64-w64-mingw32-gcc &>/dev/null; then MINGW_GCC="x86_64-w64-mingw32-gcc" MINGW_GXX="x86_64-w64-mingw32-g++" if x86_64-w64-mingw32-gcc -v 2>&1 | grep -q "posix"; then info "Using POSIX thread model" else warn "Using win32 thread model — may have threading issues" warn "Switch: sudo update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix" fi else err "mingw-w64 not found! Install: sudo apt install mingw-w64" exit 1 fi # ── Toolchain file ─────────────────────────────────────────────────────── cat > "$bd/mingw-toolchain.cmake" < "$GEN/embedded_data.h" < #include #include "incbin.h" INCBIN(sapling_spend_params, "$RES/sapling-spend.params"); INCBIN(sapling_output_params, "$RES/sapling-output.params"); HDR if [[ -n "$ASMAP_DAT" ]]; then cp -f "$ASMAP_DAT" "$RES/asmap.dat" echo "INCBIN(asmap_dat, \"$RES/asmap.dat\");" >> "$GEN/embedded_data.h" else echo 'extern "C" { static const uint8_t* g_asmap_dat_data = nullptr; }' >> "$GEN/embedded_data.h" echo 'static const unsigned int g_asmap_dat_size = 0;' >> "$GEN/embedded_data.h" fi # ── Daemon binaries ────────────────────────────────────────────── local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win" if [[ -d "$DD" && -f "$DD/hushd.exe" ]]; then info "Embedding daemon binaries ..." echo -e "\n#define HAS_EMBEDDED_DAEMON 1\n" >> "$GEN/embedded_data.h" for f in hushd.exe hush-cli.exe hush-tx.exe dragonxd.bat dragonx-cli.bat; do local sym=$(echo "$f" | sed 's/[^a-zA-Z0-9]/_/g') if [[ -f "$DD/$f" ]]; then cp -f "$DD/$f" "$RES/$f" info " Staged $f ($(du -h "$DD/$f" | cut -f1))" echo "INCBIN(${sym}, \"$RES/$f\");" >> "$GEN/embedded_data.h" else echo "extern \"C\" { static const uint8_t* g_${sym}_data = nullptr; }" >> "$GEN/embedded_data.h" echo "static const unsigned int g_${sym}_size = 0;" >> "$GEN/embedded_data.h" fi done else warn "prebuilt-binaries/dragonxd-win/ not found — wallet-only build" fi # ── xmrig binary (from prebuilt-binaries/xmrig-hac/) ──────────────── local XMRIG_DIR="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac" if [[ -f "$XMRIG_DIR/xmrig.exe" ]]; then cp -f "$XMRIG_DIR/xmrig.exe" "$RES/xmrig.exe" info " Staged xmrig.exe ($(du -h "$XMRIG_DIR/xmrig.exe" | cut -f1))" echo -e "\n#define HAS_EMBEDDED_XMRIG 1" >> "$GEN/embedded_data.h" echo "INCBIN(xmrig_exe, \"$RES/xmrig.exe\");" >> "$GEN/embedded_data.h" else echo 'extern "C" { static const uint8_t* g_xmrig_exe_data = nullptr; }' >> "$GEN/embedded_data.h" echo 'static const unsigned int g_xmrig_exe_size = 0;' >> "$GEN/embedded_data.h" fi # ── Theme images ───────────────────────────────────────────────── echo -e "\n// ---- Embedded theme images ----" >> "$GEN/embedded_data.h" local IMAGE_TABLE="" IMAGE_COUNT=0 for dir in "$SCRIPT_DIR/res/img/backgrounds/texture" "$SCRIPT_DIR/res/img/backgrounds/gradient" "$SCRIPT_DIR/res/img/logos"; do [[ -d "$dir" ]] || continue for img in "$dir"/*.png; do [[ -f "$img" ]] || continue local bn=$(basename "$img") local sym=$(echo "$bn" | sed 's/[^a-zA-Z0-9]/_/g') cp -f "$img" "$RES/$bn" echo "INCBIN(img_${sym}, \"$RES/$bn\");" >> "$GEN/embedded_data.h" IMAGE_TABLE+=" { g_img_${sym}_data, g_img_${sym}_size, \"${bn}\" },\n" IMAGE_COUNT=$((IMAGE_COUNT + 1)) done done echo "" >> "$GEN/embedded_data.h" echo "#define HAS_EMBEDDED_IMAGES 1" >> "$GEN/embedded_data.h" echo "#define EMBEDDED_IMAGE_COUNT $IMAGE_COUNT" >> "$GEN/embedded_data.h" echo "#define HAS_EMBEDDED_GRADIENT 1" >> "$GEN/embedded_data.h" echo "#define HAS_EMBEDDED_LOGO 1" >> "$GEN/embedded_data.h" # Backward compat aliases echo "static const uint8_t* g_dark_gradient_png_data = g_img_dark_gradient_png_data;" >> "$GEN/embedded_data.h" echo "static const unsigned int g_dark_gradient_png_size = g_img_dark_gradient_png_size;" >> "$GEN/embedded_data.h" echo "static const uint8_t* g_logo_ObsidianDragon_dark_png_data = g_img_logo_ObsidianDragon_dark_png_data;" >> "$GEN/embedded_data.h" echo "static const unsigned int g_logo_ObsidianDragon_dark_png_size = g_img_logo_ObsidianDragon_dark_png_size;" >> "$GEN/embedded_data.h" echo "" >> "$GEN/embedded_data.h" echo "struct EmbeddedImageEntry { const uint8_t* data; unsigned int size; const char* filename; };" >> "$GEN/embedded_data.h" echo "static const EmbeddedImageEntry s_embedded_images[] = {" >> "$GEN/embedded_data.h" echo -e "$IMAGE_TABLE" >> "$GEN/embedded_data.h" echo " { nullptr, 0, nullptr }" >> "$GEN/embedded_data.h" echo "};" >> "$GEN/embedded_data.h" # ── Overlay themes ─────────────────────────────────────────────── echo -e "\n// ---- Bundled overlay themes ----" >> "$GEN/embedded_data.h" local THEME_TABLE="" THEME_COUNT=0 for tf in "$SCRIPT_DIR/res/themes"/*.toml; do local tbn=$(basename "$tf") [[ "$tbn" == "ui.toml" ]] && continue local tsym=$(echo "$tbn" | sed 's/[^a-zA-Z0-9]/_/g') cp -f "$tf" "$RES/$tbn" echo "INCBIN(theme_${tsym}, \"$RES/$tbn\");" >> "$GEN/embedded_data.h" THEME_TABLE+=" { g_theme_${tsym}_data, g_theme_${tsym}_size, \"${tbn}\" },\n" THEME_COUNT=$((THEME_COUNT + 1)) done echo "" >> "$GEN/embedded_data.h" echo "#define EMBEDDED_THEME_COUNT $THEME_COUNT" >> "$GEN/embedded_data.h" echo "struct EmbeddedThemeEntry { const uint8_t* data; unsigned int size; const char* filename; };" >> "$GEN/embedded_data.h" echo "static const EmbeddedThemeEntry s_embedded_themes[] = {" >> "$GEN/embedded_data.h" echo -e "$THEME_TABLE" >> "$GEN/embedded_data.h" echo " { nullptr, 0, nullptr }" >> "$GEN/embedded_data.h" echo "};" >> "$GEN/embedded_data.h" info "Embedded resources header generated" else warn "Building WITHOUT embedded resources (Sapling params not found)" fi # ── Fetch libsodium for Windows if needed ────────────────────────────── if [[ ! -f "$SCRIPT_DIR/libs/libsodium-win/lib/libsodium.a" ]]; then info "Fetching libsodium for Windows ..." "$SCRIPT_DIR/scripts/fetch-libsodium.sh" --win fi # ── CMake + build ──────────────────────────────────────────────────────── info "Configuring (cross-compile) ..." cmake "$SCRIPT_DIR" \ -DCMAKE_TOOLCHAIN_FILE="$bd/mingw-toolchain.cmake" \ -DCMAKE_BUILD_TYPE=Release \ -DDRAGONX_USE_SYSTEM_SDL3=OFF info "Building with $JOBS jobs ..." cmake --build . -j "$JOBS" [[ -f "bin/ObsidianDragon.exe" ]] || { err "Windows build failed"; exit 1; } info "Binary: $(du -h bin/ObsidianDragon.exe | cut -f1)" # ── Package: release/windows/ ──────────────────────────────────────────── rm -rf "$out" mkdir -p "$out" local DIST="DragonX-Wallet-Windows-x64" local dist_dir="$out/$DIST" mkdir -p "$dist_dir" cp bin/ObsidianDragon.exe "$dist_dir/" local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win" for f in dragonxd.bat dragonx-cli.bat hushd.exe hush-cli.exe hush-tx.exe; do [[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/" done cat > "$dist_dir/README.txt" <<'README' DragonX Wallet - Windows Edition ================================ SINGLE-FILE DISTRIBUTION ======================== This wallet is a true single-file executable with all resources embedded. Just run ObsidianDragon.exe — no additional files needed! On first run, the wallet will automatically extract: - Sapling parameters to %APPDATA%\ZcashParams\ - asmap.dat to %APPDATA%\Hush\DRAGONX\ For support: https://git.hush.is/hush/ObsidianDragon README # Copy single-file exe to release dir cp bin/ObsidianDragon.exe "$out/" if command -v zip &>/dev/null; then (cd "$out" && zip -r "$DIST.zip" "$DIST") info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))" # Clean up: keep .zip + single-file exe, remove loose directory rm -rf "$dist_dir" fi info "Windows release artifacts: $out/" ls -lh "$out/" } # ═══════════════════════════════════════════════════════════════════════════════ # RELEASE: macOS — .app bundle + DMG # # Cross-compile from Linux: # 1. Install osxcross: https://github.com/tpoechtrager/osxcross # export OSXCROSS="$HOME/osxcross" (or wherever you installed it) # 2. For DMG: sudo apt install genisoimage # 3. For .icns icons: sudo apt install icnsutils # 4. Place macOS daemon binaries in prebuilt-binaries/dragonxd-mac/ (optional) # # Native on macOS: # Works out of the box. brew install create-dmg for prettier DMGs. # ═══════════════════════════════════════════════════════════════════════════════ build_release_mac() { local IS_CROSS=false local MAC_ARCH="x86_64" # Detect cross-compilation from Linux if [[ "$(uname -s)" == "Linux" ]]; then IS_CROSS=true info "Cross-compiling for macOS from Linux" # Find osxcross if [[ -z "${OSXCROSS:-}" ]]; then # Try common locations for try_path in "$SCRIPT_DIR/external/osxcross" "$HOME/osxcross" "/opt/osxcross" "/usr/local/osxcross"; do if [[ -d "$try_path/target" ]]; then OSXCROSS="$try_path" break fi done fi if [[ -z "${OSXCROSS:-}" || ! -d "${OSXCROSS}/target" ]]; then err "osxcross not found! Set OSXCROSS=/path/to/osxcross or install it:" echo "" echo " git clone https://github.com/tpoechtrager/osxcross" echo " cd osxcross" echo " # Place MacOSX SDK (e.g. MacOSX13.0.sdk.tar.xz) in tarballs/" echo " UNATTENDED=1 ./build.sh" echo " export OSXCROSS=\$PWD" echo "" exit 1 fi info "Using osxcross at: $OSXCROSS" export PATH="$OSXCROSS/target/bin:$PATH" # Find the right compiler triple local OSXCROSS_CC="" OSXCROSS_CXX="" OSXCROSS_TRIPLE="" # Look for full-triple compilers first (e.g. x86_64-apple-darwin22.4-clang++) local found_triple found_triple=$(ls "$OSXCROSS/target/bin/"x86_64-apple-darwin*-clang++ 2>/dev/null | head -1) if [[ -n "$found_triple" ]]; then OSXCROSS_TRIPLE=$(basename "$found_triple" | sed 's/-clang++$//') OSXCROSS_CC="$OSXCROSS/target/bin/${OSXCROSS_TRIPLE}-clang" OSXCROSS_CXX="$OSXCROSS/target/bin/${OSXCROSS_TRIPLE}-clang++" MAC_ARCH="x86_64" fi # Prefer arm64 if available (Apple Silicon) found_triple=$(ls "$OSXCROSS/target/bin/"aarch64-apple-darwin*-clang++ 2>/dev/null | head -1) if [[ -n "$found_triple" ]]; then OSXCROSS_TRIPLE=$(basename "$found_triple" | sed 's/-clang++$//') OSXCROSS_CC="$OSXCROSS/target/bin/${OSXCROSS_TRIPLE}-clang" OSXCROSS_CXX="$OSXCROSS/target/bin/${OSXCROSS_TRIPLE}-clang++" MAC_ARCH="arm64" fi # Also try o64/oa64 wrapper — but resolve the underlying triple if [[ -z "$OSXCROSS_CXX" ]]; then if command -v o64-clang++ &>/dev/null; then # Resolve the x86_64 triple from binutils OSXCROSS_TRIPLE=$(ls "$OSXCROSS/target/bin/"x86_64-apple-darwin*-ar 2>/dev/null | head -1 | xargs basename | sed 's/-ar$//') OSXCROSS_CC="o64-clang" OSXCROSS_CXX="o64-clang++" MAC_ARCH="x86_64" elif command -v oa64-clang++ &>/dev/null; then OSXCROSS_TRIPLE=$(ls "$OSXCROSS/target/bin/"aarch64-apple-darwin*-ar 2>/dev/null | head -1 | xargs basename | sed 's/-ar$//') OSXCROSS_CC="oa64-clang" OSXCROSS_CXX="oa64-clang++" MAC_ARCH="arm64" fi fi if [[ -z "$OSXCROSS_CXX" ]]; then err "Could not find osxcross compilers in PATH" echo " Ensure \$OSXCROSS/target/bin contains *-apple-darwin*-clang++" exit 1 fi info "macOS cross-compiler: $OSXCROSS_CXX (arch: $MAC_ARCH)" else MAC_ARCH=$(uname -m) fi header "Release: macOS ($MAC_ARCH$(${IS_CROSS} && echo ' — cross-compile'))" local bd="$SCRIPT_DIR/build/mac" local out="$SCRIPT_DIR/release/mac" if $CLEAN; then info "Cleaning $bd ..."; rm -rf "$bd" fi mkdir -p "$bd" && cd "$bd" # ── Compile ────────────────────────────────────────────────────────────── if $IS_CROSS; then # OSXCROSS_TRIPLE already resolved during compiler detection above local OSXCROSS_SDK_PATH OSXCROSS_SDK_PATH=$(ls -d "$OSXCROSS"/target/SDK/MacOSX*.sdk 2>/dev/null | head -1) # Generate osxcross toolchain file cat > "$bd/osxcross-toolchain.cmake" </dev/null || echo "") if [[ -n "$CLANG_RESOURCE_DIR" && -f "$CLANG_RESOURCE_DIR/lib/darwin/libclang_rt.osx.a" ]]; then COMPILER_RT="$CLANG_RESOURCE_DIR/lib/darwin/libclang_rt.osx.a" elif [[ -f "$OSXCROSS/build/compiler-rt/compiler-rt/build/lib/darwin/libclang_rt.osx.a" ]]; then COMPILER_RT="$OSXCROSS/build/compiler-rt/compiler-rt/build/lib/darwin/libclang_rt.osx.a" fi # Fetch libsodium for macOS if needed if [[ ! -f "$SCRIPT_DIR/libs/libsodium-mac/lib/libsodium.a" ]]; then info "Fetching libsodium for macOS ..." "$SCRIPT_DIR/scripts/fetch-libsodium.sh" --mac fi info "Configuring (cross-compile via osxcross) ..." cmake "$SCRIPT_DIR" \ -DCMAKE_TOOLCHAIN_FILE="$bd/osxcross-toolchain.cmake" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \ -DDRAGONX_USE_SYSTEM_SDL3=OFF \ -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ ${COMPILER_RT:+-DOSXCROSS_COMPILER_RT="$COMPILER_RT"} else info "Configuring (native) ..." cmake "$SCRIPT_DIR" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \ -DDRAGONX_USE_SYSTEM_SDL3=OFF \ -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 fi info "Building with $JOBS jobs ..." cmake --build . -j "$JOBS" [[ -f "bin/ObsidianDragon" ]] || { err "macOS build failed"; exit 1; } # Strip — use osxcross strip for cross-builds if $IS_CROSS; then local STRIP_CMD="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-strip" if [[ -x "$STRIP_CMD" ]]; then info "Stripping (osxcross) ..." "$STRIP_CMD" bin/ObsidianDragon else warn "osxcross strip not found at $STRIP_CMD — skipping" fi else info "Stripping ..." strip bin/ObsidianDragon fi info "Binary: $(du -h bin/ObsidianDragon | cut -f1)" # ── Create .app bundle ─────────────────────────────────────────────────── rm -rf "$out" mkdir -p "$out" local APP="$out/ObsidianDragon.app" local CONTENTS="$APP/Contents" local MACOS="$CONTENTS/MacOS" local RESOURCES="$CONTENTS/Resources" local FRAMEWORKS="$CONTENTS/Frameworks" mkdir -p "$MACOS" "$RESOURCES/res" "$FRAMEWORKS" # Main binary cp bin/ObsidianDragon "$MACOS/" chmod +x "$MACOS/ObsidianDragon" # Resources cp -r bin/res/* "$RESOURCES/res/" 2>/dev/null || true # Daemon binaries (macOS native, from dragonxd-mac/) local daemon_dir="$SCRIPT_DIR/prebuilt-binaries/dragonxd-mac" if [[ -d "$daemon_dir" ]]; then for f in hush-arrakis-chain hushd hush-cli hush-tx dragonxd; do [[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; chmod +x "$MACOS/$f"; info " Bundled $f"; } done elif ! $IS_CROSS; then # Native macOS: try standard paths local launcher_paths=( "$SCRIPT_DIR/../hush-arrakis-chain" "$HOME/hush3/src/hush-arrakis-chain" ) for p in "${launcher_paths[@]}"; do [[ -f "$p" ]] && { cp "$p" "$MACOS/hush-arrakis-chain"; chmod +x "$MACOS/hush-arrakis-chain"; info " Bundled hush-arrakis-chain"; break; } done local hushd_paths=( "$SCRIPT_DIR/../hushd" "$HOME/hush3/src/hushd" ) for p in "${hushd_paths[@]}"; do [[ -f "$p" ]] && { cp "$p" "$MACOS/hushd"; chmod +x "$MACOS/hushd"; info " Bundled hushd"; break; } done else warn "prebuilt-binaries/dragonxd-mac/ not found — place macOS daemon binaries there for bundling" fi # asmap.dat find_asmap 2>/dev/null && cp "$ASMAP_DAT" "$RESOURCES/asmap.dat" && info " Bundled asmap.dat" # Bundle SDL3 dylib local sdl_dylib="" for candidate in \ "$bd/_deps/sdl3-build/libSDL3.dylib" \ "$bd/_deps/sdl3-build/libSDL3.0.dylib" \ "/usr/local/lib/libSDL3.dylib" \ "/opt/homebrew/lib/libSDL3.dylib"; do if [[ -f "$candidate" ]]; then sdl_dylib="$candidate" break fi done if [[ -n "$sdl_dylib" ]]; then cp "$sdl_dylib" "$FRAMEWORKS/" local sdl_name=$(basename "$sdl_dylib") # Fix the rpath so the binary finds SDL3 in Frameworks/ if $IS_CROSS; then local INSTALL_NAME_TOOL="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-install_name_tool" [[ -x "$INSTALL_NAME_TOOL" ]] && "$INSTALL_NAME_TOOL" -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/ObsidianDragon" 2>/dev/null || true else install_name_tool -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/ObsidianDragon" 2>/dev/null || true fi info " Bundled $sdl_name" fi # Launcher script (ensures working dir + dylib path) mv "$MACOS/ObsidianDragon" "$MACOS/ObsidianDragon.bin" cat > "$MACOS/ObsidianDragon" <<'LAUNCH' #!/bin/bash DIR="$(cd "$(dirname "$0")" && pwd)" export DYLD_LIBRARY_PATH="$DIR/../Frameworks:$DYLD_LIBRARY_PATH" export DRAGONX_RES_PATH="$DIR/../Resources/res" cd "$DIR/../Resources" exec "$DIR/ObsidianDragon.bin" "$@" LAUNCH chmod +x "$MACOS/ObsidianDragon" # Info.plist cat > "$CONTENTS/Info.plist" < CFBundleName DragonX Wallet CFBundleDisplayName DragonX Wallet CFBundleIdentifier is.hush.dragonx CFBundleVersion ${VERSION} CFBundleShortVersionString ${VERSION} CFBundleExecutable ObsidianDragon CFBundleIconFile ObsidianDragon CFBundlePackageType APPL CFBundleSignature DRGX LSMinimumSystemVersion 11.0 NSHighResolutionCapable NSSupportsAutomaticGraphicsSwitching LSApplicationCategoryType public.app-category.finance PLIST # ── Icon (.icns) ───────────────────────────────────────────────────────── if [[ -f "$SCRIPT_DIR/res/img/ObsidianDragon.icns" ]]; then cp "$SCRIPT_DIR/res/img/ObsidianDragon.icns" "$RESOURCES/ObsidianDragon.icns" elif [[ -f "$SCRIPT_DIR/res/icons/dragonx-256.png" ]]; then if command -v iconutil &>/dev/null && command -v sips &>/dev/null; then # Native macOS: sips + iconutil local iconset="$bd/ObsidianDragon.iconset" mkdir -p "$iconset" local src="$SCRIPT_DIR/res/icons/dragonx-256.png" for sz in 16 32 64 128 256; do sips -z $sz $sz "$src" --out "$iconset/icon_${sz}x${sz}.png" &>/dev/null || true done for sz in 16 32 128; do local r=$((sz * 2)) sips -z $r $r "$src" --out "$iconset/icon_${sz}x${sz}@2x.png" &>/dev/null || true done iconutil -c icns "$iconset" -o "$RESOURCES/ObsidianDragon.icns" 2>/dev/null || true rm -rf "$iconset" elif command -v png2icns &>/dev/null; then # Linux: png2icns from icnsutils (sudo apt install icnsutils) info "Creating .icns with png2icns ..." local src="$SCRIPT_DIR/res/icons/dragonx-256.png" # png2icns needs specific sizes; resize with ImageMagick if available if command -v convert &>/dev/null; then local icondir="$bd/icon-sizes" mkdir -p "$icondir" for sz in 16 32 48 128 256; do convert "$src" -resize ${sz}x${sz} "$icondir/icon_${sz}.png" 2>/dev/null || true done png2icns "$RESOURCES/ObsidianDragon.icns" "$icondir"/icon_*.png 2>/dev/null || true rm -rf "$icondir" else # Just use the 256px as-is png2icns "$RESOURCES/ObsidianDragon.icns" "$src" 2>/dev/null || true fi else warn "No .icns tool found (install: sudo apt install icnsutils)" fi fi info ".app bundle created: $APP" # ── Create DMG ─────────────────────────────────────────────────────────── local DMG_NAME="DragonX_Wallet-${VERSION}-macOS-${MAC_ARCH}.dmg" if command -v create-dmg &>/dev/null; then # create-dmg (works on macOS; also available on Linux via npm) info "Creating DMG with create-dmg ..." create-dmg \ --volname "DragonX Wallet" \ --volicon "$RESOURCES/ObsidianDragon.icns" \ --window-pos 200 120 \ --window-size 600 400 \ --icon-size 100 \ --icon "ObsidianDragon.app" 150 190 \ --app-drop-link 450 190 \ --no-internet-enable \ "$out/$DMG_NAME" \ "$APP" 2>/dev/null && { info "DMG: $out/$DMG_NAME ($(du -h "$out/$DMG_NAME" | cut -f1))" } || warn "create-dmg failed — .app bundle still available" elif command -v hdiutil &>/dev/null; then # Native macOS info "Creating DMG with hdiutil ..." local staging="$bd/dmg-staging" rm -rf "$staging" mkdir -p "$staging" cp -a "$APP" "$staging/" ln -s /Applications "$staging/Applications" hdiutil create -volname "DragonX Wallet" \ -srcfolder "$staging" \ -ov -format UDZO \ "$out/$DMG_NAME" 2>/dev/null && { info "DMG: $out/$DMG_NAME ($(du -h "$out/$DMG_NAME" | cut -f1))" } || warn "hdiutil failed — .app bundle still available" rm -rf "$staging" elif command -v genisoimage &>/dev/null; then # Linux fallback: genisoimage produces a hybrid ISO/DMG that macOS can open info "Creating DMG with genisoimage (Linux) ..." local staging="$bd/dmg-staging" rm -rf "$staging" mkdir -p "$staging" cp -a "$APP" "$staging/" # Can't create a real symlink to /Applications in an ISO, but the .app # is the important part — users drag it to Applications manually. genisoimage -V "DragonX Wallet" \ -D -R -apple -no-pad \ -o "$out/$DMG_NAME" \ "$staging" 2>/dev/null && { info "DMG: $out/$DMG_NAME ($(du -h "$out/$DMG_NAME" | cut -f1))" info " (ISO/HFS hybrid — mountable on macOS)" } || warn "genisoimage failed — .app bundle still available" rm -rf "$staging" else warn "No DMG tool found — .app bundle still available" echo "" echo " To create DMGs on Linux, install one of:" echo " sudo apt install genisoimage # Basic DMG (recommended)" echo " sudo apt install icnsutils # For .icns icon creation" echo "" fi info "macOS release artifacts: $out/" ls -lhR "$out/" 2>/dev/null | head -30 } # ═══════════════════════════════════════════════════════════════════════════════ # MAIN # ═══════════════════════════════════════════════════════════════════════════════ echo -e "${GREEN}DragonX Wallet v${VERSION} — Unified Build${NC}" echo "─────────────────────────────────────────" $DO_DEV && build_dev $DO_LINUX && build_release_linux $DO_WIN && build_release_win $DO_MAC && build_release_mac header "Done" if $DO_LINUX || $DO_WIN || $DO_MAC; then echo -e " Release artifacts in: ${GREEN}$SCRIPT_DIR/release/${NC}" [[ -d "$SCRIPT_DIR/release/linux" ]] && echo -e " ${CYAN}linux/${NC} — AppImage + binary" [[ -d "$SCRIPT_DIR/release/windows" ]] && echo -e " ${CYAN}windows/${NC} — .exe + .zip" [[ -d "$SCRIPT_DIR/release/mac" ]] && echo -e " ${CYAN}mac/${NC} — .app + .dmg" fi