from-source builds have no business pulling a 9GB MSVC sysroot for one Tauri exe. CI builds it once; the server image downloads the signed artifact and hash-checks it against the manifest. no release / offline -> no tray, server 404-skips. also: make rebuild/up/down/logs so `up -d build` stops biting.
114 lines
4.8 KiB
Shell
Executable file
114 lines
4.8 KiB
Shell
Executable file
#!/bin/sh
|
|
# fetch-desktop-windows.sh — obtain the prebuilt, signed Windows desktop tray
|
|
# binary from the newest published release and verify it against that release's
|
|
# manifest hash.
|
|
#
|
|
# Why this exists: the agent is Go and cross-compiles to Windows for free
|
|
# (Dockerfile bakes redflag-agent.exe directly). The desktop tray is Tauri/Rust
|
|
# and its Windows cross-compile drags in the whole MSVC sysroot (~9GB of build
|
|
# toolchain). Forcing that into every from-source `docker-compose build` is
|
|
# unacceptable — build-from-source is the primary distribution path. So the
|
|
# expensive compile lives in CI (release.yml, cargo-xwin) exactly once, and
|
|
# from-source servers fetch the resulting signed artifact here.
|
|
#
|
|
# Trust: TLS to the forge + the manifest sha256 gate this download, the same
|
|
# trust any release download carries. The server re-signs the binary with its
|
|
# own Ed25519 key at startup before serving it to the fleet — that is the
|
|
# load-bearing signature agents verify. This step gets a known-good exe into the
|
|
# serving path; it is not the fleet trust root.
|
|
#
|
|
# The desktop component is OPTIONAL (manifest required:false). Every failure that
|
|
# is not active tampering degrades to "no Windows tray" rather than breaking the
|
|
# build: offline, no release yet, no desktop asset in the release, no manifest
|
|
# entry. A hash MISMATCH is the one hard stop — that is tampering, not absence.
|
|
set -eu
|
|
|
|
ARCH="${1:-amd64}"
|
|
OUT_DIR="${2:-/out}"
|
|
REPO_API="${DESKTOP_RELEASE_REPO_API:-https://codeberg.org/api/v1/repos/Fimeg/RedFlag}"
|
|
|
|
log() { printf '%s\n' "[INFO] [build] [desktop-fetch] $*" >&2; }
|
|
warn() { printf '%s\n' "[WARN] [build] [desktop-fetch] $*" >&2; }
|
|
fail() { printf '%s\n' "[ERROR] [build] [desktop-fetch] $*" >&2; exit 1; }
|
|
|
|
mkdir -p "$OUT_DIR"
|
|
work="$(mktemp -d)"
|
|
trap 'rm -rf "$work"' EXIT
|
|
|
|
# Newest release INCLUDING prereleases. /releases/latest skips prereleases, and
|
|
# every tag below v0.3.0 publishes as a prerelease during the alpha — using it
|
|
# would always come back empty. The list endpoint sorts newest-first.
|
|
log "querying newest release from $REPO_API"
|
|
if ! curl -sfL "$REPO_API/releases?limit=1&draft=false" -o "$work/releases.json"; then
|
|
warn "release API unreachable (offline or forge down) — skipping Windows tray"
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$(jq 'length' "$work/releases.json" 2>/dev/null || echo 0)" = "0" ]; then
|
|
log "no published releases at $REPO_API yet — Windows tray ships once a release exists"
|
|
exit 0
|
|
fi
|
|
|
|
manifest_url="$(jq -r '.[0].assets[]? | select(.name == "manifest.json") | .browser_download_url' "$work/releases.json" 2>/dev/null | head -1)"
|
|
tag="$(jq -r '.[0].tag_name // empty' "$work/releases.json" 2>/dev/null)"
|
|
if [ -z "$manifest_url" ] || [ "$manifest_url" = "null" ]; then
|
|
warn "newest release ($tag) has no manifest.json asset — skipping Windows tray"
|
|
exit 0
|
|
fi
|
|
log "newest release: $tag"
|
|
|
|
if ! curl -sfL "$manifest_url" -o "$work/manifest.json"; then
|
|
warn "manifest download failed — skipping Windows tray"
|
|
exit 0
|
|
fi
|
|
|
|
# Pull the expected hash + filename for desktop-windows/<arch> out of the manifest.
|
|
expected_sha="$(jq -r --arg a "$ARCH" \
|
|
'.artifacts[]? | select(.platform == "desktop-windows" and .architecture == $a) | .sha256' \
|
|
"$work/manifest.json" 2>/dev/null | head -1)"
|
|
exe_name="$(jq -r --arg a "$ARCH" \
|
|
'.artifacts[]? | select(.platform == "desktop-windows" and .architecture == $a) | .filename' \
|
|
"$work/manifest.json" 2>/dev/null | head -1)"
|
|
|
|
if [ -z "$expected_sha" ] || [ "$expected_sha" = "null" ]; then
|
|
warn "release $tag carries no desktop-windows/$ARCH binary — Windows tray unavailable from this release"
|
|
exit 0
|
|
fi
|
|
|
|
# The exe ships inside the platform zip (release.yml attaches zips, not loose
|
|
# exes). Derive the zip asset name from the release version.
|
|
ver="${tag#v}"
|
|
zip_name="redflag-${ver}-windows-${ARCH}.zip"
|
|
zip_url="$(jq -r --arg z "$zip_name" \
|
|
'.[0].assets[]? | select(.name == $z) | .browser_download_url' \
|
|
"$work/releases.json" 2>/dev/null | head -1)"
|
|
if [ -z "$zip_url" ] || [ "$zip_url" = "null" ]; then
|
|
warn "release $tag has a manifest entry but no $zip_name asset — skipping Windows tray"
|
|
exit 0
|
|
fi
|
|
|
|
log "downloading $zip_name"
|
|
if ! curl -sfL "$zip_url" -o "$work/win.zip"; then
|
|
warn "zip download failed — skipping Windows tray"
|
|
exit 0
|
|
fi
|
|
|
|
if ! unzip -o -q "$work/win.zip" -d "$work/unz"; then
|
|
warn "zip extraction failed — skipping Windows tray"
|
|
exit 0
|
|
fi
|
|
|
|
src="$work/unz/$exe_name"
|
|
if [ ! -f "$src" ]; then
|
|
warn "$exe_name not found inside $zip_name — skipping Windows tray"
|
|
exit 0
|
|
fi
|
|
|
|
actual_sha="$(sha256sum "$src" | awk '{print $1}')"
|
|
if [ "$actual_sha" != "$expected_sha" ]; then
|
|
# Tampering — refuse the build. This is the one non-recoverable case.
|
|
fail "hash mismatch on $exe_name: manifest=$expected_sha actual=$actual_sha"
|
|
fi
|
|
|
|
cp "$src" "$OUT_DIR/redflag-desktop.exe"
|
|
log "verified Windows tray $tag (desktop-windows/$ARCH) -> $OUT_DIR/redflag-desktop.exe"
|