#!/usr/bin/env bash # Build peardock server + client for all 64-bit hosts and publish a rolling Gitea release. # # Cross-compile (default — single Linux CI runner is enough): # Server: bare-build + bare-runtime prebuilds (all hosts) # Client: electron-forge --platform/--arch (downloads Electron + native prebuilds) # # Required: # RELEASE_TOKEN — Gitea PAT with repository release write # Optional: # GITEA_URL / GITEA_OWNER / GITEA_REPO # PEARDOCK_SERVER_HOSTS — default: all 64-bit (see scripts/hosts.cjs) # PEARDOCK_CLIENT_HOSTS — default: same all 64-bit list # PEARDOCK_SKIP_CLIENT=1 # RELEASE_TAG (default: rolling) # DRY_RUN=1 — build + stage only, no upload set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" VERSION="$(node -p "require('./package.json').version")" STAMP="$(date -u +%Y%m%dT%H%M%SZ)" SHA="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)" TAG="${RELEASE_TAG:-rolling}" RELEASE_TITLE="${RELEASE_NAME:-peardock rolling}" DIST="$ROOT/dist/release" DEFAULT_HOSTS="$(node -p "require('./scripts/hosts.cjs').ALL_64.join(',')")" rm -rf "$DIST" mkdir -p "$DIST" log() { echo "[release] $*"; } detect_remote() { local url url="$(git remote get-url origin 2>/dev/null || true)" if [[ "$url" =~ git@([^:]+):([^/]+)/([^/.]+) ]]; then echo "https://${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" elif [[ "$url" =~ https?://([^/]+)/([^/]+)/([^/.]+) ]]; then echo "https://${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" else echo "" "" "" fi } read -r DETECTED_URL DETECTED_OWNER DETECTED_REPO <<<"$(detect_remote)" GITEA_URL="${GITEA_URL:-${DETECTED_URL:-}}" GITEA_OWNER="${GITEA_OWNER:-${DETECTED_OWNER:-}}" GITEA_REPO="${GITEA_REPO:-${DETECTED_REPO:-peardock}}" if [[ -z "${GITEA_URL}" ]]; then log "WARN: could not detect GITEA_URL — set GITEA_URL for upload" fi export PEARDOCK_SERVER_HOSTS="${PEARDOCK_SERVER_HOSTS:-$DEFAULT_HOSTS}" export PEARDOCK_CLIENT_HOSTS="${PEARDOCK_CLIENT_HOSTS:-$DEFAULT_HOSTS}" # Prefer prebuilt natives when electron-forge cross-packages export npm_config_build_from_source="${npm_config_build_from_source:-false}" # Skip @electron/rebuild during forge package (prebuilds only) — avoids multi-minute "Finalizing" export PEARDOCK_SKIP_REBUILD="${PEARDOCK_SKIP_REBUILD:-1}" # --- server: all hosts (bare cross-compile) --- log "building server binaries for: $PEARDOCK_SERVER_HOSTS" node scripts/make.cjs server # --- client: all hosts (electron-forge cross package) --- if [[ "${PEARDOCK_SKIP_CLIENT:-0}" != "1" ]]; then log "building client binaries for: $PEARDOCK_CLIENT_HOSTS" node scripts/make.cjs client else log "skipping client (PEARDOCK_SKIP_CLIENT=1)" fi # --- stage artifacts --- sha_file() { local f="$1" if command -v sha256sum >/dev/null 2>&1; then sha256sum "$f" else shasum -a 256 "$f" fi } stage_server() { local host="$1" local dir="$ROOT/out/peardock-server-$host" local bin="peardock-server" [[ "$host" == win32-* ]] && bin="peardock-server.exe" if [[ ! -d "$dir" ]]; then log "WARN: missing server dir $dir" return 1 fi if [[ ! -f "$dir/$bin" ]]; then local f f="$(find "$dir" -maxdepth 2 -type f \( -name 'peardock-server' -o -name 'peardock-server.exe' \) | head -1 || true)" if [[ -n "$f" ]]; then bin="$(basename "$f")" # ensure top-level copy for tarball convenience cp -f "$f" "$dir/$bin" 2>/dev/null || true else log "WARN: no server binary in $dir" return 1 fi fi local archive="peardock-server-${VERSION}-${host}.tar.gz" tar -C "$dir" -czf "$DIST/$archive" . (cd "$DIST" && sha_file "$archive" >"${archive}.sha256") log "staged $archive" } stage_client() { local host="$1" local dir="$ROOT/out/peardock-$host" if [[ ! -d "$dir" ]]; then log "WARN: missing client dir $dir" return 1 fi local archive="peardock-client-${VERSION}-${host}.tar.gz" tar -C "$ROOT/out" -czf "$DIST/$archive" "peardock-$host" (cd "$DIST" && sha_file "$archive" >"${archive}.sha256") log "staged $archive" } SERVER_OK=0 CLIENT_OK=0 SERVER_FAIL=0 CLIENT_FAIL=0 IFS=',' read -ra SHOSTS <<<"$PEARDOCK_SERVER_HOSTS" for h in "${SHOSTS[@]}"; do h="$(echo "$h" | xargs)" if stage_server "$h"; then SERVER_OK=$((SERVER_OK + 1)); else SERVER_FAIL=$((SERVER_FAIL + 1)); fi done if [[ "${PEARDOCK_SKIP_CLIENT:-0}" != "1" ]]; then IFS=',' read -ra CHOSTS <<<"$PEARDOCK_CLIENT_HOSTS" for h in "${CHOSTS[@]}"; do h="$(echo "$h" | xargs)" if stage_client "$h"; then CLIENT_OK=$((CLIENT_OK + 1)); else CLIENT_FAIL=$((CLIENT_FAIL + 1)); fi done fi cat >"$DIST/RELEASE_NOTES.md" </dev/null || true)" if [[ -n "$REL_JSON" ]]; then REL_ID="$(node -e "try{const j=JSON.parse(process.argv[1]);console.log(j.id||'')}catch{console.log('')}" "$REL_JSON")" if [[ -n "$REL_ID" ]]; then log "deleting previous release id=$REL_ID" curl -fsSL -X DELETE -H "$AUTH" "$API/releases/$REL_ID" >/dev/null || true fi fi curl -fsSL -X DELETE -H "$AUTH" "$API/tags/${TAG}" >/dev/null 2>&1 || true CREATE_BODY="$(node -e " const notes=require('fs').readFileSync(process.argv[1],'utf8'); console.log(JSON.stringify({ tag_name: process.argv[2], name: process.argv[3], body: notes, draft: false, prerelease: true, target_commitish: process.env.GITHUB_SHA || process.env.GITEA_SHA || 'main' })); " "$DIST/RELEASE_NOTES.md" "$TAG" "${RELEASE_TITLE} ${VERSION} ${SHA}")" CREATE_RESP="$(curl -fsSL -X POST -H "$AUTH" -H 'Content-Type: application/json' \ -d "$CREATE_BODY" "$API/releases")" REL_ID="$(node -e "console.log(JSON.parse(process.argv[1]).id)" "$CREATE_RESP")" log "created release id=$REL_ID" shopt -s nullglob for f in "$DIST"/*; do [[ -f "$f" ]] || continue base="$(basename "$f")" case "$base" in *.tar.gz|*.sha256|*.md) ;; *) continue ;; esac log "upload $base" name_q="$(node -e "console.log(encodeURIComponent(process.argv[1]))" "$base")" curl -fsSL -X POST -H "$AUTH" \ -F "attachment=@${f}" \ "$API/releases/${REL_ID}/assets?name=${name_q}" \ >/dev/null done log "release published: ${GITEA_URL}/${GITEA_OWNER}/${GITEA_REPO}/releases/tag/${TAG}"