#!/usr/bin/env bash # Build peardata server (Linux) + client (all arches) and publish a rolling Gitea release. # # Cross-compile (default — single Linux CI runner is enough): # Server: bare-build + bare-runtime prebuilds (linux-x64, linux-arm64 only) # Client: electron-forge --platform/--arch (all 64-bit hosts) # # Required: # RELEASE_TOKEN — Gitea PAT with repository release write # Optional: # GITEA_URL / GITEA_OWNER / GITEA_REPO # PEARDATA_SERVER_HOSTS — default: linux-x64,linux-arm64 # PEARDATA_CLIENT_HOSTS — default: all 64-bit (see scripts/hosts.cjs) # PEARDATA_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)" FULL_SHA="${GITHUB_SHA:-${GITEA_SHA:-$(git rev-parse HEAD 2>/dev/null || echo main)}}" TAG="${RELEASE_TAG:-rolling}" RELEASE_TITLE="${RELEASE_NAME:-peardata rolling}" DIST="$ROOT/dist/release" DEFAULT_SERVER="$(node -p "require('./scripts/hosts.cjs').SERVER_LINUX.join(',')")" DEFAULT_CLIENT="$(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:-peardata}}" if [[ -z "${GITEA_URL}" ]]; then log "WARN: could not detect GITEA_URL — set GITEA_URL for upload" fi export PEARDATA_SERVER_HOSTS="${PEARDATA_SERVER_HOSTS:-$DEFAULT_SERVER}" export PEARDATA_CLIENT_HOSTS="${PEARDATA_CLIENT_HOSTS:-$DEFAULT_CLIENT}" export npm_config_build_from_source="${npm_config_build_from_source:-false}" export PEARDATA_SKIP_REBUILD="${PEARDATA_SKIP_REBUILD:-1}" # --- server: Linux only --- log "building server binaries for: $PEARDATA_SERVER_HOSTS" node scripts/make.cjs server # --- client: all hosts --- if [[ "${PEARDATA_SKIP_CLIENT:-0}" != "1" ]]; then log "building client binaries for: $PEARDATA_CLIENT_HOSTS" node scripts/make.cjs client else log "skipping client (PEARDATA_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/peardata-server-$host" local bin="peardata-server" 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 'peardata-server' | head -1 || true)" if [[ -n "$f" ]]; then bin="$(basename "$f")" cp -f "$f" "$dir/$bin" 2>/dev/null || true else log "WARN: no server binary in $dir" return 1 fi fi local archive="peardata-server-${VERSION}-${host}.tar.gz" tar -C "$dir" -czf "$DIST/$archive" . (cd "$DIST" && sha_file "$archive" >"${archive}.sha256") log "staged $archive" } find_client_dir() { local host="$1" # Prefer stable lowercase out/peardata- (forge packagerConfig.name). # Also accept legacy PearData- / productName variants from older builds. local candidates=( "$ROOT/out/peardata-$host" "$ROOT/out/PearData-$host" "$ROOT/out/peardata-client-$host" ) local d for d in "${candidates[@]}"; do if [[ -d "$d" ]]; then echo "$d" return 0 fi done # Last resort: any out/*- Electron package (exclude Bare server dirs) while IFS= read -r d; do [[ -z "$d" ]] && continue case "$(basename "$d")" in peardata-server-*) continue ;; esac echo "$d" return 0 done < <(find "$ROOT/out" -maxdepth 1 -type d -name "*-$host" 2>/dev/null | sort) return 1 } stage_client() { local host="$1" local dir if ! dir="$(find_client_dir "$host")"; then log "WARN: missing client dir for $host (looked under out/*-$host)" ls -la "$ROOT/out" 2>/dev/null || true return 1 fi local leaf leaf="$(basename "$dir")" local archive="peardata-client-${VERSION}-${host}.tar.gz" tar -C "$ROOT/out" -czf "$DIST/$archive" "$leaf" (cd "$DIST" && sha_file "$archive" >"${archive}.sha256") log "staged $archive (from $leaf)" } SERVER_OK=0 CLIENT_OK=0 SERVER_FAIL=0 CLIENT_FAIL=0 IFS=',' read -ra SHOSTS <<<"$PEARDATA_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 [[ "${PEARDATA_SKIP_CLIENT:-0}" != "1" ]]; then IFS=',' read -ra CHOSTS <<<"$PEARDATA_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" </; check make:client logs above" exit 1 fi if [[ "${DRY_RUN:-0}" == "1" ]]; then log "DRY_RUN=1 — skip Gitea upload" exit 0 fi if [[ -z "${RELEASE_TOKEN:-}" ]]; then log "ERROR: RELEASE_TOKEN is required (or DRY_RUN=1)" exit 1 fi if [[ -z "${GITEA_URL}" || -z "${GITEA_OWNER}" || -z "${GITEA_REPO}" ]]; then log "ERROR: GITEA_URL / GITEA_OWNER / GITEA_REPO must be set" exit 1 fi API="${GITEA_URL%/}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}" AUTH="Authorization: token ${RELEASE_TOKEN}" log "Gitea API: $API tag=$TAG" REL_JSON="$(curl -fsSL -H "$AUTH" "$API/releases/tags/${TAG}" 2>/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.argv[4] })); " "$DIST/RELEASE_NOTES.md" "$TAG" "${RELEASE_TITLE} ${VERSION} ${SHA}" "$FULL_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}"