From bd6733b2e5d76b5259020f1c30a5223a9773b6aa Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Sun, 12 Apr 2026 12:43:55 +0400 Subject: [PATCH] feat(signal): advertise build version in Offer/Answer Add caller_build_version / callee_build_version (git short hash) to DirectCallOffer and DirectCallAnswer so peers can identify each other's build in debug logs. Also log own build at register time. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/wzp-proto/src/packet.rs | 6 + desktop/src-tauri/src/lib.rs | 14 +- ...-and-notify.sh => build-and-notify.sh.old} | 0 scripts/build-tauri-android.sh | 8 +- scripts/build.sh | 361 ++++++++++++++++++ 5 files changed, 383 insertions(+), 6 deletions(-) rename scripts/{build-and-notify.sh => build-and-notify.sh.old} (100%) create mode 100755 scripts/build.sh diff --git a/crates/wzp-proto/src/packet.rs b/crates/wzp-proto/src/packet.rs index fe788cd..7e106b0 100644 --- a/crates/wzp-proto/src/packet.rs +++ b/crates/wzp-proto/src/packet.rs @@ -755,6 +755,9 @@ pub enum SignalMessage { /// the same LAN. #[serde(default, skip_serializing_if = "Vec::is_empty")] caller_local_addrs: Vec, + /// Build version (git short hash) for debugging. + #[serde(default, skip_serializing_if = "Option::is_none")] + caller_build_version: Option, }, /// Callee's response to a direct call. @@ -788,6 +791,9 @@ pub enum SignalMessage { /// `callee_reflexive_addr`. #[serde(default, skip_serializing_if = "Vec::is_empty")] callee_local_addrs: Vec, + /// Build version (git short hash) for debugging. + #[serde(default, skip_serializing_if = "Option::is_none")] + callee_build_version: Option, }, /// Relay tells both parties: media room is ready. diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 01ff499..b2a97e4 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -927,7 +927,7 @@ fn do_register_signal( let (server_cfg, _cert_der) = wzp_transport::server_config(); let endpoint = wzp_transport::create_endpoint(bind, Some(server_cfg)) .map_err(|e| format!("{e}"))?; - emit_call_debug(&app, "register_signal:endpoint_created", serde_json::json!({ "bind": bind.to_string() })); + emit_call_debug(&app, "register_signal:endpoint_created", serde_json::json!({ "bind": bind.to_string(), "build": GIT_HASH })); let conn = wzp_transport::connect(&endpoint, addr, "_signal", wzp_transport::client_config()) .await .map_err(|e| { @@ -981,13 +981,14 @@ fn do_register_signal( let mut sig = signal_state.lock().await; sig.signal_status = "ringing".into(); let _ = app_clone.emit("signal-event", serde_json::json!({"type":"ringing","call_id":call_id})); } - Ok(Some(SignalMessage::DirectCallOffer { caller_fingerprint, caller_alias, call_id, caller_reflexive_addr, .. })) => { - tracing::info!(%call_id, caller = %caller_fingerprint, "signal: DirectCallOffer"); + Ok(Some(SignalMessage::DirectCallOffer { caller_fingerprint, caller_alias, call_id, caller_reflexive_addr, caller_build_version, .. })) => { + tracing::info!(%call_id, caller = %caller_fingerprint, peer_build = ?caller_build_version, "signal: DirectCallOffer"); emit_call_debug(&app_clone, "recv:DirectCallOffer", serde_json::json!({ "call_id": call_id, "caller_fp": caller_fingerprint, "caller_alias": caller_alias, "caller_reflexive_addr": caller_reflexive_addr, + "peer_build": caller_build_version, })); let mut sig = signal_state.lock().await; sig.signal_status = "incoming".into(); sig.incoming_call_id = Some(call_id.clone()); sig.incoming_caller_fp = Some(caller_fingerprint.clone()); sig.incoming_caller_alias = caller_alias.clone(); @@ -1004,12 +1005,13 @@ fn do_register_signal( let _ = app_clone.emit("signal-event", serde_json::json!({"type":"incoming","call_id":call_id,"caller_fp":caller_fingerprint,"caller_alias":caller_alias})); let _ = app_clone.emit("history-changed", ()); } - Ok(Some(SignalMessage::DirectCallAnswer { call_id, accept_mode, callee_reflexive_addr, .. })) => { - tracing::info!(%call_id, ?accept_mode, "signal: DirectCallAnswer (forwarded by relay)"); + Ok(Some(SignalMessage::DirectCallAnswer { call_id, accept_mode, callee_reflexive_addr, callee_build_version, .. })) => { + tracing::info!(%call_id, ?accept_mode, peer_build = ?callee_build_version, "signal: DirectCallAnswer (forwarded by relay)"); emit_call_debug(&app_clone, "recv:DirectCallAnswer", serde_json::json!({ "call_id": call_id, "accept_mode": format!("{:?}", accept_mode), "callee_reflexive_addr": callee_reflexive_addr, + "peer_build": callee_build_version, })); } Ok(Some(SignalMessage::CallSetup { call_id, room, relay_addr, peer_direct_addr, peer_local_addrs })) => { @@ -1378,6 +1380,7 @@ async fn place_call( supported_profiles: vec![wzp_proto::QualityProfile::GOOD], caller_reflexive_addr: own_reflex.clone(), caller_local_addrs: caller_local_addrs.clone(), + caller_build_version: Some(GIT_HASH.to_string()), }) .await .map_err(|e| { @@ -1492,6 +1495,7 @@ async fn answer_call( chosen_profile: Some(wzp_proto::QualityProfile::GOOD), callee_reflexive_addr: own_reflex.clone(), callee_local_addrs: callee_local_addrs.clone(), + callee_build_version: Some(GIT_HASH.to_string()), }) .await .map_err(|e| { diff --git a/scripts/build-and-notify.sh b/scripts/build-and-notify.sh.old similarity index 100% rename from scripts/build-and-notify.sh rename to scripts/build-and-notify.sh.old diff --git a/scripts/build-tauri-android.sh b/scripts/build-tauri-android.sh index b633e23..368b1b8 100755 --- a/scripts/build-tauri-android.sh +++ b/scripts/build-tauri-android.sh @@ -29,7 +29,7 @@ REMOTE_HOST="SepehrHomeserverdk" BASE_DIR="/mnt/storage/manBuilder" NTFY_TOPIC="https://ntfy.sh/wzp" LOCAL_OUTPUT="target/tauri-android-apk" -BRANCH="${WZP_BRANCH:-feat/desktop-audio-rewrite}" +BRANCH="${WZP_BRANCH:-$(git -C "$(dirname "$0")/.." branch --show-current 2>/dev/null || echo "")}" SSH_OPTS="-o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4 -o LogLevel=ERROR" REBUILD_RUST=0 @@ -50,6 +50,12 @@ for arg in "$@"; do esac done +if [ -z "$BRANCH" ]; then + echo "ERROR: could not determine target branch (detached HEAD?). Pass WZP_BRANCH=name." + exit 1 +fi +echo "Target branch: $BRANCH" + log() { echo -e "\033[1;36m>>> $*\033[0m"; } ssh_cmd() { ssh -A $SSH_OPTS "$REMOTE_HOST" "$@"; } diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..e21e701 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,361 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================= +# WZ Phone — unified build script +# +# Builds Tauri Android APK and/or Linux x86_64 binaries via Docker on a +# remote build server. Uploads artifacts, notifies via ntfy.sh/wzp. +# +# Two servers: +# PRIMARY (default) SepehrHomeserverdk paste.dk.manko.yoga origin (gitea) +# ALT (--alt) manwe@172.16.81.175 paste.tbs.amn.gg fj (forgejo) +# +# Usage: +# ./scripts/build.sh Android APK (current branch, primary) +# ./scripts/build.sh --alt Android APK on alt server +# ./scripts/build.sh --linux Linux binaries only +# ./scripts/build.sh --all Android + Linux +# ./scripts/build.sh --branch NAME Override branch +# ./scripts/build.sh --rust Force Rust rebuild +# ./scripts/build.sh --no-pull Skip git pull +# ./scripts/build.sh --init First-time setup (clone + Docker image) +# ./scripts/build.sh --install Download APK + adb install locally +# ./scripts/build.sh --release Release APK (not debug) +# ============================================================================= + +NTFY_TOPIC="https://ntfy.sh/wzp" +LOCAL_OUTPUT="target/tauri-android-apk" +SSH_BASE_OPTS="-o ConnectTimeout=15 -o ServerAliveInterval=15 -o ServerAliveCountMax=4 -o LogLevel=ERROR" + +# ── Server profiles ───────────────────────────────────────────────────────── +USE_ALT=0 +REBUILD_RUST=0 +DO_PULL=1 +DO_INSTALL=0 +DO_INIT=0 +BUILD_ANDROID=1 +BUILD_LINUX=0 +BUILD_RELEASE=0 +BRANCH=$(git -C "$(dirname "$0")/.." branch --show-current 2>/dev/null || echo "") + +while [ $# -gt 0 ]; do + case "$1" in + --alt) USE_ALT=1 ;; + --rust) REBUILD_RUST=1 ;; + --pull) DO_PULL=1 ;; + --no-pull) DO_PULL=0 ;; + --install) DO_INSTALL=1 ;; + --init) DO_INIT=1 ;; + --android) BUILD_ANDROID=1; BUILD_LINUX=0 ;; + --linux) BUILD_ANDROID=0; BUILD_LINUX=1 ;; + --all) BUILD_ANDROID=1; BUILD_LINUX=1 ;; + --release) BUILD_RELEASE=1 ;; + --branch) shift; BRANCH="$1" ;; + --branch=*) BRANCH="${1#--branch=}" ;; + -h|--help) sed -n '3,22p' "$0"; exit 0 ;; + *) echo "Unknown arg: $1"; exit 1 ;; + esac + shift +done + +if [ -z "$BRANCH" ]; then + echo "ERROR: could not determine target branch (detached HEAD?). Pass --branch NAME." + exit 1 +fi + +# ── Select server profile ─────────────────────────────────────────────────── +if [ "$USE_ALT" = "1" ]; then + SERVER_TAG="ALT" + REMOTE_HOST="manwe@172.16.81.175" + BASE_DIR="/home/manwe/wzp-builder" + SSH_OPTS="$SSH_BASE_OPTS" + GIT_ORIGIN="ssh://git@git.tbs.amn.gg:2222/manawenuz/wzp.git" + # Alt server uploads directly (no .env file) + UPLOAD_MODE="direct" + PASTE_URL="http://paste.tbs.amn.gg" + PASTE_AUTH="X2j6szIQaoJGaxZjLkpl3A8IX9/mTkDgdhhgyYFcpaU=" +else + SERVER_TAG="PRI" + REMOTE_HOST="SepehrHomeserverdk" + BASE_DIR="/mnt/storage/manBuilder" + SSH_OPTS="-A $SSH_BASE_OPTS" + GIT_ORIGIN="" # uses existing origin on the remote + # Primary server uses .env file for rustypaste credentials + UPLOAD_MODE="envfile" + PASTE_URL="" + PASTE_AUTH="" +fi + +TARGETS="" +[ "$BUILD_ANDROID" = 1 ] && TARGETS="Android" +[ "$BUILD_LINUX" = 1 ] && TARGETS="${TARGETS:+$TARGETS + }Linux" +echo "[$SERVER_TAG] branch: $BRANCH | targets: $TARGETS" + +log() { echo -e "\033[1;36m>>> $*\033[0m"; } +ssh_cmd() { ssh $SSH_OPTS "$REMOTE_HOST" "$@"; } + +# ── First-time setup (--init) ─────────────────────────────────────────────── +if [ "$DO_INIT" = "1" ]; then + log "[$SERVER_TAG] First-time setup..." + ssh_cmd "mkdir -p $BASE_DIR/data/{source,cache/target,cache/cargo-registry,cache/cargo-git,cache/gradle,cache/android-home,cache-linux/target,cache-linux/cargo-registry,cache-linux/cargo-git}" + + if [ -n "$GIT_ORIGIN" ]; then + log "Cloning from $GIT_ORIGIN..." + ssh_cmd "if [ ! -d $BASE_DIR/data/source/.git ]; then git clone $GIT_ORIGIN $BASE_DIR/data/source; else echo 'Repo already cloned'; fi" + fi + + log "Uploading Dockerfile..." + cat scripts/Dockerfile.android-builder | ssh_cmd "cat > /tmp/Dockerfile.android-builder" + log "Building Docker image (10-20 min on first run)..." + ssh_cmd "cd /tmp && docker build -t wzp-android-builder -f Dockerfile.android-builder . 2>&1 | tail -20" + + log "[$SERVER_TAG] Init done! Run without --init to build." + exit 0 +fi + +# ── Upload remote build script ────────────────────────────────────────────── +log "[$SERVER_TAG] Uploading build script..." +ssh_cmd "cat > /tmp/wzp-build.sh" < /dev/null 2>&1 || true; } + +# Upload a file; print URL on stdout. +upload_file() { + local file="\$1" + if [ "\$UPLOAD_MODE" = "direct" ]; then + curl -s -F "file=@\$file" -H "Authorization: \$PASTE_AUTH" "\$PASTE_URL" || echo "" + else + local env_file="\$BASE_DIR/.env" + [ ! -f "\$env_file" ] && { echo ""; return; } + source "\$env_file" + if [ -n "\${rusty_address:-}" ] && [ -n "\${rusty_auth_token:-}" ]; then + curl -s -F "file=@\$file" -H "Authorization: \$rusty_auth_token" "\$rusty_address" || echo "" + else + echo "" + fi + fi +} + +trap 'notify "WZP [\$SERVER_TAG] build FAILED [\$BRANCH]! Check /tmp/wzp-build.log"' ERR + +# ── Pull source ───────────────────────────────────────────────────────── +if [ "\$DO_PULL" = "1" ]; then + echo ">>> Pulling branch '\$BRANCH' from origin..." + cd "\$BASE_DIR/data/source" + git reset --hard HEAD 2>/dev/null || true + # NOTE: do NOT git clean -fd — it wipes tauri-generated scaffold + git fetch origin "\$BRANCH" 2>&1 | tail -3 + git checkout "\$BRANCH" 2>/dev/null || git checkout -b "\$BRANCH" "origin/\$BRANCH" + git reset --hard "origin/\$BRANCH" + git submodule update --init || true + echo ">>> HEAD: \$(git rev-parse --short HEAD) — \$(git log -1 --format=%s)" +fi + +GIT_HASH=\$(cd "\$BASE_DIR/data/source" && git rev-parse --short HEAD 2>/dev/null || echo unknown) +GIT_MSG=\$(cd "\$BASE_DIR/data/source" && git log -1 --pretty=%s 2>/dev/null | head -c 60 || echo "?") + +# ── Clean Rust if requested ───────────────────────────────────────────── +if [ "\$REBUILD_RUST" = "1" ]; then + echo ">>> Cleaning Rust targets..." + rm -rf "\$BASE_DIR/data/cache/target/aarch64-linux-android" \ + "\$BASE_DIR/data/cache/target/armv7-linux-androideabi" \ + "\$BASE_DIR/data/cache/target/i686-linux-android" \ + "\$BASE_DIR/data/cache/target/x86_64-linux-android" + rm -rf "\$BASE_DIR/data/cache-linux/target/release" +fi + +# ── Fix perms ─────────────────────────────────────────────────────────── +find "\$BASE_DIR/data/source" "\$BASE_DIR/data/cache" \ + ! -user 1000 -o ! -group 1000 2>/dev/null | \ + xargs -r chown 1000:1000 2>/dev/null || true +if [ -d "\$BASE_DIR/data/cache-linux" ]; then + find "\$BASE_DIR/data/cache-linux" \ + ! -user 1000 -o ! -group 1000 2>/dev/null | \ + xargs -r chown 1000:1000 2>/dev/null || true +fi + +# ── Tauri Android APK ────────────────────────────────────────────────── +if [ "\$BUILD_ANDROID" = "1" ]; then + notify "WZP [\$SERVER_TAG] Tauri Android build STARTED [\$BRANCH @ \$GIT_HASH] — \$GIT_MSG" + echo ">>> Building Tauri Android APK..." + + PROFILE_FLAG="--debug" + [ "\$BUILD_RELEASE" = "1" ] && PROFILE_FLAG="" + + mkdir -p "\$BASE_DIR/data/cache/android-home" + chown 1000:1000 "\$BASE_DIR/data/cache/android-home" 2>/dev/null || true + + docker run --rm --user 1000:1000 \ + -e PROFILE_FLAG="\$PROFILE_FLAG" \ + -v "\$BASE_DIR/data/source:/build/source" \ + -v "\$BASE_DIR/data/cache/cargo-registry:/home/builder/.cargo/registry" \ + -v "\$BASE_DIR/data/cache/cargo-git:/home/builder/.cargo/git" \ + -v "\$BASE_DIR/data/cache/target:/build/source/target" \ + -v "\$BASE_DIR/data/cache/gradle:/home/builder/.gradle" \ + -v "\$BASE_DIR/data/cache/android-home:/home/builder/.android" \ + wzp-android-builder bash -c ' +set -euo pipefail +cd /build/source/desktop + +echo ">>> npm install" +npm install --silent 2>&1 | tail -5 || npm install 2>&1 | tail -20 + +cd src-tauri + +if [ ! -x gen/android/gradlew ]; then + echo ">>> cargo tauri android init" + cargo tauri android init 2>&1 | tail -20 +fi + +echo ">>> cargo ndk build -p wzp-native --release" +JNI_ABI_DIR=gen/android/app/src/main/jniLibs/arm64-v8a +mkdir -p "\$JNI_ABI_DIR" +( + cd /build/source + cargo ndk -t arm64-v8a -o desktop/src-tauri/gen/android/app/src/main/jniLibs \ + build --release -p wzp-native 2>&1 | tail -10 +) +[ -f "\$JNI_ABI_DIR/libwzp_native.so" ] && ls -lh "\$JNI_ABI_DIR/libwzp_native.so" + +if [ ! -f "\$JNI_ABI_DIR/libc++_shared.so" ]; then + echo ">>> libc++_shared.so missing, copying from NDK..." + NDK_LIBCXX=\$(find "\$ANDROID_NDK_HOME" -name "libc++_shared.so" -path "*/aarch64-linux-android/*" | head -1) + if [ -n "\$NDK_LIBCXX" ]; then + cp "\$NDK_LIBCXX" "\$JNI_ABI_DIR/" + else + echo "ERROR: libc++_shared.so not found in NDK"; exit 1 + fi +fi + +echo ">>> cargo tauri android build \${PROFILE_FLAG} --target aarch64 --apk" +cargo tauri android build \${PROFILE_FLAG} --target aarch64 --apk + +echo ">>> Build artifacts:" +find gen/android -name "*.apk" -exec ls -lh {} \; 2>/dev/null +echo "APK_BUILT" +' + + echo ">>> Uploading APK..." + APK=\$(find "\$BASE_DIR/data/source/desktop/src-tauri/gen/android" -name "*.apk" -type f 2>/dev/null | head -1) + if [ -n "\$APK" ]; then + APK_SIZE=\$(du -h "\$APK" | cut -f1) + URL=\$(upload_file "\$APK") + echo "APK_URL=\$URL" + notify "WZP [\$SERVER_TAG] Tauri Android OK [\$BRANCH @ \$GIT_HASH] (\$APK_SIZE) +\$URL" + echo ">>> APK: \$URL (\$APK_SIZE)" + else + notify "WZP [\$SERVER_TAG] Tauri Android FAILED [\$BRANCH @ \$GIT_HASH] - no APK" + echo "ERROR: No APK found"; exit 1 + fi +fi + +# ── Linux x86_64 binaries ─────────────────────────────────────────────── +if [ "\$BUILD_LINUX" = "1" ]; then + mkdir -p "\$BASE_DIR/data/cache-linux/target" \ + "\$BASE_DIR/data/cache-linux/cargo-registry" \ + "\$BASE_DIR/data/cache-linux/cargo-git" + + notify "WZP [\$SERVER_TAG] Linux x86_64 build STARTED [\$BRANCH @ \$GIT_HASH]..." + echo ">>> Building Linux binaries..." + + docker run --rm --user 1000:1000 \ + -v "\$BASE_DIR/data/source:/build/source" \ + -v "\$BASE_DIR/data/cache-linux/cargo-registry:/home/builder/.cargo/registry" \ + -v "\$BASE_DIR/data/cache-linux/cargo-git:/home/builder/.cargo/git" \ + -v "\$BASE_DIR/data/cache-linux/target:/build/source/target" \ + wzp-android-builder bash -c ' +set -euo pipefail +cd /build/source + +echo ">>> Building relay + client + web + bench..." +cargo build --release --bin wzp-relay --bin wzp-client --bin wzp-web --bin wzp-bench 2>&1 | tail -5 + +echo ">>> Building audio client..." +cargo build --release --bin wzp-client --features audio 2>&1 | tail -3 +cp target/release/wzp-client target/release/wzp-client-audio +cargo build --release --bin wzp-client 2>&1 | tail -3 + +echo ">>> Binaries:" +ls -lh target/release/wzp-relay target/release/wzp-client target/release/wzp-client-audio target/release/wzp-web target/release/wzp-bench + +echo ">>> Packaging..." +tar czf /tmp/wzp-linux-x86_64.tar.gz \ + -C target/release wzp-relay wzp-client wzp-client-audio wzp-web wzp-bench +echo "BINARIES_BUILT" +' + + echo ">>> Uploading Linux binaries..." + docker run --rm \ + -v "\$BASE_DIR/data/cache-linux/target:/build/target" \ + wzp-android-builder bash -c \ + "cp /build/target/release/wzp-relay /build/target/release/wzp-client /build/target/release/wzp-client-audio /build/target/release/wzp-web /build/target/release/wzp-bench /tmp/ && tar czf /tmp/wzp-linux-x86_64.tar.gz -C /tmp wzp-relay wzp-client wzp-client-audio wzp-web wzp-bench && cat /tmp/wzp-linux-x86_64.tar.gz" \ + > /tmp/wzp-linux-x86_64.tar.gz + + URL=\$(upload_file /tmp/wzp-linux-x86_64.tar.gz) + if [ -n "\$URL" ]; then + echo "LINUX_URL=\$URL" + notify "WZP [\$SERVER_TAG] Linux x86_64 OK [\$BRANCH @ \$GIT_HASH] +\$URL" + echo ">>> Linux binaries: \$URL" + else + notify "WZP [\$SERVER_TAG] Linux build FAILED - upload error" + echo "ERROR: Linux upload failed"; exit 1 + fi +fi + +echo ">>> All builds complete!" +REMOTE_SCRIPT + +ssh_cmd "chmod +x /tmp/wzp-build.sh" + +# Run in tmux +log "[$SERVER_TAG] Starting build in tmux (branch: $BRANCH)..." +ssh_cmd "tmux kill-session -t wzp-build 2>/dev/null; true" +ssh_cmd "tmux new-session -d -s wzp-build '/tmp/wzp-build.sh 2>&1 | tee /tmp/wzp-build.log'" + +log "[$SERVER_TAG] Build running! Notification on ntfy.sh/wzp when done." +echo "" +echo " Monitor: ssh $REMOTE_HOST 'tail -f /tmp/wzp-build.log'" +echo " Status: ssh $REMOTE_HOST 'tail -5 /tmp/wzp-build.log'" +echo "" + +# Optionally wait and install locally +if [ "$DO_INSTALL" = "1" ]; then + log "Waiting for build..." + while true; do + sleep 15 + if ssh_cmd "grep -q 'APK_URL\|LINUX_URL\|ERROR\|All builds complete' /tmp/wzp-build.log 2>/dev/null"; then + break + fi + done + + URL=$(ssh_cmd "grep APK_URL /tmp/wzp-build.log | tail -1 | cut -d= -f2") + if [ -n "$URL" ]; then + log "Downloading APK..." + mkdir -p "$LOCAL_OUTPUT" + curl -s -o "$LOCAL_OUTPUT/wzp-tauri.apk" "$URL" + log "Installing..." + adb uninstall com.wzp.phone 2>/dev/null || true + adb install "$LOCAL_OUTPUT/wzp-tauri.apk" + log "Done!" + else + log "No APK URL found in log" + fi +fi