#!/usr/bin/env bash
# Reproducible Windows release build for pvtcoms (the "free way": unsigned + published hashes).
#
# Trust model: we do NOT pay a CA to sign the binary. Instead the build is made byte-for-byte
# reproducible, so anyone can rebuild from the same source + toolchain and confirm the published
# SHA-256 matches. Trust comes from verifiability, not from a certificate authority.
#
# This script is secret-free and committed. The PUBLISHED binary is CONFIG-FREE — nothing secret is
# baked in — so ANYONE can rebuild from this recipe + toolchain and confirm the published SHA-256.
# Members reach the gated relay via a `pvtcoms.conf` SIDECAR (relay onion + access key) dropped next to
# the exe; the relay config never touches the binary, the repo, or git.
set -euo pipefail

REPO="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO"
SECRETS="${PVTCOMS_SECRETS:-$HOME/.ssh/pvtcoms-relay.secrets}"
DIST="${DIST:-$REPO/dist/windows}"
TARGET=x86_64-pc-windows-gnu

# The PUBLISHED binary is CONFIG-FREE: we bake NOTHING, so its hash is reproducible by anyone from this
# recipe alone. If a secrets file is present we additionally EMIT a `pvtcoms.conf` sidecar (relay onion +
# access key) next to the exe, for distribution to invited members — the sidecar carries the config the
# binary no longer does. The exe is byte-identical whether or not secrets are present.
if [ -f "$SECRETS" ]; then
  SIDECAR_RELAY="$(grep '^RELAY_ONION=' "$SECRETS" | cut -d= -f2)"
  SIDECAR_KEY="$(grep '^RELAY_ACCESS_KEY=' "$SECRETS" | cut -d= -f2)"
  # The bridge value is shell-quoted in the secrets file (it contains spaces); strip surrounding quotes
  # so the emitted sidecar carries a clean `bridge=obfs4 …` line (a leading `"` breaks PT detection).
  unquote() { local v="$1"; v="${v%\"}"; v="${v#\"}"; v="${v%\'}"; v="${v#\'}"; printf '%s' "$v"; }
  SIDECAR_BRIDGE="$(unquote "$(grep '^RELAY_BRIDGE=' "$SECRETS" | cut -d= -f2-)")"  # bridge line (obfs4/vanilla)
  SIDECAR_RELAYC="$(unquote "$(grep '^RELAY_CLEARNET=' "$SECRETS" | cut -d= -f2-)")" # clearnet-over-Tor IP:port
  EMIT_SIDECAR=1
else
  echo "NOTE: no secrets file at $SECRETS — building the config-free exe without emitting a sidecar."
  echo "      The exe is identical to the published one; members add their own pvtcoms.conf to connect."
  EMIT_SIDECAR=0
fi

# --- reproducibility knobs ---
# Fixed epoch (project genesis) so any timestamp baked by tooling is deterministic.
export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-1735689600}"  # 2025-01-01T00:00:00Z
export CARGO_INCREMENTAL=0
export TZ=UTC LC_ALL=C
# Remap absolute paths out of the binary, and tell the mingw linker NOT to stamp the PE header
# with the build time (the single biggest source of non-determinism in Windows exes).
export RUSTFLAGS="--remap-path-prefix=$REPO=/pvtcoms --remap-path-prefix=$HOME/.cargo=/cargo --remap-path-prefix=$HOME=/home -C link-arg=-Wl,--no-insert-timestamp"

# --- mingw cross toolchain ---
export CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc
export CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++
export AR_x86_64_pc_windows_gnu=x86_64-w64-mingw32-ar
export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc

# Harden the C compiled by the `cc` crate (vendored OpenSSL + bundled SQLCipher + ring) — Rust code is
# hardened by default, but the bundled native libs inherit only the compiler defaults. Stack-protector
# + FORTIFY catch buffer overflows in that C. (mingw-w64 supports both.)
HARDEN_CFLAGS="-O2 -fstack-protector-strong -D_FORTIFY_SOURCE=2"
export CFLAGS_x86_64_pc_windows_gnu="$HARDEN_CFLAGS"
export CXXFLAGS_x86_64_pc_windows_gnu="$HARDEN_CFLAGS"

# The obfs4 pluggable-transport client (lyrebird) ships as a SEPARATE file next to the exe — NOT embedded.
# We previously baked it into the exe (self-extracting), but "an exe that drops + runs another exe" trips
# antivirus ML heuristics (Wacatac!ml false positives). Shipping the official Tor lyrebird.exe alongside
# (it's widely whitelisted — it's in Tor Browser) keeps both binaries clean. `unset` so build.rs does NOT
# embed. The app finds lyrebird.exe next to itself (see tor.rs `pt_binary_path`).
unset PVTCOMS_LYREBIRD_BIN
LYREBIRD_SRC="${PVTCOMS_LYREBIRD_SRC:-$HOME/.cache/pvtcoms-pt/lyrebird.exe}"

