Files
peardata/scripts/gitea-rolling-release.sh
T
Raven Scott 3fc0245b32
CI / test (push) Successful in 1m3s
Release rolling / release (push) Has been cancelled
Updates
2026-07-18 23:47:54 -04:00

303 lines
9.1 KiB
Bash
Executable File

#!/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-<host> (forge packagerConfig.name).
# Also accept legacy PearData-<host> / 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/*-<host> 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" <<EOF
# peardata ${VERSION} (${TAG})
- Commit: \`${SHA}\` (\`${FULL_SHA}\`)
- Built: ${STAMP}
- Server archives staged: ${SERVER_OK} (failed: ${SERVER_FAIL})
- Client archives staged: ${CLIENT_OK} (failed: ${CLIENT_FAIL})
## Host matrix
| Product | Hosts |
|---------|-------|
| **Server** (Bare) | \`linux-x64\`, \`linux-arm64\` only |
| **Client** (Electron) | \`linux-x64\`, \`linux-arm64\`, \`darwin-x64\`, \`darwin-arm64\`, \`win32-x64\`, \`win32-arm64\` |
## Installer
\`\`\`bash
curl -fsSL ${GITEA_URL%/}/${GITEA_OWNER}/${GITEA_REPO}/raw/branch/main/scripts/install.sh | bash
# or: … | bash -s -- --server --yes
# or: … | bash -s -- --client --yes
\`\`\`
## Server (Bare standalone)
\`\`\`bash
tar -xzf peardata-server-${VERSION}-linux-x64.tar.gz
./peardata-server
# REST: http://127.0.0.1:18888/api/v3/info
\`\`\`
## Client (Electron)
\`\`\`bash
tar -xzf peardata-client-${VERSION}-darwin-arm64.tar.gz
open peardata-darwin-arm64/peardata.app # macOS (.app name follows packager name)
# Linux: ./peardata-linux-x64/peardata-client
# Windows: peardata-win32-x64\\\\peardata-client.exe
\`\`\`
macOS clients are codesigned in CI (ad-hoc / self-signed via \`rcodesign\` on Linux)
to avoid Gatekeeper "**damaged**" false positives. First open may still need
right-click → Open unless notarized with Developer ID.
## Checksums
See \`*.sha256\` beside each archive.
EOF
log "artifacts in $DIST:"
ls -la "$DIST" || true
log "summary: server ok=${SERVER_OK} fail=${SERVER_FAIL} | client ok=${CLIENT_OK} fail=${CLIENT_FAIL}"
if [[ "$SERVER_OK" -eq 0 ]]; then
log "ERROR: no server artifacts staged"
exit 1
fi
if [[ "${PEARDATA_SKIP_CLIENT:-0}" != "1" && "$CLIENT_OK" -eq 0 ]]; then
log "ERROR: no client artifacts staged (clients were not skipped)"
log "HINT: forge should write out/peardata-<host>/; 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}"