Wire Forge-native release infrastructure
Some checks failed
Cache: Publish Nix / Publish Nix Cache (push) Waiting to run
Build Rust / Cargo Test (push) Successful in 4m14s
Build Site / Next.js Build (push) Failing after 6s
Infra: OpenTofu / OpenTofu (grafana) (push) Successful in 5s
Lint Governance / BEP Metadata (push) Successful in 0s
Build: Android / Android Rust Core Stub (push) Failing after 23s

This commit is contained in:
Conrad Kramer 2026-06-07 05:51:12 -07:00
parent 97c569fb35
commit 002bd382e9
199 changed files with 14268 additions and 185 deletions

View file

@ -0,0 +1,332 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "${repo_root}"
platform="${1:-all}"
build_number="${BUILD_NUMBER:-${RELEASE_BUILD_NUMBER:-${RELEASE_REF:-manual}}}"
out_root="${BURROW_RELEASE_OUT:-${repo_root}/dist/builds/${build_number}}"
mkdir -p "${out_root}"
sha256_file() {
local file="$1"
if command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$file" > "${file}.sha256"
else
sha256sum "$file" > "${file}.sha256"
fi
}
burrow_crate_version() {
awk -F '"' '/^version = / { print $2; exit }' burrow/Cargo.toml
}
package_release_suffix() {
printf '%s' "$build_number" | tr -c 'A-Za-z0-9.+~' '.'
}
arch_package_suffix() {
printf '%s' "$build_number" | tr -c 'A-Za-z0-9.+_' '.'
}
deb_arch_for_target() {
case "$1" in
x86_64-unknown-linux-gnu) printf 'amd64' ;;
aarch64-unknown-linux-gnu) printf 'arm64' ;;
riscv64gc-unknown-linux-gnu) printf 'riscv64' ;;
*) return 1 ;;
esac
}
rpm_arch_for_target() {
case "$1" in
x86_64-unknown-linux-gnu) printf 'x86_64' ;;
aarch64-unknown-linux-gnu) printf 'aarch64' ;;
riscv64gc-unknown-linux-gnu) printf 'riscv64' ;;
*) return 1 ;;
esac
}
pacman_arch_for_target() {
case "$1" in
x86_64-unknown-linux-gnu) printf 'x86_64' ;;
aarch64-unknown-linux-gnu) printf 'aarch64' ;;
riscv64gc-unknown-linux-gnu) printf 'riscv64' ;;
*) return 1 ;;
esac
}
copy_license_readme() {
local dst="$1"
cp README.md "$dst/README.md"
if [[ -f LICENSE ]]; then
cp LICENSE "$dst/LICENSE"
elif [[ -f LICENSE.md ]]; then
cp LICENSE.md "$dst/LICENSE.md"
fi
}
package_deb() {
local target binary deb_arch pkg_version suffix staging deb_file installed_size
target="$1"
binary="$2"
if ! deb_arch="$(deb_arch_for_target "$target")"; then
echo "warning: no Debian architecture mapping for ${target}; skipping .deb package" >&2
return 0
fi
if ! command -v dpkg-deb >/dev/null 2>&1; then
echo "warning: dpkg-deb is not available; skipping .deb package" >&2
return 0
fi
suffix="$(package_release_suffix)"
pkg_version="${BURROW_PACKAGE_VERSION:-$(burrow_crate_version)+${suffix}}"
staging="${out_root}/linux/pkg-deb/burrow_${pkg_version}_${deb_arch}"
rm -rf "$staging"
mkdir -p \
"$staging/DEBIAN" \
"$staging/usr/bin" \
"$staging/usr/lib/systemd/system" \
"$staging/usr/share/doc/burrow" \
"$staging/usr/share/licenses/burrow"
install -m 0755 "$binary" "$staging/usr/bin/burrow"
install -m 0644 systemd/burrow.service "$staging/usr/lib/systemd/system/burrow.service"
install -m 0644 systemd/burrow.socket "$staging/usr/lib/systemd/system/burrow.socket"
install -m 0644 README.md "$staging/usr/share/doc/burrow/README.md"
install -m 0644 LICENSE.md "$staging/usr/share/licenses/burrow/LICENSE.md"
installed_size="$(du -sk "$staging/usr" | awk '{ print $1 }')"
cat > "$staging/DEBIAN/control" <<EOF
Package: burrow
Version: ${pkg_version}
Section: net
Priority: optional
Architecture: ${deb_arch}
Maintainer: Burrow Release Automation <packages@burrow.net>
Installed-Size: ${installed_size}
Depends: systemd
Description: Burrow VPN daemon and CLI
Burrow installs the daemon entrypoint and systemd socket used by the desktop UI.
EOF
deb_file="${out_root}/linux/burrow_${pkg_version}_${deb_arch}.deb"
dpkg-deb --build --root-owner-group "$staging" "$deb_file"
sha256_file "$deb_file"
}
package_rpm() {
local target binary rpm_arch
target="$1"
binary="$2"
if ! rpm_arch="$(rpm_arch_for_target "$target")"; then
echo "warning: no RPM architecture mapping for ${target}; skipping .rpm package" >&2
return 0
fi
if ! command -v cargo-generate-rpm >/dev/null 2>&1; then
echo "warning: cargo-generate-rpm is not available; skipping .rpm package" >&2
return 0
fi
mkdir -p target/release
cp "$binary" target/release/burrow
if cargo generate-rpm -p burrow; then
find target/generate-rpm -type f -name '*.rpm' -exec cp {} "${out_root}/linux/" \; 2>/dev/null || true
find "${out_root}/linux" -maxdepth 1 -type f -name '*.rpm' -print | while read -r rpm; do
sha256_file "$rpm"
done
else
echo "warning: cargo-generate-rpm failed for ${rpm_arch}; skipping .rpm package" >&2
fi
}
package_pacman() {
local target binary pacman_arch pkg_version pkgrel work source_sum service_sum socket_sum readme_sum license_sum
target="$1"
binary="$2"
if ! pacman_arch="$(pacman_arch_for_target "$target")"; then
echo "warning: no pacman architecture mapping for ${target}; skipping Arch package" >&2
return 0
fi
if ! command -v makepkg >/dev/null 2>&1; then
echo "warning: makepkg is not available; skipping Arch package" >&2
return 0
fi
pkg_version="${BURROW_ARCH_PKGVER:-$(burrow_crate_version)_$(arch_package_suffix)}"
pkgrel="${BURROW_ARCH_PKGREL:-1}"
work="${out_root}/linux/pkg-arch/${pacman_arch}"
rm -rf "$work"
mkdir -p "$work"
install -m 0755 "$binary" "$work/burrow"
install -m 0644 systemd/burrow.service "$work/burrow.service"
install -m 0644 systemd/burrow.socket "$work/burrow.socket"
install -m 0644 README.md "$work/README.md"
install -m 0644 LICENSE.md "$work/LICENSE.md"
source_sum="$(sha256sum "$work/burrow" | awk '{ print $1 }')"
service_sum="$(sha256sum "$work/burrow.service" | awk '{ print $1 }')"
socket_sum="$(sha256sum "$work/burrow.socket" | awk '{ print $1 }')"
readme_sum="$(sha256sum "$work/README.md" | awk '{ print $1 }')"
license_sum="$(sha256sum "$work/LICENSE.md" | awk '{ print $1 }')"
cat > "$work/PKGBUILD" <<EOF
# Maintainer: Burrow Release Automation <packages@burrow.net>
pkgname=burrow
pkgver=${pkg_version}
pkgrel=${pkgrel}
pkgdesc="Burrow VPN daemon and CLI"
arch=("${pacman_arch}")
url="https://git.burrow.net/hackclub/burrow"
license=("GPL-3.0-or-later")
depends=("systemd")
source=("burrow" "burrow.service" "burrow.socket" "README.md" "LICENSE.md")
sha256sums=("${source_sum}" "${service_sum}" "${socket_sum}" "${readme_sum}" "${license_sum}")
package() {
install -Dm755 "\${srcdir}/burrow" "\${pkgdir}/usr/bin/burrow"
install -Dm644 "\${srcdir}/burrow.service" "\${pkgdir}/usr/lib/systemd/system/burrow.service"
install -Dm644 "\${srcdir}/burrow.socket" "\${pkgdir}/usr/lib/systemd/system/burrow.socket"
install -Dm644 "\${srcdir}/README.md" "\${pkgdir}/usr/share/doc/burrow/README.md"
install -Dm644 "\${srcdir}/LICENSE.md" "\${pkgdir}/usr/share/licenses/burrow/LICENSE.md"
}
EOF
(
cd "$work"
makepkg --force --nodeps --noconfirm
)
find "$work" -maxdepth 1 -type f -name '*.pkg.tar.*' -exec cp {} "${out_root}/linux/" \;
find "${out_root}/linux" -maxdepth 1 -type f -name '*.pkg.tar.*' -print | while read -r package; do
sha256_file "$package"
done
}
package_linux() {
local target host archive staging
host="$(rustc -vV | awk '/^host:/ { print $2 }')"
target="${BURROW_LINUX_TARGET:-$host}"
if [[ "$target" != *-linux-* && -z "${BURROW_LINUX_TARGET:-}" ]]; then
mkdir -p "${out_root}/linux"
echo "warning: host target ${target} is not Linux; skipping Linux artifact on this host" >&2
printf 'Linux build was skipped on host target %s. Run this lane on a Linux Namespace runner or set BURROW_LINUX_TARGET explicitly.\n' "$target" > "${out_root}/linux/README.txt"
return 0
fi
staging="${out_root}/linux/burrow-${build_number}-${target}"
mkdir -p "$staging"
cargo build --locked --release -p burrow --bin burrow --target "$target"
install -m 0755 "target/${target}/release/burrow" "$staging/burrow"
copy_license_readme "$staging"
archive="${out_root}/linux/burrow-${build_number}-${target}.tar.gz"
tar -C "${out_root}/linux" -czf "$archive" "$(basename "$staging")"
sha256_file "$archive"
package_deb "$target" "target/${target}/release/burrow"
package_rpm "$target" "target/${target}/release/burrow"
package_pacman "$target" "target/${target}/release/burrow"
}
package_windows_stub_unix() {
local target exe archive staging
target="${BURROW_WINDOWS_TARGET:-x86_64-pc-windows-gnu}"
staging="${out_root}/windows/burrow-${build_number}-${target}"
mkdir -p "$staging"
target_libdir="$(rustc --print target-libdir --target "$target" 2>/dev/null || true)"
if [[ -z "$target_libdir" || ! -d "$target_libdir" ]]; then
echo "warning: Rust target ${target} is not installed on this host" >&2
printf 'Windows stub build for %s was skipped on this host. Use the Namespace Windows lane for the native artifact.\n' "$target" > "${out_root}/windows/README.txt"
return 0
fi
if cargo build --locked --release -p burrow-windows-stub --target "$target"; then
exe="target/${target}/release/burrow.exe"
else
echo "warning: unable to build the Windows release stub on this host" >&2
printf 'Windows stub build for %s was skipped on this host. Use the Namespace Windows lane for the native artifact.\n' "$target" > "${out_root}/windows/README.txt"
return 0
fi
install -m 0755 "$exe" "$staging/burrow.exe"
copy_license_readme "$staging"
printf 'Burrow Windows stub build %s\n' "$build_number" > "$staging/README-Windows.txt"
archive="${out_root}/windows/burrow-${build_number}-${target}.zip"
(cd "${out_root}/windows" && zip -qr "$archive" "$(basename "$staging")")
sha256_file "$archive"
}
package_sparkle_unsigned() {
local macos_dir appcast channel dmg
channel="${SPARKLE_CHANNEL:-build-${build_number}}"
macos_dir="${out_root}/macos"
appcast="${out_root}/sparkle/${channel}/appcast.xml"
mkdir -p "$(dirname "$appcast")"
dmg="$(find "$macos_dir" -maxdepth 1 -type f -name '*.dmg' -print 2>/dev/null | sort | tail -n 1 || true)"
if [[ -z "$dmg" ]]; then
echo "No macOS DMG exists yet; Sparkle appcast generation is waiting on the macOS signed artifact." > "${out_root}/sparkle/${channel}/README.txt"
return 0
fi
local name length pubdate url
name="$(basename "$dmg")"
length="$(wc -c < "$dmg" | tr -d ' ')"
pubdate="$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S +0000')"
url="${SPARKLE_DOWNLOAD_PREFIX:-https://releases.burrow.net/sparkle}/${channel}/${name}"
cat > "$appcast" <<EOF
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
<channel>
<title>Burrow ${channel}</title>
<item>
<title>Burrow ${build_number}</title>
<pubDate>${pubdate}</pubDate>
<sparkle:version>${build_number}</sparkle:version>
<sparkle:shortVersionString>0.1</sparkle:shortVersionString>
<enclosure url="${url}" sparkle:version="${build_number}" length="${length}" type="application/octet-stream" />
</item>
</channel>
</rss>
EOF
cp "$dmg" "${out_root}/sparkle/${channel}/"
}
case "$platform" in
all)
package_linux
package_windows_stub_unix
if [[ "$(uname -s)" == "Darwin" ]] && command -v xcodebuild >/dev/null 2>&1; then
Scripts/ci/package-apple-artifacts.sh all
else
mkdir -p "${out_root}/apple"
printf 'Apple artifacts require a macOS runner with Xcode. The release workflow builds them in the dedicated Apple lane.\n' > "${out_root}/apple/README.txt"
fi
package_sparkle_unsigned
;;
linux)
package_linux
;;
windows-stub|windows)
package_windows_stub_unix
;;
sparkle)
package_sparkle_unsigned
;;
apple)
Scripts/ci/package-apple-artifacts.sh all
;;
ios|macos)
Scripts/ci/package-apple-artifacts.sh "$platform"
;;
*)
echo "unknown release platform: $platform" >&2
exit 64
;;
esac
find "$out_root" -type f -print | sort