echo "building config-free (reproducible by anyone; relay config lives in the pvtcoms.conf sidecar)"
cargo build --release --locked -p pvtcoms-demo --features tor --target "$TARGET" "$@"

mkdir -p "$DIST"
cp "target/$TARGET/release/pvtcoms-demo.exe" "$DIST/pvtcoms.exe"

# Ship lyrebird.exe alongside (the obfs4 helper). Without it, obfs4 bridges won't work.
if [ -f "$LYREBIRD_SRC" ]; then
  cp "$LYREBIRD_SRC" "$DIST/lyrebird.exe"
  echo "✓ bundled obfs4 client: $DIST/lyrebird.exe ($(sha256sum "$LYREBIRD_SRC" | cut -c1-12)…)"
else
  echo "NOTE: no lyrebird at $LYREBIRD_SRC — packaging WITHOUT the obfs4 helper (vanilla/onion only)."
fi

# Emit the member sidecar (config the exe no longer bakes). NOT for public distribution — it carries the
# gated relay's access bearer secret; ship it only to invited members alongside the exe. Gitignored.
if [ "$EMIT_SIDECAR" = 1 ]; then
  {
    echo "# pvtcoms relay config — keep next to pvtcoms.exe. Do NOT share publicly (carries the access key)."
    echo "relay=$SIDECAR_RELAY"
    echo "key=$SIDECAR_KEY"
    [ -n "$SIDECAR_BRIDGE" ] && echo "bridge=$SIDECAR_BRIDGE"
    # Preferred clearnet-over-Tor endpoint (3-hop exit circuit; reliable through a single bridge where
    # an onion's 6-hop rendezvous can't complete). The onion `relay=` above stays as the fallback.
    [ -n "$SIDECAR_RELAYC" ] && echo "relayc=$SIDECAR_RELAYC"
  } > "$DIST/pvtcoms.conf"
  chmod 600 "$DIST/pvtcoms.conf"
  echo "✓ emitted $DIST/pvtcoms.conf (member sidecar — relay ${SIDECAR_RELAY:0:12}…, key ${SIDECAR_KEY:0:6}…)"
fi

# Provenance: how to reproduce + verify this binary.
{
  echo "pvtcoms Windows release — reproducible build provenance"
  echo
  echo "The published binary is CONFIG-FREE and byte-for-byte DETERMINISTIC: nothing secret is baked in,"
  echo "so the same source + toolchain always produce the same exe (timestamps stripped, paths remapped)."
  echo "Trust comes from verifiability, not a CA signature."
  echo
  echo "toolchain identity (not wall-clock):"
  echo "  rustc:  $(rustc --version)"
  echo "  cargo:  $(cargo --version)"
  echo "  mingw:  $(x86_64-w64-mingw32-gcc --version | head -1)"
  echo "  target: $TARGET"
  echo "  SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH"
  echo "  config: config-free (relay config supplied at runtime via pvtcoms.conf / env)"
  if [ -f "$DIST/lyrebird.exe" ]; then
    echo "  bundled obfs4 client (lyrebird.exe) sha256: $(sha256sum "$DIST/lyrebird.exe" | cut -d' ' -f1)"
    echo "    └ official Tor Project lyrebird (tor-expert-bundle, windows-x86_64) — shipped as a SEPARATE"
    echo "      file next to pvtcoms.exe (NOT embedded, to avoid antivirus ML false positives). The exe"
    echo "      launches it only when an obfs4 bridge is configured. It is the same binary inside Tor Browser."
  fi
  echo
  echo "VERIFY (anyone, no secrets needed): run scripts/build-windows-release.sh at the matching commit +"
  echo "toolchain. The resulting pvtcoms.exe must match the SHA-256 below — there is ONE published hash and"
  echo "everyone can reproduce it, because no per-deployment config is baked in. This proves the published"
  echo "binary is exactly this source with no hidden backdoor."
  echo
  echo "CONNECT: the exe reads its relay config, in priority order, from (1) PVTCOMS_RELAY / PVTCOMS_RELAY_KEY"
  echo "env vars, then (2) a 'pvtcoms.conf' file next to the exe (relay=<onion>, key=<hex>, optional pow=<n>)."
  echo "Invited members receive a pvtcoms.conf alongside the exe; it is never part of the published binary."
} > "$DIST/BUILD.txt"

