Update CI
Release rolling / release (push) Has been cancelled

This commit is contained in:
Raven Scott
2026-07-11 11:44:08 -04:00
parent 668d3b7f36
commit 6303b5108d
11 changed files with 281 additions and 358 deletions
+1 -8
View File
@@ -28,14 +28,7 @@ const id = require('bare-bundle-id')
const root = path.resolve(__dirname, '..')
const pkg = require(path.join(root, 'package.json'))
const ALL_HOSTS = [
'linux-x64',
'linux-arm64',
'darwin-arm64',
'darwin-x64',
'win32-x64',
]
const { ALL_64: ALL_HOSTS } = require('./hosts.cjs')
const STUBS = {
ssh2: 'build/stubs/ssh2.cjs',
+82 -79
View File
@@ -1,20 +1,19 @@
#!/usr/bin/env bash
# Build peardock server (+ client when possible) and publish a rolling Gitea release.
# Build peardock server + client for all 64-bit hosts and publish a rolling Gitea release.
#
# Required secrets / env:
# 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 — forge base (default: derived from git remote or https://gitea.com)
# GITEA_OWNER — org/user (default: from git remote)
# GITEA_REPO — repo name (default: from git remote)
# PEARDOCK_SKIP_CLIENT=1 — server-only release
# PEARDOCK_SERVER_HOSTS — comma list (default: all five hosts)
# PEARDOCK_CLIENT_HOSTS — comma list (default: host of this runner only)
# RELEASE_TAG — default: rolling
# RELEASE_NAME — default: peardock rolling
#
# Local dry-run (no upload):
# DRY_RUN=1 bash scripts/gitea-rolling-release.sh
# 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)"
@@ -26,16 +25,16 @@ 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] $*"; }
# --- resolve Gitea API endpoint / repo ---
detect_remote() {
local url
url="$(git remote get-url origin 2>/dev/null || true)"
# git@host:owner/repo.git or https://host/owner/repo.git
if [[ "$url" =~ git@([^:]+):([^/]+)/([^/.]+) ]]; then
echo "https://${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}"
elif [[ "$url" =~ https?://([^/]+)/([^/]+)/([^/.]+) ]]; then
@@ -51,69 +50,61 @@ 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 from git remote — set GITEA_URL explicitly for upload"
log "WARN: could not detect GITEA_URL — set GITEA_URL for upload"
fi
# --- build server (all hosts via bare-build cross) ---
log "building server binaries (v${VERSION})"
export PEARDOCK_SERVER_HOSTS="${PEARDOCK_SERVER_HOSTS:-linux-x64,linux-arm64,darwin-arm64,darwin-x64,win32-x64}"
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}"
# --- server: all hosts (bare cross-compile) ---
log "building server binaries for: $PEARDOCK_SERVER_HOSTS"
node scripts/make.cjs server
# --- build client for this runner (native electron rebuild) ---
# --- client: all hosts (electron-forge cross package) ---
if [[ "${PEARDOCK_SKIP_CLIENT:-0}" != "1" ]]; then
THIS_HOST="$(node -p "process.platform + '-' + process.arch")"
export PEARDOCK_CLIENT_HOSTS="${PEARDOCK_CLIENT_HOSTS:-$THIS_HOST}"
log "building client for: $PEARDOCK_CLIENT_HOSTS"
# client-all honors PEARDOCK_CLIENT_HOSTS via make.cjs? Currently make client-all uses env list
# Use make.cjs client-all only if multiple; else make:client:* scripts
IFS=',' read -ra CHOSTS <<<"$PEARDOCK_CLIENT_HOSTS"
npm run build:client-bundle
for h in "${CHOSTS[@]}"; do
h="$(echo "$h" | xargs)"
case "$h" in
linux-x64) npm run make:client:linux-x64 ;;
linux-arm64) npm run make:client:linux-arm64 ;;
darwin-arm64) npm run make:client:darwin-arm64 ;;
darwin-x64) npm run make:client:darwin-x64 ;;
win32-x64) npm run make:client:win32-x64 ;;
*) log "WARN: skip unknown client host $h" ;;
esac
done
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 $dir"
return
log "WARN: missing server dir $dir"
return 1
fi
if [[ ! -f "$dir/$bin" ]]; then
# pick first executable-ish file
local f
f="$(find "$dir" -maxdepth 1 -type f ! -name '*.json' ! -name '.*' | head -1 || true)"
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 binary in $dir"
return
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"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$archive" >"${archive}.sha256"
else
shasum -a 256 "$archive" >"${archive}.sha256"
fi
)
(cd "$DIST" && sha_file "$archive" >"${archive}.sha256")
log "staged $archive"
}
@@ -122,62 +113,78 @@ stage_client() {
local dir="$ROOT/out/peardock-$host"
if [[ ! -d "$dir" ]]; then
log "WARN: missing client dir $dir"
return
return 1
fi
local archive="peardock-client-${VERSION}-${host}.tar.gz"
tar -C "$ROOT/out" -czf "$DIST/$archive" "peardock-$host"
(
cd "$DIST"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$archive" >"${archive}.sha256"
else
shasum -a 256 "$archive" >"${archive}.sha256"
fi
)
(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
stage_server "$(echo "$h" | xargs)"
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:-}"
IFS=',' read -ra CHOSTS <<<"$PEARDOCK_CLIENT_HOSTS"
for h in "${CHOSTS[@]}"; do
stage_client "$(echo "$h" | xargs)"
h="$(echo "$h" | xargs)"
if stage_client "$h"; then CLIENT_OK=$((CLIENT_OK + 1)); else CLIENT_FAIL=$((CLIENT_FAIL + 1)); fi
done
fi
# notes
cat >"$DIST/RELEASE_NOTES.md" <<EOF
# peardock ${VERSION} (${TAG})
- Commit: \`${SHA}\`
- Built: ${STAMP}
- Server archives staged: ${SERVER_OK} (failed: ${SERVER_FAIL})
- Client archives staged: ${CLIENT_OK} (failed: ${CLIENT_FAIL})
## Hosts (64-bit only)
\`linux-x64\`, \`linux-arm64\`, \`darwin-x64\`, \`darwin-arm64\`, \`win32-x64\`, \`win32-arm64\`
## Server (Bare standalone)
Extract and run:
\`\`\`bash
tar -xzf peardock-server-${VERSION}-linux-x64.tar.gz
./peardock-server
./peardock-server # or peardock-server.exe on Windows
\`\`\`
Requires Docker socket access (\`/var/run/docker.sock\` or Windows named pipe).
Needs Docker socket access.
## Client (Electron)
Extract the archive for your OS and launch \`peardock-client\` / \`peardock.app\`.
\`\`\`bash
tar -xzf peardock-client-${VERSION}-darwin-arm64.tar.gz
open peardock-darwin-arm64/peardock.app # macOS
# Linux: ./peardock-linux-x64/peardock-client
# Windows: peardock-win32-x64\\\\peardock-client.exe
\`\`\`
macOS builds from Linux CI are **unsigned** (Gatekeeper may require right-click → Open).
## Checksums
See \`*.sha256\` next to each archive.
See \`*.sha256\` beside each archive.
EOF
log "artifacts in $DIST:"
ls -la "$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 [[ "${DRY_RUN:-0}" == "1" ]]; then
log "DRY_RUN=1 — skip Gitea upload"
@@ -185,7 +192,7 @@ if [[ "${DRY_RUN:-0}" == "1" ]]; then
fi
if [[ -z "${RELEASE_TOKEN:-}" ]]; then
log "ERROR: RELEASE_TOKEN is required to upload (or set DRY_RUN=1)"
log "ERROR: RELEASE_TOKEN is required (or DRY_RUN=1)"
exit 1
fi
@@ -199,10 +206,9 @@ AUTH="Authorization: token ${RELEASE_TOKEN}"
log "Gitea API: $API tag=$TAG"
# Delete existing rolling release + tag (ignore errors)
REL_JSON="$(curl -fsSL -H "$AUTH" "$API/releases/tags/${TAG}" 2>/dev/null || true)"
if [[ -n "$REL_JSON" && "$REL_JSON" != *"message"* ]]; then
REL_ID="$(node -e "const j=JSON.parse(process.argv[1]); console.log(j.id||'')" "$REL_JSON")"
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
@@ -210,8 +216,6 @@ if [[ -n "$REL_JSON" && "$REL_JSON" != *"message"* ]]; then
fi
curl -fsSL -X DELETE -H "$AUTH" "$API/tags/${TAG}" >/dev/null 2>&1 || true
# Create release
NOTES="$(cat "$DIST/RELEASE_NOTES.md")"
CREATE_BODY="$(node -e "
const notes=require('fs').readFileSync(process.argv[1],'utf8');
console.log(JSON.stringify({
@@ -229,7 +233,6 @@ CREATE_RESP="$(curl -fsSL -X POST -H "$AUTH" -H 'Content-Type: application/json'
REL_ID="$(node -e "console.log(JSON.parse(process.argv[1]).id)" "$CREATE_RESP")"
log "created release id=$REL_ID"
# Upload assets (Gitea expects multipart form field "attachment")
shopt -s nullglob
for f in "$DIST"/*; do
[[ -f "$f" ]] || continue
+61
View File
@@ -0,0 +1,61 @@
/**
* Canonical peardock binary host list (64-bit only — no ia32 / armv7).
* Used by make.cjs, bare-standalone.cjs, and release scripts.
*/
'use strict'
/** @type {readonly string[]} */
const ALL_64 = Object.freeze([
'linux-x64',
'linux-arm64',
'darwin-x64',
'darwin-arm64',
'win32-x64',
'win32-arm64',
])
/**
* @param {string|undefined} envVal comma-separated override
* @param {readonly string[]} fallback
*/
function parseHostList(envVal, fallback = ALL_64) {
if (!envVal || !String(envVal).trim()) return [...fallback]
return String(envVal)
.split(',')
.map((s) => s.trim())
.filter(Boolean)
}
/**
* electron-forge platform/arch for a host triple
* @param {string} host
* @returns {{ platform: string, arch: string }}
*/
function hostToElectron(host) {
const [platform, arch] = host.split('-')
if (!platform || !arch) throw new Error(`Invalid host: ${host}`)
return { platform, arch }
}
/**
* npm script name for client package, or null
* @param {string} host
*/
function clientNpmScript(host) {
const map = {
'linux-x64': 'make:client:linux-x64',
'linux-arm64': 'make:client:linux-arm64',
'darwin-arm64': 'make:client:darwin-arm64',
'darwin-x64': 'make:client:darwin-x64',
'win32-x64': 'make:client:win32-x64',
'win32-arm64': 'make:client:win32-arm64',
}
return map[host] || null
}
module.exports = {
ALL_64,
parseHostList,
hostToElectron,
clientNpmScript,
}
+36 -47
View File
@@ -1,38 +1,26 @@
#!/usr/bin/env node
/**
* Orchestrate peardock binary builds.
* Orchestrate peardock binary builds for all 64-bit hosts.
*
* node scripts/make.cjs server # all server hosts (bare-build)
* node scripts/make.cjs client # client for this host (electron-forge)
* node scripts/make.cjs both # server all hosts + client this host
* node scripts/make.cjs all # server all hosts + client all hosts (needs multi-OS)
* node scripts/make.cjs server # all server hosts (bare-build cross)
* node scripts/make.cjs client # all client hosts (electron-forge cross)
* node scripts/make.cjs both|all # server + client, all hosts
* node scripts/make.cjs client-this # client for this runner only
*
* Env:
* PEARDOCK_SERVER_HOSTS=linux-x64,darwin-arm64 # override server host list
* PEARDOCK_CLIENT_HOSTS=linux-x64 # override client host list
* PEARDOCK_SERVER_HOSTS=linux-x64,darwin-arm64
* PEARDOCK_CLIENT_HOSTS=linux-x64,win32-x64
*/
'use strict'
const path = require('path')
const { spawnSync } = require('child_process')
const { ALL_64, parseHostList, clientNpmScript } = require('./hosts.cjs')
const root = path.resolve(__dirname, '..')
const SERVER_HOSTS = (
process.env.PEARDOCK_SERVER_HOSTS ||
'linux-x64,linux-arm64,darwin-arm64,darwin-x64,win32-x64'
)
.split(',')
.map((s) => s.trim())
.filter(Boolean)
const CLIENT_HOSTS_ALL = (
process.env.PEARDOCK_CLIENT_HOSTS ||
'linux-x64,linux-arm64,darwin-arm64,darwin-x64,win32-x64'
)
.split(',')
.map((s) => s.trim())
.filter(Boolean)
const SERVER_HOSTS = parseHostList(process.env.PEARDOCK_SERVER_HOSTS, ALL_64)
const CLIENT_HOSTS = parseHostList(process.env.PEARDOCK_CLIENT_HOSTS, ALL_64)
function run(cmd, args, opts = {}) {
console.log(`\n$ ${cmd} ${args.join(' ')}\n`)
@@ -51,8 +39,9 @@ function npmRun(script) {
run(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', script])
}
function makeServer() {
for (const host of SERVER_HOSTS) {
function makeServer(hosts = SERVER_HOSTS) {
console.log(`[make] server hosts: ${hosts.join(', ')}`)
for (const host of hosts) {
run(process.execPath, [
path.join(root, 'scripts', 'bare-standalone.cjs'),
'--product',
@@ -63,29 +52,26 @@ function makeServer() {
}
}
function clientScript(host) {
const map = {
'linux-x64': 'make:client:linux-x64',
'linux-arm64': 'make:client:linux-arm64',
'darwin-arm64': 'make:client:darwin-arm64',
'darwin-x64': 'make:client:darwin-x64',
'win32-x64': 'make:client:win32-x64',
}
const s = map[host]
if (!s) throw new Error(`No client build script for host ${host}`)
return s
}
function makeClient(hosts) {
// Always rebuild GUI CJS bundle first
function makeClient(hosts = CLIENT_HOSTS) {
console.log(`[make] client hosts: ${hosts.join(', ')}`)
npmRun('build:client-bundle')
for (const host of hosts) {
npmRun(clientScript(host))
const script = clientNpmScript(host)
if (!script) {
console.error(`[make] no client script for host ${host}`)
process.exit(1)
}
// Prefer prebuilds when cross-packaging (electron-rebuild on foreign arch fails)
const env = {
...process.env,
npm_config_build_from_source: 'false',
}
run(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', script], { env })
}
}
function main() {
const mode = process.argv[2] || 'both'
const mode = process.argv[2] || 'all'
const thisHost = `${process.platform}-${process.arch}`
switch (mode) {
@@ -93,22 +79,25 @@ function main() {
makeServer()
break
case 'client':
makeClient()
break
case 'client-this':
makeClient([thisHost])
break
case 'both':
makeServer()
makeClient([thisHost])
break
case 'all':
makeServer()
makeClient(CLIENT_HOSTS_ALL)
makeClient()
break
case 'client-all':
makeClient(CLIENT_HOSTS_ALL)
makeClient(ALL_64)
break
default:
console.error(`Unknown mode: ${mode}`)
console.error('Usage: node scripts/make.cjs [server|client|both|all|client-all]')
console.error(
'Usage: node scripts/make.cjs [server|client|client-this|both|all|client-all]'
)
console.error(`Default 64-bit hosts: ${ALL_64.join(', ')}`)
process.exit(1)
}