( cd "$DIST" && sha256sum pvtcoms.exe $( [ -f lyrebird.exe ] && echo lyrebird.exe ) > SHA256SUMS.txt )
echo "✓ $DIST/pvtcoms.exe"
cat "$DIST/SHA256SUMS.txt"

# Bundle everything the user needs into ONE zip — pvtcoms.exe + lyrebird.exe + pvtcoms.conf — so a
# non-technical user makes one download, extracts to a folder, and runs pvtcoms.exe (with the helper +
# config already beside it). Deterministic zip (sorted, fixed mtime) so the zip hash is reproducible too.
ZIP="$DIST/pvtcoms-windows.zip"
rm -f "$ZIP"
# Build the bundle with Python's zipfile (always present; deterministic) — fixed mtime + sorted entries
# so the zip hash is reproducible, matching the reproducible-build promise.
DIST="$DIST" SDE="$SOURCE_DATE_EPOCH" python3 - <<'PY'
import os, zipfile, time
dist = os.environ["DIST"]
zpath = os.path.join(dist, "pvtcoms-windows.zip")
names = [n for n in ["pvtcoms.exe", "lyrebird.exe", "pvtcoms.conf", "BUILD.txt", "SHA256SUMS.txt"]
         if os.path.exists(os.path.join(dist, n))]
dt = time.gmtime(int(os.environ.get("SDE", "315532800")))[:6]  # deterministic timestamp
with zipfile.ZipFile(zpath, "w", zipfile.ZIP_DEFLATED) as z:
    for n in sorted(names):
        zi = zipfile.ZipInfo(n, date_time=dt)
        zi.external_attr = 0o755 << 16 if n.endswith(".exe") else 0o644 << 16
        zi.compress_type = zipfile.ZIP_DEFLATED
        with open(os.path.join(dist, n), "rb") as f:
            z.writestr(zi, f.read())
print(f"✓ {zpath} ({os.path.getsize(zpath)//(1024*1024)} MB) — {', '.join(sorted(names))}")
PY

# --- publish (keeps the download site's version LINKED to the project VERSION) ---
# When PVTCOMS_PUBLISH_DIR is set, copy the artifacts there and (re)generate version.json from the
# project VERSION + the just-built hash. version.json is the single source of truth the in-app
# "Check for updates" reads, and the download page renders its version/hash from it — so a release is
# never out of sync with VERSION. Run: PVTCOMS_PUBLISH_DIR=/path bash scripts/build-windows-release.sh
if [ -n "${PVTCOMS_PUBLISH_DIR:-}" ]; then
  PUB="$PVTCOMS_PUBLISH_DIR"
  mkdir -p "$PUB"
  VER="$(tr -d '[:space:]' < "$REPO/VERSION")"
  SHA="$(sha256sum "$DIST/pvtcoms.exe" | cut -d' ' -f1)"
  URL="${PVTCOMS_DOWNLOAD_URL:-http://192.168.1.108:8009/pvtcoms.exe}"
  NOTES="${PVTCOMS_RELEASE_NOTES:-See the changelog for what changed in this release.}"
  cp "$DIST/pvtcoms.exe" "$PUB/pvtcoms.exe"
  [ -f "$DIST/lyrebird.exe" ] && cp "$DIST/lyrebird.exe" "$PUB/lyrebird.exe"
  [ -f "$DIST/pvtcoms.conf" ] && cp "$DIST/pvtcoms.conf" "$PUB/pvtcoms.conf"
  [ -f "$DIST/pvtcoms-windows.zip" ] && cp "$DIST/pvtcoms-windows.zip" "$PUB/pvtcoms-windows.zip"
  cp "$DIST/BUILD.txt" "$PUB/BUILD.txt"
  cp "$DIST/SHA256SUMS.txt" "$PUB/SHA256SUMS.txt"
  # Publish the download page (tracked in web/) so it stays in sync with the release + is reproducible.
  [ -f "$REPO/web/index.html" ] && cp "$REPO/web/index.html" "$PUB/index.html"
  [ -f "$REPO/web/favicon.svg" ] && cp "$REPO/web/favicon.svg" "$PUB/favicon.svg"
  ZIPSHA=""; [ -f "$DIST/pvtcoms-windows.zip" ] && ZIPSHA="$(sha256sum "$DIST/pvtcoms-windows.zip" | cut -d' ' -f1)"
  printf '{"version":"%s","sha256":"%s","url":"%s","zip":"pvtcoms-windows.zip","zip_sha256":"%s","notes":"%s"}\n' \
    "$VER" "$SHA" "$URL" "$ZIPSHA" "$NOTES" > "$PUB/version.json"
  echo "✓ published v$VER → $PUB (version.json links the site to VERSION)"
fi
