Make peardock-client the full Pear GUI via Electron Forge
Rolling release / server / win32-arm64 (push) Failing after 9s
Rolling release / server / darwin-arm64 (push) Has been cancelled
Rolling release / server / darwin-x64 (push) Has been cancelled
Rolling release / server / linux-arm64 (push) Has been cancelled
Rolling release / server / linux-x64 (push) Has been cancelled
Rolling release / server / win32-x64 (push) Has been cancelled
Rolling release / client / macos-latest (push) Has been cancelled
Rolling release / client / windows-latest (push) Has been cancelled
Rolling release / Publish rolling release (push) Has been cancelled
Rolling release / client / ubuntu-latest (push) Has been cancelled
CI / test (push) Has been cancelled
Rolling release / server / win32-arm64 (push) Failing after 9s
Rolling release / server / darwin-arm64 (push) Has been cancelled
Rolling release / server / darwin-x64 (push) Has been cancelled
Rolling release / server / linux-arm64 (push) Has been cancelled
Rolling release / server / linux-x64 (push) Has been cancelled
Rolling release / server / win32-x64 (push) Has been cancelled
Rolling release / client / macos-latest (push) Has been cancelled
Rolling release / client / windows-latest (push) Has been cancelled
Rolling release / Publish rolling release (push) Has been cancelled
Rolling release / client / ubuntu-latest (push) Has been cancelled
CI / test (push) Has been cancelled
Replace the headless Bare agent client with a real desktop app: Electron shell loads the same index.html/app.js UI as pear run, with a Pear polyfill, Holesail control, and Node ESM bootstrap for HyperDHT. Server remains bare-build standalone; CI rolling release publishes both product types.
This commit is contained in:
+10
-2
@@ -26,11 +26,19 @@ jobs:
|
||||
- name: Unit + RPC tests
|
||||
run: npm test
|
||||
|
||||
- name: Smoke-pack server (linux-x64)
|
||||
- name: Smoke-pack server (linux-x64 Bare)
|
||||
run: npm run make:server:linux-x64
|
||||
|
||||
- name: Verify binary exists
|
||||
- name: Verify server binary
|
||||
run: |
|
||||
test -x out/server/linux-x64/peardock-server
|
||||
file out/server/linux-x64/peardock-server || true
|
||||
ls -lh out/server/linux-x64/
|
||||
|
||||
- name: Smoke-package client (Electron full Pear GUI)
|
||||
run: npx electron-forge package --platform linux --arch x64
|
||||
|
||||
- name: Verify client package
|
||||
run: |
|
||||
test -x out/peardock-linux-x64/peardock-client
|
||||
ls -lh out/peardock-linux-x64/peardock-client
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
# peardock — rolling multi-arch Bare standalone binaries
|
||||
# peardock — rolling release
|
||||
# - peardock-server: Bare standalone (bare-build --standalone, embeds all modules)
|
||||
# - peardock-client: full Pear GUI (Electron Forge package / AppImage / zip)
|
||||
#
|
||||
# Secret required in the Gitea repo:
|
||||
# RELEASE_TOKEN — personal access token with repository release write scope
|
||||
#
|
||||
# Optional secrets / vars:
|
||||
# GITEA_URL — defaults to the forge this workflow runs on
|
||||
#
|
||||
# Builds use bare-build --standalone (embeds full JS module graph + native addons)
|
||||
# Pattern: holepunchto/hello-pear-bare + bare-build README
|
||||
#
|
||||
# Gitea Actions is Actions-compatible; runner labels may differ on your forge.
|
||||
# Secret: RELEASE_TOKEN (Gitea PAT with release write)
|
||||
# Optional: GITEA_URL
|
||||
|
||||
name: Rolling release
|
||||
|
||||
@@ -27,134 +21,153 @@ env:
|
||||
RELEASE_TAG: rolling
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.product }} / ${{ matrix.host }}
|
||||
build-server:
|
||||
name: server / ${{ matrix.host }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Linux x64 — primary server target
|
||||
- os: ubuntu-latest
|
||||
host: linux-x64
|
||||
product: server
|
||||
- os: ubuntu-latest
|
||||
host: linux-x64
|
||||
product: client
|
||||
# Linux arm64 (label may be ubuntu-latest on multiarch runners)
|
||||
- os: ubuntu-latest
|
||||
host: linux-arm64
|
||||
product: server
|
||||
- os: ubuntu-latest
|
||||
host: linux-arm64
|
||||
product: client
|
||||
# Cross-pack from Linux where prebuilds exist (darwin/win32)
|
||||
- os: ubuntu-latest
|
||||
host: darwin-arm64
|
||||
product: server
|
||||
- os: ubuntu-latest
|
||||
host: darwin-x64
|
||||
product: server
|
||||
- os: ubuntu-latest
|
||||
host: win32-x64
|
||||
product: server
|
||||
- os: ubuntu-latest
|
||||
host: win32-arm64
|
||||
product: server
|
||||
- os: ubuntu-latest
|
||||
host: darwin-arm64
|
||||
product: client
|
||||
- os: ubuntu-latest
|
||||
host: darwin-x64
|
||||
product: client
|
||||
- os: ubuntu-latest
|
||||
host: win32-x64
|
||||
product: client
|
||||
- os: ubuntu-latest
|
||||
host: win32-arm64
|
||||
product: client
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build standalone binary
|
||||
run: npm run make:${{ matrix.product }}:${{ matrix.host }}
|
||||
|
||||
- name: List artifact
|
||||
- run: npm ci
|
||||
- run: npm run make:server:${{ matrix.host }}
|
||||
- name: Stage server binary
|
||||
run: |
|
||||
set -e
|
||||
dir="out/${{ matrix.product }}/${{ matrix.host }}"
|
||||
dir="out/server/${{ matrix.host }}"
|
||||
ls -la "$dir"
|
||||
# Normalize name for upload
|
||||
if [ -f "$dir/peardock-${{ matrix.product }}.exe" ]; then
|
||||
cp "$dir/peardock-${{ matrix.product }}.exe" \
|
||||
"peardock-${{ matrix.product }}-${{ matrix.host }}.exe"
|
||||
if [ -f "$dir/peardock-server.exe" ]; then
|
||||
cp "$dir/peardock-server.exe" "peardock-server-${{ matrix.host }}.exe"
|
||||
else
|
||||
cp "$dir/peardock-${{ matrix.product }}" \
|
||||
"peardock-${{ matrix.product }}-${{ matrix.host }}"
|
||||
cp "$dir/peardock-server" "peardock-server-${{ matrix.host }}"
|
||||
fi
|
||||
ls -la peardock-${{ matrix.product }}-${{ matrix.host }}*
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: peardock-${{ matrix.product }}-${{ matrix.host }}
|
||||
path: peardock-${{ matrix.product }}-${{ matrix.host }}*
|
||||
name: peardock-server-${{ matrix.host }}
|
||||
path: peardock-server-${{ matrix.host }}*
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
build-client:
|
||||
name: client / ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
platform: linux
|
||||
arch: x64
|
||||
host: linux-x64
|
||||
- os: ubuntu-latest
|
||||
platform: linux
|
||||
arch: arm64
|
||||
host: linux-arm64
|
||||
# macOS / Windows need matching runners for native Electron rebuild
|
||||
- os: macos-latest
|
||||
platform: darwin
|
||||
arch: arm64
|
||||
host: darwin-arm64
|
||||
- os: windows-latest
|
||||
platform: win32
|
||||
arch: x64
|
||||
host: win32-x64
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Electron Forge make (full Pear GUI)
|
||||
run: npx electron-forge make --platform ${{ matrix.platform }} --arch ${{ matrix.arch }}
|
||||
- name: Stage client artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p staged
|
||||
# Prefer AppImage / zip / dir from forge make + package output
|
||||
if compgen -G "out/make/**/*.AppImage" > /dev/null; then
|
||||
cp out/make/**/*.AppImage "staged/peardock-client-${{ matrix.host }}.AppImage" || true
|
||||
# flatten globs
|
||||
find out/make -name '*.AppImage' -exec cp {} "staged/peardock-client-${{ matrix.host }}.AppImage" \;
|
||||
fi
|
||||
if compgen -G "out/make/**/*.zip" > /dev/null; then
|
||||
find out/make -name '*.zip' -exec cp {} "staged/peardock-client-${{ matrix.host }}.zip" \;
|
||||
fi
|
||||
if compgen -G "out/make/**/*.deb" > /dev/null; then
|
||||
find out/make -name '*.deb' -exec cp {} "staged/peardock-client-${{ matrix.host }}.deb" \;
|
||||
fi
|
||||
# Packaged app directory (always present after package/make)
|
||||
dir=$(find out -maxdepth 1 -type d -name 'peardock-*' | head -1)
|
||||
if [ -n "$dir" ] && [ ! -f "staged/peardock-client-${{ matrix.host }}.zip" ]; then
|
||||
(cd out && zip -ry "../staged/peardock-client-${{ matrix.host }}.zip" "$(basename "$dir")")
|
||||
fi
|
||||
ls -la staged/
|
||||
test -n "$(ls -A staged)"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: peardock-client-${{ matrix.host }}
|
||||
path: staged/*
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
publish:
|
||||
name: Publish rolling release
|
||||
needs: [build]
|
||||
needs: [build-server, build-client]
|
||||
runs-on: ubuntu-latest
|
||||
# Only publish from the default branch pushes / manual dispatch
|
||||
if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Stage binaries for release
|
||||
- name: Stage for release script
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p out/server out/client dist
|
||||
# Flatten artifact dirs into out/{product}/{host}/peardock-*
|
||||
find artifacts -type f -name 'peardock-*' | while read -r f; do
|
||||
base="$(basename "$f")"
|
||||
# peardock-server-linux-x64[.exe]
|
||||
if [[ "$base" =~ ^peardock-(server|client)-([a-z0-9]+-[a-z0-9]+)(\.exe)?$ ]]; then
|
||||
product="${BASH_REMATCH[1]}"
|
||||
host="${BASH_REMATCH[2]}"
|
||||
ext="${BASH_REMATCH[3]}"
|
||||
mkdir -p "out/${product}/${host}"
|
||||
cp -a "$f" "out/${product}/${host}/peardock-${product}${ext}"
|
||||
echo "staged $base → out/${product}/${host}/"
|
||||
mkdir -p out/server out/client dist/rolling-stage
|
||||
find artifacts -type f | sort
|
||||
# Servers
|
||||
find artifacts -type f -name 'peardock-server-*' | while read -r f; do
|
||||
base=$(basename "$f")
|
||||
# peardock-server-linux-x64
|
||||
host=${base#peardock-server-}
|
||||
host=${host%.exe}
|
||||
mkdir -p "out/server/$host"
|
||||
if [[ "$base" == *.exe ]]; then
|
||||
cp "$f" "out/server/$host/peardock-server.exe"
|
||||
else
|
||||
echo "skip unrecognized: $base"
|
||||
cp "$f" "out/server/$host/peardock-server"
|
||||
fi
|
||||
done
|
||||
# Clients — copy into dist stage with original names
|
||||
find artifacts -type f \( -name 'peardock-client-*' -o -name '*.AppImage' -o -name '*.zip' -o -name '*.deb' \) | while read -r f; do
|
||||
cp -a "$f" dist/rolling-stage/
|
||||
done
|
||||
find out -type f | sort
|
||||
ls -la artifacts || true
|
||||
|
||||
ls -la dist/rolling-stage/ || true
|
||||
- name: Publish to Gitea (rolling)
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
@@ -163,10 +176,11 @@ jobs:
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
RELEASE_TAG: ${{ env.RELEASE_TAG }}
|
||||
ARTIFACT_DIR: ${{ github.workspace }}/out
|
||||
CLIENT_STAGE: ${{ github.workspace }}/dist/rolling-stage
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${RELEASE_TOKEN:-}" ]; then
|
||||
echo "ERROR: secret RELEASE_TOKEN is not set on this repository"
|
||||
echo "ERROR: secret RELEASE_TOKEN is not set"
|
||||
exit 1
|
||||
fi
|
||||
export GITEA_URL="${GITEA_URL_SECRET:-${GITHUB_SERVER_URL}}"
|
||||
|
||||
@@ -76,22 +76,33 @@ pear release .
|
||||
pear run pear://<your-app-key>
|
||||
```
|
||||
|
||||
### Standalone Bare binaries
|
||||
### Standalone binaries
|
||||
|
||||
Self-contained ELFs/Mach-O/PE with **all modules and native addons embedded**
|
||||
([bare-build --standalone](https://github.com/holepunchto/bare-build) pattern):
|
||||
| Binary | Kind | Command |
|
||||
|--------|------|---------|
|
||||
| **peardock-server** | Bare standalone (all modules embedded) | `npm run make:server:linux-x64` |
|
||||
| **peardock-client** | **Full Pear GUI** (Electron; same UI as `pear run`) | `npm run make:client` |
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run make:server:linux-x64 # → out/server/linux-x64/peardock-server
|
||||
npm run make:client:linux-x64 # → out/client/linux-x64/peardock-client
|
||||
npm run make:server:linux-x64
|
||||
# → out/server/linux-x64/peardock-server
|
||||
|
||||
npm run make:client
|
||||
# → out/peardock-linux-x64/peardock-client (+ AppImage/zip under out/make/)
|
||||
|
||||
./out/server/linux-x64/peardock-server
|
||||
./out/client/linux-x64/peardock-client --connect <server-public-key>
|
||||
./out/peardock-linux-x64/peardock-client # full desktop app
|
||||
```
|
||||
|
||||
Gitea CI publishes a **rolling** release on every `main` push using secret
|
||||
`RELEASE_TOKEN`. See [docs/RELEASE.md](docs/RELEASE.md).
|
||||
Dev UI (unchanged):
|
||||
|
||||
```bash
|
||||
npm run dev # pear run -d .
|
||||
npm run start:client # Electron shell of the same GUI
|
||||
```
|
||||
|
||||
Gitea CI publishes a **rolling** release (`RELEASE_TOKEN`). See [docs/RELEASE.md](docs/RELEASE.md).
|
||||
|
||||
---
|
||||
|
||||
|
||||
+8
-142
@@ -1,146 +1,12 @@
|
||||
/**
|
||||
* Bare standalone peardock client agent.
|
||||
* peardock-client entry — full Pear GUI application.
|
||||
*
|
||||
* Built with bare-build / scripts/bare-standalone.cjs:
|
||||
* npm run make:client:linux-x64
|
||||
* Preferred run modes:
|
||||
* 1) Electron packaged binary (CI / make:client): peardock-client
|
||||
* 2) Electron dev: npm run start:client
|
||||
* 3) Pear platform (pear-electron + bridge): npm run dev / pear run -d .
|
||||
*
|
||||
* What this binary is:
|
||||
* - Self-contained Bare ELF with embedded JS graph + native addons
|
||||
* - Local Holesail client control plane (real `holesail` package)
|
||||
* - Optional headless HyperDHT connect for health/RPC smoke tests
|
||||
*
|
||||
* What this binary is not:
|
||||
* - The full pear-electron GUI. Desktop UI still uses:
|
||||
* pear run -d .
|
||||
* (see hello-pear-electron for Electron-forge installers later)
|
||||
*
|
||||
* Usage:
|
||||
* peardock-client
|
||||
* peardock-client --connect <64-hex-server-key>
|
||||
* peardock-client --storage /var/lib/peardock-client
|
||||
* This module is the Pear platform entry (same as package "main" / index.js).
|
||||
* Electron packaging uses electron/main.cjs as the process entry instead.
|
||||
*/
|
||||
/* global Bare */
|
||||
|
||||
const isBare = typeof globalThis.Bare !== 'undefined'
|
||||
|
||||
if (isBare) {
|
||||
try {
|
||||
await import('bare-node-runtime/global')
|
||||
} catch {
|
||||
// optional
|
||||
}
|
||||
}
|
||||
|
||||
import process from 'process'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
import bareControl from '../client/holesailBareControl.cjs'
|
||||
import { PearDockConnection } from '../client/connection.js'
|
||||
|
||||
function parseArgs(argv) {
|
||||
const out = { connect: null, storage: null, help: false }
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '--help' || a === '-h') out.help = true
|
||||
else if (a === '--connect') out.connect = argv[++i]
|
||||
else if (a === '--storage') out.storage = argv[++i]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const argv = isBare
|
||||
? globalThis.Bare.argv.slice(2)
|
||||
: process.argv.slice(2)
|
||||
const args = parseArgs(argv)
|
||||
|
||||
if (args.help) {
|
||||
console.log(`peardock-client — Bare standalone agent
|
||||
|
||||
Usage:
|
||||
peardock-client [--storage <dir>] [--connect <server-public-key>]
|
||||
|
||||
Options:
|
||||
--storage <dir> State directory (default: OS temp / peardock-client)
|
||||
--connect <key> Dial HyperDHT server and print handshake, then keep running
|
||||
--help Show this help
|
||||
|
||||
Full desktop GUI:
|
||||
pear run -d . # from the peardock source tree with pear-electron
|
||||
`)
|
||||
if (isBare) Bare.exit(0)
|
||||
else process.exit(0)
|
||||
}
|
||||
|
||||
const storage =
|
||||
args.storage ||
|
||||
path.join(os.tmpdir(), 'peardock-client')
|
||||
|
||||
const sep = storage.includes('\\') ? '\\' : '/'
|
||||
const statePath = storage.endsWith(sep)
|
||||
? `${storage}peardock-holesail-local.json`
|
||||
: `${storage}${sep}peardock-holesail-local.json`
|
||||
|
||||
console.log('peardock-client (Bare standalone agent)')
|
||||
console.log(` storage: ${storage}`)
|
||||
|
||||
let holesailControl = null
|
||||
try {
|
||||
const start = bareControl.start || bareControl.default?.start
|
||||
if (typeof start !== 'function') throw new Error('holesailBareControl missing start()')
|
||||
holesailControl = await start({ statePath })
|
||||
console.log(` holesail control: ${holesailControl.baseUrl}`)
|
||||
console.log(` state: ${statePath}`)
|
||||
} catch (err) {
|
||||
console.error(' holesail control failed:', err.message || err)
|
||||
}
|
||||
|
||||
/** @type {import('../client/connection.js').PearDockConnection|null} */
|
||||
let conn = null
|
||||
if (args.connect) {
|
||||
try {
|
||||
conn = new PearDockConnection(args.connect)
|
||||
await conn.connect()
|
||||
console.log(` connected: ${conn.id}… role=${conn.role || '?'}`)
|
||||
try {
|
||||
const pong = await conn.ping()
|
||||
console.log(' ping:', JSON.stringify(pong))
|
||||
} catch (e) {
|
||||
console.log(' ping failed:', e.message || e)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(' connect failed:', err.message || err)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('')
|
||||
console.log('Agent running. Ctrl+C to stop.')
|
||||
console.log('GUI: install Pear and run `pear run -d .` from the peardock tree.')
|
||||
|
||||
const shutdown = async (code = 0) => {
|
||||
try {
|
||||
await holesailControl?.close?.()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
await conn?.close?.()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
if (isBare) Bare.exit(code)
|
||||
else process.exit(code)
|
||||
}
|
||||
|
||||
try {
|
||||
process.on?.('SIGINT', () => shutdown(130))
|
||||
process.on?.('SIGTERM', () => shutdown(143))
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
if (isBare) {
|
||||
try {
|
||||
Bare.on?.('suspend', () => {})
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
await import('../index.js')
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
@@ -21,7 +21,8 @@ let controlCache = null
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isPearGui() {
|
||||
return Boolean(globalThis.Pear?.config)
|
||||
// pear run (pear-electron) and Electron peardock-client both set Pear.config.storage
|
||||
return Boolean(globalThis.Pear?.config?.storage)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+67
-144
@@ -1,194 +1,117 @@
|
||||
# peardock release process
|
||||
|
||||
## Standalone Bare binaries (recommended)
|
||||
## Two products
|
||||
|
||||
peardock ships **two separate self-contained binaries** built with the Holepunch
|
||||
stack (`bare-pack` + `bare-build --standalone`):
|
||||
| Artifact | What it is | Build |
|
||||
|----------|------------|--------|
|
||||
| **peardock-server** | Bare standalone daemon (HyperDHT + Docker) | `bare-pack` + `bare-build --standalone` |
|
||||
| **peardock-client** | **Full Pear GUI app** (same UI as `pear run -d .`) | Electron Forge (hello-pear-electron pattern) |
|
||||
|
||||
| Binary | Role |
|
||||
|--------|------|
|
||||
| `peardock-server-<host>` | HyperDHT Docker control plane (dockerode over Unix socket) |
|
||||
| `peardock-client-<host>` | Bare agent: Holesail local control + optional headless HyperDHT connect |
|
||||
---
|
||||
|
||||
Each binary embeds the full JS module graph and native addons (no `node_modules`
|
||||
on the target host). Pattern matches [hello-pear-bare](https://github.com/holepunchto/hello-pear-bare)
|
||||
and [bare-build](https://github.com/holepunchto/bare-build).
|
||||
## peardock-server (Bare binary)
|
||||
|
||||
### Local build
|
||||
Self-contained ELF/Mach-O/PE with the full JS graph and native addons embedded.
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
# Native host (server + client)
|
||||
npm run make
|
||||
|
||||
# Explicit targets
|
||||
npm run make:server:linux-x64
|
||||
npm run make:client:linux-x64
|
||||
npm run make:all # all desktop hosts × both products
|
||||
```
|
||||
# → out/server/linux-x64/peardock-server
|
||||
|
||||
Outputs:
|
||||
|
||||
```
|
||||
out/server/<host>/peardock-server[.exe]
|
||||
out/client/<host>/peardock-client[.exe]
|
||||
./out/server/linux-x64/peardock-server
|
||||
# Identity: PEARDOCK_HOME or next to the binary (.env)
|
||||
```
|
||||
|
||||
Hosts: `linux-x64`, `linux-arm64`, `darwin-arm64`, `darwin-x64`, `win32-x64`, `win32-arm64`.
|
||||
|
||||
### Why `scripts/bare-standalone.cjs` instead of plain `bare-build`?
|
||||
|
||||
`bare-build` 1.x packs with Bare resolution but does **not** expose
|
||||
`bare-pack --imports`. Node packages such as `dockerode` need
|
||||
`bare-node-runtime/imports.json` so builtins (`events`, `stream`, …) map to
|
||||
`bare-*`. The peardock builder:
|
||||
|
||||
1. `bare-pack` with bare-node-runtime imports + small shims/stubs
|
||||
2. Embeds the bundle into a portable Bare runtime ELF/Mach-O/PE (same as
|
||||
`bare-build --standalone`)
|
||||
|
||||
Shims under `build/`:
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `shims/http.cjs` | Unix `socketPath` for Docker (bare-http1 is TCP-only) |
|
||||
| `shims/url.cjs` | Legacy `url.resolve` / `parse` for docker-modem |
|
||||
| `stubs/ssh2.cjs` | docker-modem optional SSH (local socket only) |
|
||||
| `stubs/grpc-js.cjs` | dockerode BuildKit gRPC sessions (not used for Engine API) |
|
||||
|
||||
### Run server binary
|
||||
|
||||
```bash
|
||||
# Identity lives next to the binary by default (.env)
|
||||
./out/server/linux-x64/peardock-server
|
||||
|
||||
# Or point at a home directory
|
||||
PEARDOCK_HOME=/var/lib/peardock ./peardock-server
|
||||
# / PEARDOCK_ENV=/etc/peardock.env
|
||||
```
|
||||
|
||||
Requires: Docker Engine socket (`/var/run/docker.sock`) and network for HyperDHT.
|
||||
|
||||
### Run client binary (agent)
|
||||
|
||||
```bash
|
||||
./out/client/linux-x64/peardock-client
|
||||
./out/client/linux-x64/peardock-client --connect <server-public-key-hex>
|
||||
```
|
||||
|
||||
Full **desktop GUI** still uses Pear + pear-electron:
|
||||
|
||||
```bash
|
||||
npm run dev # pear run -d .
|
||||
```
|
||||
|
||||
(Electron-forge installers can follow the [hello-pear-electron](https://github.com/holepunchto/hello-pear-electron)
|
||||
pattern later.)
|
||||
Builder: `scripts/bare-standalone.cjs` (passes `bare-node-runtime` imports + Docker socket shims).
|
||||
|
||||
---
|
||||
|
||||
## Gitea CI — rolling release (`RELEASE_TOKEN`)
|
||||
## peardock-client (full Pear GUI)
|
||||
|
||||
The desktop app is the **same** peardock UI as development (`index.html` + `app.js` + HyperDHT client + Holesail).
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Pear platform (recommended for day-to-day UI work)
|
||||
npm run dev # pear run -d . → index.js (pear-electron + bridge)
|
||||
|
||||
# Electron shell (what the released client binary uses)
|
||||
npm run start:client # electron electron/main.cjs
|
||||
```
|
||||
|
||||
### Package / make
|
||||
|
||||
```bash
|
||||
npm run package:client # → out/peardock-<platform>-<arch>/peardock-client
|
||||
npm run make:client # zip / deb / AppImage under out/make/
|
||||
npm run make:client:linux-x64
|
||||
```
|
||||
|
||||
Architecture:
|
||||
|
||||
- `electron/main.cjs` — window, local static server, Holesail control, Pear polyfill
|
||||
- `electron/preload.cjs` — `globalThis.Pear.config.storage` etc. for UI + holesailLocal
|
||||
- App entry remains `app.js` (Node ESM via dynamic `import(fileURL)` so npm deps resolve)
|
||||
- Packaged `package.json` main rewritten to `electron/main.cjs` (source tree keeps `main: index.js` for `pear run`)
|
||||
|
||||
Pattern: [hello-pear-electron](https://github.com/holepunchto/hello-pear-electron) + peardock’s existing pear-electron UI.
|
||||
|
||||
---
|
||||
|
||||
## Gitea CI rolling release
|
||||
|
||||
Workflow: `.gitea/workflows/release-rolling.yml`
|
||||
|
||||
### Secret
|
||||
| Secret | Purpose |
|
||||
|--------|---------|
|
||||
| `RELEASE_TOKEN` | Gitea PAT with release write |
|
||||
| `GITEA_URL` | Optional forge base URL |
|
||||
|
||||
In the Gitea repo **Settings → Secrets**:
|
||||
On push to `main`:
|
||||
|
||||
| Name | Value |
|
||||
|------|--------|
|
||||
| `RELEASE_TOKEN` | Personal access token with **repository** write (create/delete releases + upload assets) |
|
||||
1. Matrix-build **servers** (Bare) for all desktop hosts
|
||||
2. Matrix-build **clients** (Electron Forge) on Linux/macOS/Windows runners
|
||||
3. Publish/replace tag **`rolling`** with all assets + `SHA256SUMS`
|
||||
|
||||
Optional:
|
||||
|
||||
| Name | Value |
|
||||
|------|--------|
|
||||
| `GITEA_URL` | Forge base URL if not the same as the Actions host (e.g. `https://git.example.com`) |
|
||||
|
||||
### Behaviour
|
||||
|
||||
1. On every push to `main` / `master` (and manual dispatch), matrix-builds
|
||||
server + client for desktop hosts via `npm run make:<product>:<host>`.
|
||||
2. `publish` job downloads artifacts and runs
|
||||
`scripts/gitea-rolling-release.sh`.
|
||||
3. That script **replaces** the release tagged `rolling` (delete + recreate) and
|
||||
uploads:
|
||||
|
||||
- `peardock-server-<host>[.exe]`
|
||||
- `peardock-client-<host>[.exe]`
|
||||
- `SHA256SUMS`
|
||||
|
||||
### Manual publish (from a machine with artifacts)
|
||||
Manual:
|
||||
|
||||
```bash
|
||||
export RELEASE_TOKEN=...
|
||||
export GITEA_URL=https://your.gitea.host
|
||||
export GITEA_OWNER=snxraven
|
||||
export GITEA_REPO=peardock
|
||||
npm run make:all # or copy CI artifacts into out/
|
||||
export RELEASE_TOKEN=... GITEA_URL=https://your.gitea GITEA_OWNER=... GITEA_REPO=peardock
|
||||
npm run make:server:linux-x64
|
||||
npm run make:client
|
||||
npm run release:rolling
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Source tarball + checksums (legacy / Node install)
|
||||
|
||||
```bash
|
||||
chmod +x scripts/release-checksums.sh
|
||||
./scripts/release-checksums.sh dist/
|
||||
# Optional GPG:
|
||||
GPG_KEY_ID=YOUR_KEY_ID ./scripts/release-checksums.sh dist/
|
||||
```
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `peardock-<ver>-<stamp>.tar.gz` | Source/runtime tree (no node_modules) |
|
||||
| `*.sha256` | SHA-256 checksum |
|
||||
| `*.asc` | Detached GPG signature (if keyed) |
|
||||
|
||||
```bash
|
||||
tar -xzf peardock-*.tar.gz -C /opt/peardock
|
||||
cd /opt/peardock && npm ci --omit=dev
|
||||
# Prefer binary: copy peardock-server into place and set ExecStart
|
||||
cp deploy/peardock.service /etc/systemd/system/
|
||||
systemctl enable --now peardock
|
||||
```
|
||||
|
||||
Update `deploy/peardock.service` `ExecStart` to the standalone binary when using
|
||||
Bare builds:
|
||||
## systemd (server)
|
||||
|
||||
```ini
|
||||
ExecStart=/opt/peardock/peardock-server
|
||||
Environment=PEARDOCK_HOME=/opt/peardock
|
||||
```
|
||||
|
||||
See `deploy/peardock.service`.
|
||||
|
||||
---
|
||||
|
||||
## Pear desktop app (GUI OTA)
|
||||
## Pear OTA (optional)
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
pear stage .
|
||||
pear release .
|
||||
# Distribute pear:// link per Pear docs
|
||||
# pear-install pear://...
|
||||
```
|
||||
|
||||
Electron client can later embed `pear-runtime` OTA like hello-pear-electron (`upgrade` field in package.json).
|
||||
|
||||
---
|
||||
|
||||
## Certification soak (24h)
|
||||
## Source tarball (legacy Node server)
|
||||
|
||||
```bash
|
||||
# Terminal 1
|
||||
./out/server/linux-x64/peardock-server
|
||||
# or: npm run server
|
||||
|
||||
# Terminal 2
|
||||
SOAK_DURATION_MS=86400000 npm run soak
|
||||
./scripts/release-checksums.sh dist/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Encoding profile
|
||||
|
||||
Handshake returns `schemaVersion` and features. Default encoding remains JSON
|
||||
(`shared/encodings.js`); binary bulk uses `binaryStream*` + `push:binaryChunk`.
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* peardock desktop client — Electron shell (full Pear GUI app).
|
||||
*
|
||||
* Architecture (hello-pear-electron + peardock pear-electron layout):
|
||||
* - Electron owns the window chrome
|
||||
* - A Bare worker (pear-runtime) runs holesail control (require.addon)
|
||||
* - Renderer loads the real peardock UI (index.html + app.js) with Node integration
|
||||
* so HyperDHT / protomux client modules work like under pear run
|
||||
* - Pear polyfill provides config.storage, exit, teardown for UI code
|
||||
*
|
||||
* Dev: npm run start:client
|
||||
* Pack: npm run make:client
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
const { app, BrowserWindow, ipcMain, session } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const os = require('os')
|
||||
const http = require('http')
|
||||
const { pathToFileURL } = require('url')
|
||||
|
||||
const pkg = require('../package.json')
|
||||
const appName = pkg.productName || pkg.name || 'peardock'
|
||||
|
||||
// ---- CLI flags (paparam optional) ----
|
||||
const argv = app.isPackaged ? process.argv.slice(1) : process.argv.slice(2)
|
||||
let storageOverride = null
|
||||
let noSandbox = false
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
if (argv[i] === '--storage') storageOverride = argv[++i]
|
||||
if (argv[i] === '--no-sandbox') noSandbox = true
|
||||
}
|
||||
if (noSandbox || process.platform === 'linux') {
|
||||
app.commandLine.appendSwitch('no-sandbox')
|
||||
}
|
||||
|
||||
if (storageOverride) {
|
||||
app.setPath('userData', storageOverride)
|
||||
}
|
||||
|
||||
const userData = () => app.getPath('userData')
|
||||
const storageDir = () => path.join(userData(), 'storage')
|
||||
|
||||
/** @type {import('http').Server|null} */
|
||||
let staticServer = null
|
||||
/** @type {number} */
|
||||
let staticPort = 0
|
||||
/** @type {{ close?: () => Promise<void> }|null} */
|
||||
let holesailControl = null
|
||||
|
||||
function ensureDir(p) {
|
||||
fs.mkdirSync(p, { recursive: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal static file server rooted at the app directory.
|
||||
* Serves the full peardock UI tree (index.html, app.js, client/, ui/, …).
|
||||
*
|
||||
* index.html is rewritten so app.js loads via Node ESM (`import(fileURL)`),
|
||||
* which resolves hyperdht etc. from node_modules — same graph as pear run.
|
||||
*/
|
||||
function startStaticServer(rootDir) {
|
||||
const appJsFileUrl = pathToFileURL(path.join(rootDir, 'app.js')).href
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const mime = {
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.js': 'text/javascript; charset=utf-8',
|
||||
'.mjs': 'text/javascript; charset=utf-8',
|
||||
'.cjs': 'text/javascript; charset=utf-8',
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.json': 'application/json',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.ico': 'image/x-icon',
|
||||
'.woff': 'font/woff',
|
||||
'.woff2': 'font/woff2',
|
||||
'.map': 'application/json',
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
try {
|
||||
const u = new URL(req.url || '/', 'http://127.0.0.1')
|
||||
let rel = decodeURIComponent(u.pathname)
|
||||
if (rel === '/') rel = '/index.html'
|
||||
// block path escape
|
||||
const filePath = path.normalize(path.join(rootDir, rel))
|
||||
if (!filePath.startsWith(rootDir)) {
|
||||
res.writeHead(403)
|
||||
res.end('Forbidden')
|
||||
return
|
||||
}
|
||||
if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
|
||||
res.writeHead(404)
|
||||
res.end('Not found')
|
||||
return
|
||||
}
|
||||
const ext = path.extname(filePath).toLowerCase()
|
||||
|
||||
// Full Pear GUI bootstrap: Node dynamic import of app.js (nodeIntegration)
|
||||
if (rel === '/index.html' || filePath.endsWith(`${path.sep}index.html`)) {
|
||||
let html = fs.readFileSync(filePath, 'utf8')
|
||||
// Remove browser ESM entry (cannot resolve npm packages over HTTP)
|
||||
html = html.replace(
|
||||
/<script\s+type=["']module["']\s+src=["'][^"']*app\.js["']\s*>\s*<\/script>/i,
|
||||
''
|
||||
)
|
||||
const boot = `
|
||||
<script>
|
||||
(function () {
|
||||
const href = ${JSON.stringify(appJsFileUrl)};
|
||||
console.log('[peardock] loading full app via Node ESM:', href);
|
||||
import(href).catch(function (err) {
|
||||
console.error('[peardock] failed to load app.js', err);
|
||||
var el = document.createElement('pre');
|
||||
el.style.cssText = 'color:#f88;padding:2rem;white-space:pre-wrap;font:14px monospace';
|
||||
el.textContent = 'peardock failed to start:\\n' + (err && err.stack ? err.stack : err);
|
||||
document.body.appendChild(el);
|
||||
});
|
||||
})();
|
||||
</script>`
|
||||
if (html.includes('</body>')) {
|
||||
html = html.replace('</body>', boot + '\n</body>')
|
||||
} else {
|
||||
html += boot
|
||||
}
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
'Cache-Control': 'no-cache',
|
||||
})
|
||||
res.end(html)
|
||||
return
|
||||
}
|
||||
|
||||
res.writeHead(200, {
|
||||
'Content-Type': mime[ext] || 'application/octet-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
})
|
||||
fs.createReadStream(filePath).pipe(res)
|
||||
} catch (err) {
|
||||
res.writeHead(500)
|
||||
res.end(String(err && err.message ? err.message : err))
|
||||
}
|
||||
})
|
||||
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const addr = server.address()
|
||||
staticPort = typeof addr === 'object' && addr ? addr.port : 0
|
||||
staticServer = server
|
||||
resolve(staticPort)
|
||||
})
|
||||
server.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject Pear polyfill + bootstrap Node ESM load of app.js before other scripts.
|
||||
* Pear-electron uses pear-bridge/script-linker; here Electron provides Node and we
|
||||
* polyfill the small Pear surface peardock needs.
|
||||
*/
|
||||
function buildPreloadPath() {
|
||||
return path.join(__dirname, 'preload.cjs')
|
||||
}
|
||||
|
||||
async function startHolesailControl() {
|
||||
ensureDir(storageDir())
|
||||
const statePath = path.join(storageDir(), 'peardock-holesail-local.json')
|
||||
try {
|
||||
// Prefer CJS control (works under Node main with native addons when available)
|
||||
const bareControl = require('../client/holesailBareControl.cjs')
|
||||
const start = bareControl.start || bareControl.default?.start
|
||||
if (typeof start !== 'function') throw new Error('holesailBareControl missing start()')
|
||||
holesailControl = await start({ statePath })
|
||||
console.log(`[peardock] Holesail local control at ${holesailControl.baseUrl}`)
|
||||
return holesailControl
|
||||
} catch (err) {
|
||||
console.error('[peardock] Holesail local control failed (tunnels UI may be limited):', err.message || err)
|
||||
// Write a stub endpoint file so UI can detect absence cleanly
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
statePath,
|
||||
JSON.stringify({ error: String(err.message || err), at: new Date().toISOString() }, null, 2)
|
||||
)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
const win = new BrowserWindow({
|
||||
width: pkg.pear?.gui?.width || 1280,
|
||||
height: pkg.pear?.gui?.height || 800,
|
||||
backgroundColor: pkg.pear?.gui?.backgroundColor || '#0a0c10',
|
||||
title: appName,
|
||||
webPreferences: {
|
||||
preload: buildPreloadPath(),
|
||||
// Full peardock UI imports HyperDHT etc. as ESM node packages — same as pear UI.
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
sandbox: false,
|
||||
spellcheck: false,
|
||||
webSecurity: true,
|
||||
},
|
||||
show: false,
|
||||
})
|
||||
|
||||
win.once('ready-to-show', () => win.show())
|
||||
|
||||
const url = `http://127.0.0.1:${staticPort}/index.html`
|
||||
win.loadURL(url).catch((err) => {
|
||||
console.error('Failed to load UI:', err)
|
||||
app.quit()
|
||||
})
|
||||
|
||||
win.webContents.on('did-fail-load', (_e, code, desc) => {
|
||||
console.error('did-fail-load', code, desc)
|
||||
})
|
||||
|
||||
return win
|
||||
}
|
||||
|
||||
// IPC for Pear polyfill
|
||||
ipcMain.on('peardock:get-pear-config', (evt) => {
|
||||
evt.returnValue = {
|
||||
storage: storageDir(),
|
||||
name: appName,
|
||||
version: pkg.version,
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('peardock:exit', () => {
|
||||
app.quit()
|
||||
})
|
||||
|
||||
const teardownFns = []
|
||||
ipcMain.on('peardock:teardown-register', () => {
|
||||
// Renderer registers via preload bridge; actual teardown on before-quit
|
||||
})
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
ensureDir(storageDir())
|
||||
|
||||
// App root: project root in dev; Electron app path (app.asar or app/) when packaged
|
||||
const appRoot = app.isPackaged ? app.getAppPath() : path.resolve(__dirname, '..')
|
||||
|
||||
await startStaticServer(appRoot)
|
||||
await startHolesailControl()
|
||||
|
||||
// Expose storage path for renderer polyfill via process.env (nodeIntegration)
|
||||
process.env.PEARDOCK_STORAGE = storageDir()
|
||||
process.env.PEARDOCK_APP_NAME = appName
|
||||
|
||||
createWindow()
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
|
||||
app.on('before-quit', async (e) => {
|
||||
try {
|
||||
await holesailControl?.close?.()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
if (staticServer) {
|
||||
try {
|
||||
staticServer.close()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
for (const fn of teardownFns) {
|
||||
try {
|
||||
await fn()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Identity for single-instance
|
||||
const gotLock = app.requestSingleInstanceLock()
|
||||
if (!gotLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', () => {
|
||||
const wins = BrowserWindow.getAllWindows()
|
||||
if (wins[0]) {
|
||||
if (wins[0].isMinimized()) wins[0].restore()
|
||||
wins[0].focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Preload + Pear polyfill for the full peardock GUI under Electron.
|
||||
* Runs with contextIsolation: false / nodeIntegration: true so app.js ESM
|
||||
* can import HyperDHT packages (pear-electron UI equivalent).
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
const { ipcRenderer } = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const os = require('os')
|
||||
|
||||
const cfg = ipcRenderer.sendSync('peardock:get-pear-config') || {}
|
||||
const storage =
|
||||
cfg.storage ||
|
||||
process.env.PEARDOCK_STORAGE ||
|
||||
path.join(os.homedir(), '.config', 'peardock', 'storage')
|
||||
|
||||
try {
|
||||
fs.mkdirSync(storage, { recursive: true })
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const teardownHooks = []
|
||||
|
||||
/** Pear surface used by peardock index/holesailLocal/UI */
|
||||
const Pear = {
|
||||
config: {
|
||||
storage,
|
||||
name: cfg.name || 'peardock',
|
||||
version: cfg.version || '0.0.0',
|
||||
},
|
||||
// alias some runtimes use
|
||||
app: {
|
||||
storage,
|
||||
name: cfg.name || 'peardock',
|
||||
},
|
||||
exit(code = 0) {
|
||||
try {
|
||||
ipcRenderer.invoke('peardock:exit', code)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
teardown(fn) {
|
||||
if (typeof fn === 'function') teardownHooks.push(fn)
|
||||
},
|
||||
constructor: {
|
||||
IPC: null,
|
||||
UI: null,
|
||||
CUTOVER: false,
|
||||
},
|
||||
}
|
||||
|
||||
globalThis.Pear = Pear
|
||||
// Also on window for any non-module scripts
|
||||
try {
|
||||
window.Pear = Pear
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// pear-ctrl custom element is a no-op stub outside pear platform chrome
|
||||
try {
|
||||
if (typeof customElements !== 'undefined' && !customElements.get('pear-ctrl')) {
|
||||
class PearCtrl extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.style.display = 'none'
|
||||
}
|
||||
}
|
||||
customElements.define('pear-ctrl', PearCtrl)
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
for (const fn of teardownHooks) {
|
||||
try {
|
||||
const r = fn()
|
||||
if (r && typeof r.then === 'function') r.catch(() => {})
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
console.log('[peardock] Pear polyfill ready, storage=', storage)
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Electron Forge config for peardock-client (full Pear GUI).
|
||||
* Pattern: holepunchto/hello-pear-electron
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const pkg = require('./package.json')
|
||||
const appName = pkg.productName || pkg.name || 'peardock'
|
||||
|
||||
/** Files/dirs to ignore when packaging (keep client UI + deps, drop server bulk if possible) */
|
||||
const ignore = [
|
||||
/^\/\.git($|\/)/,
|
||||
/^\/\.gitea($|\/)/,
|
||||
/^\/\.github($|\/)/,
|
||||
/^\/out($|\/)/,
|
||||
/^\/dist($|\/)/,
|
||||
/^\/screenshots($|\/)/,
|
||||
/^\/test($|\/)/,
|
||||
/^\/docs($|\/)/,
|
||||
// Server is a separate bare binary — still include shared protocol used by client
|
||||
// Keep server/ out of client package to slim the app
|
||||
/^\/server($|\/)/,
|
||||
/^\/deploy($|\/)/,
|
||||
/^\/build\/stubs($|\/)/,
|
||||
/^\/build\/shims($|\/)/,
|
||||
/\.md$/,
|
||||
/^\/peardock-.*\.json$/,
|
||||
/^\/\.env$/,
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
packagerConfig: {
|
||||
name: appName,
|
||||
executableName: 'peardock-client',
|
||||
appBundleId: 'com.peardock.app',
|
||||
icon: fs.existsSync(path.join(__dirname, 'build', 'icon.png'))
|
||||
? path.join(__dirname, 'build', 'icon')
|
||||
: undefined,
|
||||
// Unpack native addons so HyperDHT / holesail work
|
||||
asar: {
|
||||
unpack: '**/{*.node,*.bare,prebuilds/**}',
|
||||
},
|
||||
ignore: (file) => {
|
||||
if (!file) return false
|
||||
return ignore.some((re) => re.test(file))
|
||||
},
|
||||
derefSymlinks: true,
|
||||
prune: true,
|
||||
},
|
||||
|
||||
rebuildConfig: {},
|
||||
|
||||
makers: [
|
||||
{
|
||||
name: '@electron-forge/maker-zip',
|
||||
platforms: ['darwin', 'linux', 'win32'],
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-deb',
|
||||
platforms: ['linux'],
|
||||
config: {
|
||||
options: {
|
||||
maintainer: 'peardock',
|
||||
homepage: 'https://github.com/snxraven/peardock',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'pear-electron-forge-maker-appimage',
|
||||
platforms: ['linux'],
|
||||
config: {
|
||||
icons: fs.existsSync(path.join(__dirname, 'build', 'icon.png'))
|
||||
? [{ file: 'build/icon.png', size: 256 }]
|
||||
: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
plugins: [
|
||||
{
|
||||
name: 'electron-forge-plugin-universal-prebuilds',
|
||||
config: {},
|
||||
},
|
||||
{
|
||||
name: 'electron-forge-plugin-prune-prebuilds',
|
||||
config: {},
|
||||
},
|
||||
],
|
||||
|
||||
hooks: {
|
||||
preMake: async () => {
|
||||
fs.rmSync(path.join(__dirname, 'out', 'make'), { recursive: true, force: true })
|
||||
},
|
||||
/**
|
||||
* Pear keeps package.json "main" as index.js (pear run).
|
||||
* Packaged Electron must boot electron/main.cjs instead.
|
||||
*/
|
||||
packageAfterCopy: async (_forgeConfig, buildPath) => {
|
||||
const pkgPath = path.join(buildPath, 'package.json')
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
||||
pkg.main = 'electron/main.cjs'
|
||||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
|
||||
},
|
||||
},
|
||||
}
|
||||
Generated
+8004
-4
File diff suppressed because it is too large
Load Diff
+55
-39
@@ -500,7 +500,7 @@
|
||||
"sbom:notes": "node -e \"console.log('See docs/SBOM.md')\"",
|
||||
"make": "node scripts/make.cjs both",
|
||||
"make:server": "node scripts/make.cjs server",
|
||||
"make:client": "node scripts/make.cjs client",
|
||||
"make:client": "electron-forge make",
|
||||
"make:all": "node scripts/make.cjs all",
|
||||
"make:server:linux-x64": "node scripts/bare-standalone.cjs --product server --host linux-x64",
|
||||
"make:server:linux-arm64": "node scripts/bare-standalone.cjs --product server --host linux-arm64",
|
||||
@@ -508,31 +508,59 @@
|
||||
"make:server:darwin-x64": "node scripts/bare-standalone.cjs --product server --host darwin-x64",
|
||||
"make:server:win32-x64": "node scripts/bare-standalone.cjs --product server --host win32-x64",
|
||||
"make:server:win32-arm64": "node scripts/bare-standalone.cjs --product server --host win32-arm64",
|
||||
"make:client:linux-x64": "node scripts/bare-standalone.cjs --product client --host linux-x64",
|
||||
"make:client:linux-arm64": "node scripts/bare-standalone.cjs --product client --host linux-arm64",
|
||||
"make:client:darwin-arm64": "node scripts/bare-standalone.cjs --product client --host darwin-arm64",
|
||||
"make:client:darwin-x64": "node scripts/bare-standalone.cjs --product client --host darwin-x64",
|
||||
"make:client:win32-x64": "node scripts/bare-standalone.cjs --product client --host win32-x64",
|
||||
"make:client:win32-arm64": "node scripts/bare-standalone.cjs --product client --host win32-arm64",
|
||||
"make:client:linux-x64": "electron-forge make --platform linux --arch x64",
|
||||
"make:client:linux-arm64": "electron-forge make --platform linux --arch arm64",
|
||||
"make:client:darwin-arm64": "electron-forge make --platform darwin --arch arm64",
|
||||
"make:client:darwin-x64": "electron-forge make --platform darwin --arch x64",
|
||||
"make:client:win32-x64": "electron-forge make --platform win32 --arch x64",
|
||||
"make:client:win32-arm64": "electron-forge make --platform win32 --arch arm64",
|
||||
"release:rolling": "bash scripts/gitea-rolling-release.sh",
|
||||
"start:server:bin": "node bin/peardock-server.mjs",
|
||||
"make:bin": "node scripts/bare-standalone.cjs"
|
||||
"make:bin": "node scripts/bare-standalone.cjs",
|
||||
"start:client": "electron electron/main.cjs",
|
||||
"package:client": "electron-forge package"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"dependencies": {
|
||||
"b4a": "^1.8.1",
|
||||
"bare-abort-controller": "^1.0.0",
|
||||
"bare-assert": "^1.1.0",
|
||||
"bare-async-hooks": "^0.0.0",
|
||||
"bare-buffer": "^3.3.1",
|
||||
"bare-console": "^6.0.1",
|
||||
"bare-crypto": "^1.15.3",
|
||||
"bare-dgram": "^1.0.1",
|
||||
"bare-diagnostics-channel": "^1.1.0",
|
||||
"bare-dns": "^2.1.4",
|
||||
"bare-encoding": "^1.0.0",
|
||||
"bare-events": "^2.9.1",
|
||||
"bare-fetch": "^3.0.0",
|
||||
"bare-fs": "^4.7.4",
|
||||
"bare-http1": "^4.5.7",
|
||||
"bare-https": "^3.0.0",
|
||||
"bare-inspector": "^6.0.1",
|
||||
"bare-module": "^6.1.2",
|
||||
"bare-net": "^2.3.2",
|
||||
"bare-node-runtime": "^1.5.0",
|
||||
"bare-os": "^3.9.3",
|
||||
"bare-path": "^3.1.1",
|
||||
"bare-performance": "^2.0.0",
|
||||
"bare-process": "^4.5.1",
|
||||
"bare-punycode": "^0.0.0",
|
||||
"bare-querystring": "^1.0.0",
|
||||
"bare-stream": "^2.7.0",
|
||||
"bare-string-decoder": "^1.0.0",
|
||||
"bare-subprocess": "^5.2.3",
|
||||
"bare-timers": "^3.0.0",
|
||||
"bare-tls": "^3.0.0",
|
||||
"bare-tty": "^5.0.0",
|
||||
"bare-url": "^2.4.5",
|
||||
"bare-utils": "^1.5.1",
|
||||
"bare-worker": "^4.0.0",
|
||||
"bare-ws": "^3.0.0",
|
||||
"bare-zlib": "^1.3.1",
|
||||
"compact-encoding": "^3.3.0",
|
||||
"dockerode": "^5.0.1",
|
||||
"dotenv": "^17.4.2",
|
||||
@@ -549,39 +577,27 @@
|
||||
"protomux-rpc": "^1.10.0",
|
||||
"safety-catch": "^1.0.3",
|
||||
"which-runtime": "^1.4.0",
|
||||
"z32": "^1.1.0",
|
||||
"bare-assert": "^1.1.0",
|
||||
"bare-async-hooks": "^0.0.0",
|
||||
"bare-buffer": "^3.3.1",
|
||||
"bare-console": "^6.0.1",
|
||||
"bare-dgram": "^1.0.1",
|
||||
"bare-diagnostics-channel": "^1.1.0",
|
||||
"bare-dns": "^2.1.4",
|
||||
"bare-fetch": "^3.0.0",
|
||||
"bare-https": "^3.0.0",
|
||||
"bare-inspector": "^6.0.1",
|
||||
"bare-module": "^6.1.2",
|
||||
"bare-performance": "^2.0.0",
|
||||
"bare-punycode": "^0.0.0",
|
||||
"bare-querystring": "^1.0.0",
|
||||
"bare-stream": "^2.7.0",
|
||||
"bare-string-decoder": "^1.0.0",
|
||||
"bare-timers": "^3.0.0",
|
||||
"bare-tls": "^3.0.0",
|
||||
"bare-tty": "^5.0.0",
|
||||
"bare-utils": "^1.5.1",
|
||||
"bare-worker": "^4.0.0",
|
||||
"bare-ws": "^3.0.0",
|
||||
"bare-zlib": "^1.3.1",
|
||||
"bare-node-runtime": "^1.5.0",
|
||||
"bare-abort-controller": "^1.0.0",
|
||||
"bare-encoding": "^1.0.0"
|
||||
"z32": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"brittle": "^4.1.0",
|
||||
"pear-interface": "^1.1.0",
|
||||
"@electron-forge/cli": "^7.11.2",
|
||||
"@electron-forge/maker-deb": "^7.11.2",
|
||||
"@electron-forge/maker-squirrel": "^7.11.2",
|
||||
"@electron-forge/maker-zip": "^7.11.2",
|
||||
"bare-build": "^1.0.2",
|
||||
"bare-runtime": "1.30.3"
|
||||
"bare-runtime": "1.30.3",
|
||||
"brittle": "^4.1.0",
|
||||
"electron": "^33.4.11",
|
||||
"electron-forge-plugin-prune-prebuilds": "^1.0.1",
|
||||
"electron-forge-plugin-universal-prebuilds": "^1.0.0",
|
||||
"framed-stream": "^1.0.1",
|
||||
"paparam": "^1.10.1",
|
||||
"pear-electron-forge-maker-appimage": "^2.0.0",
|
||||
"pear-interface": "^1.1.0",
|
||||
"pear-runtime": "^1.3.1"
|
||||
},
|
||||
"productName": "peardock"
|
||||
"productName": "peardock",
|
||||
"config": {
|
||||
"forge": "./forge.config.cjs"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,8 @@ const PRODUCTS = {
|
||||
entry: path.join(root, 'bin/peardock-server.mjs'),
|
||||
outPrefix: path.join(root, 'out/server'),
|
||||
},
|
||||
client: {
|
||||
name: 'peardock-client',
|
||||
entry: path.join(root, 'bin/peardock-client.mjs'),
|
||||
outPrefix: path.join(root, 'out/client'),
|
||||
},
|
||||
// peardock-client (full Pear GUI) is built with Electron Forge — not bare-build.
|
||||
// See: npm run make:client / electron-forge make
|
||||
}
|
||||
|
||||
const DEFAULT_DEFER = [
|
||||
@@ -154,8 +151,13 @@ async function embedStandalone(bundle, hosts, name, outDir) {
|
||||
}
|
||||
|
||||
async function buildProduct(productKey, hosts) {
|
||||
if (productKey === 'client') {
|
||||
throw new Error(
|
||||
'peardock-client is the full Pear GUI — build with: npm run make:client (Electron Forge), not bare-standalone'
|
||||
)
|
||||
}
|
||||
const product = PRODUCTS[productKey]
|
||||
if (!product) throw new Error(`Unknown product '${productKey}' (server|client)`)
|
||||
if (!product) throw new Error(`Unknown product '${productKey}' (server)`)
|
||||
|
||||
const imports = loadImports()
|
||||
console.log(
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# Publish / refresh a rolling Gitea release with standalone bare-build binaries.
|
||||
# Publish / refresh a rolling Gitea release.
|
||||
#
|
||||
# Artifacts:
|
||||
# peardock-server-* — Bare standalone (out/server/<host>/)
|
||||
# peardock-client-* — full Pear GUI (Electron: .AppImage / .zip / .deb from CLIENT_STAGE or out/make)
|
||||
#
|
||||
# Required env:
|
||||
# RELEASE_TOKEN Gitea personal access token (repo write + package/release)
|
||||
# GITEA_URL e.g. https://git.example.com (no trailing slash)
|
||||
# GITEA_OWNER e.g. snxraven
|
||||
# GITEA_REPO e.g. peardock
|
||||
# RELEASE_TOKEN, GITEA_URL, GITEA_OWNER, GITEA_REPO
|
||||
#
|
||||
# Optional:
|
||||
# RELEASE_TAG default: rolling
|
||||
# RELEASE_TITLE default: "Rolling release"
|
||||
# ARTIFACT_DIR default: ./out
|
||||
# GITEA_API override full API base (default: $GITEA_URL/api/v1)
|
||||
# RELEASE_TAG (default rolling), ARTIFACT_DIR (default ./out), CLIENT_STAGE
|
||||
#
|
||||
# Usage (after make):
|
||||
# ./scripts/gitea-rolling-release.sh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ARTIFACT_DIR="${ARTIFACT_DIR:-$ROOT/out}"
|
||||
CLIENT_STAGE="${CLIENT_STAGE:-}"
|
||||
RELEASE_TAG="${RELEASE_TAG:-rolling}"
|
||||
RELEASE_TITLE="${RELEASE_TITLE:-Rolling release}"
|
||||
VERSION="$(node -p "require('$ROOT/package.json').version" 2>/dev/null || echo 0.0.0)"
|
||||
@@ -38,54 +35,94 @@ CT="Content-Type: application/json"
|
||||
echo "[release] tag=${RELEASE_TAG} version=${VERSION} commit=${COMMIT} stamp=${STAMP}"
|
||||
echo "[release] api=${API}/repos/${GITEA_OWNER}/${GITEA_REPO}"
|
||||
|
||||
# Collect artifacts: out/{server,client}/<host>/peardock-*
|
||||
mapfile -t FILES < <(find "$ARTIFACT_DIR" -type f \( -name 'peardock-server' -o -name 'peardock-server.exe' -o -name 'peardock-client' -o -name 'peardock-client.exe' \) 2>/dev/null | sort)
|
||||
if [[ ${#FILES[@]} -eq 0 ]]; then
|
||||
echo "[release] no binaries under $ARTIFACT_DIR — run: npm run make:all (or matrix CI builds)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STAGE="$ROOT/dist/rolling-${STAMP}"
|
||||
mkdir -p "$STAGE"
|
||||
|
||||
for f in "${FILES[@]}"; do
|
||||
# out/server/linux-x64/peardock-server → peardock-server-linux-x64
|
||||
# --- Server Bare binaries ---
|
||||
mapfile -t SERVER_FILES < <(find "$ARTIFACT_DIR/server" -type f \( -name 'peardock-server' -o -name 'peardock-server.exe' \) 2>/dev/null | sort || true)
|
||||
for f in "${SERVER_FILES[@]+"${SERVER_FILES[@]}"}"; do
|
||||
[[ -z "${f:-}" ]] && continue
|
||||
rel="${f#"$ARTIFACT_DIR"/}"
|
||||
product="$(echo "$rel" | cut -d/ -f1)" # server|client
|
||||
host="$(echo "$rel" | cut -d/ -f2)" # linux-x64
|
||||
base="$(basename "$f")"
|
||||
base="${base%.exe}"
|
||||
host="$(echo "$rel" | cut -d/ -f2)"
|
||||
ext=""
|
||||
[[ "$f" == *.exe ]] && ext=".exe"
|
||||
dest="${base}-${host}${ext}"
|
||||
dest="peardock-server-${host}${ext}"
|
||||
cp -a "$f" "$STAGE/$dest"
|
||||
echo "[release] staged $dest"
|
||||
done
|
||||
|
||||
# Checksums
|
||||
# --- Client full Pear GUI packages ---
|
||||
collect_clients() {
|
||||
local dir="$1"
|
||||
[[ -d "$dir" ]] || return 0
|
||||
find "$dir" -type f \( \
|
||||
-name 'peardock-client-*' -o \
|
||||
-name '*.AppImage' -o \
|
||||
-name '*.zip' -o \
|
||||
-name '*.deb' -o \
|
||||
-name '*.dmg' -o \
|
||||
-name '*.msix' \
|
||||
\) 2>/dev/null | sort
|
||||
}
|
||||
|
||||
mapfile -t CLIENT_FILES < <({
|
||||
collect_clients "${CLIENT_STAGE}"
|
||||
collect_clients "$ARTIFACT_DIR/make"
|
||||
collect_clients "$ARTIFACT_DIR/client"
|
||||
# Packaged dirs: zip if not already staged
|
||||
find "$ARTIFACT_DIR" -maxdepth 1 -type d -name 'peardock-*' 2>/dev/null | while read -r d; do
|
||||
base=$(basename "$d")
|
||||
zipname="peardock-client-${base#peardock-}.zip"
|
||||
if [[ ! -f "$STAGE/$zipname" ]]; then
|
||||
(cd "$ARTIFACT_DIR" && zip -qry "$STAGE/$zipname" "$base")
|
||||
echo "[release] zipped $zipname" >&2
|
||||
fi
|
||||
done
|
||||
} | sort -u)
|
||||
|
||||
for f in "${CLIENT_FILES[@]+"${CLIENT_FILES[@]}"}"; do
|
||||
[[ -z "${f:-}" || ! -f "$f" ]] && continue
|
||||
dest=$(basename "$f")
|
||||
# Normalize forge names
|
||||
if [[ "$dest" != peardock-client-* ]]; then
|
||||
dest="peardock-client-${dest}"
|
||||
fi
|
||||
cp -a "$f" "$STAGE/$dest"
|
||||
echo "[release] staged $dest"
|
||||
done
|
||||
|
||||
count=$(find "$STAGE" -type f ! -name 'SHA256SUMS' | wc -l)
|
||||
if [[ "$count" -eq 0 ]]; then
|
||||
echo "[release] no artifacts staged — build server (bare) and/or client (electron-forge) first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(
|
||||
cd "$STAGE"
|
||||
sha256sum peardock-* > SHA256SUMS
|
||||
sha256sum peardock-* > SHA256SUMS 2>/dev/null || sha256sum * > SHA256SUMS
|
||||
)
|
||||
|
||||
BODY=$(cat <<EOF
|
||||
Automated **rolling** release of peardock standalone Bare binaries.
|
||||
Automated **rolling** release of peardock.
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Version | \`${VERSION}\` |
|
||||
| Commit | \`${COMMIT}\` |
|
||||
| Built | \`${STAMP}\` |
|
||||
| Tooling | \`bare-build --standalone\` (embeds JS graph + native addons) |
|
||||
|
||||
## Binaries
|
||||
## Artifacts
|
||||
|
||||
- **peardock-server-\<host\>** — HyperDHT Docker control plane
|
||||
- **peardock-client-\<host\>** — Pear/Bare desktop client entry
|
||||
| Product | Tooling | Contents |
|
||||
|---------|---------|----------|
|
||||
| **peardock-server-\<host\>** | \`bare-build --standalone\` | HyperDHT Docker control plane (all JS + addons embedded) |
|
||||
| **peardock-client-\<host\>** | Electron Forge (full Pear GUI) | Desktop UI + HyperDHT client + Holesail control |
|
||||
|
||||
Hosts follow Bare addon naming: \`linux-x64\`, \`linux-arm64\`, \`darwin-arm64\`, \`darwin-x64\`, \`win32-x64\`, \`win32-arm64\`.
|
||||
### Server hosts
|
||||
\`linux-x64\`, \`linux-arm64\`, \`darwin-arm64\`, \`darwin-x64\`, \`win32-x64\`, \`win32-arm64\`
|
||||
|
||||
Verify:
|
||||
### Client
|
||||
AppImage / zip of the packaged Electron app (same UI as \`pear run -d .\`).
|
||||
|
||||
\`\`\`bash
|
||||
sha256sum -c SHA256SUMS
|
||||
@@ -93,13 +130,11 @@ sha256sum -c SHA256SUMS
|
||||
EOF
|
||||
)
|
||||
|
||||
# Escape body for JSON
|
||||
json_escape() {
|
||||
node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>process.stdout.write(JSON.stringify(s)))'
|
||||
}
|
||||
BODY_JSON=$(printf '%s' "$BODY" | json_escape)
|
||||
|
||||
# Find existing release by tag
|
||||
EXISTING_ID=$(curl -fsSL -H "$AUTH" -H "$ACCEPT" \
|
||||
"${API}/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/tags/${RELEASE_TAG}" \
|
||||
2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{try{console.log(JSON.parse(s).id||"")}catch{console.log("")}}') || true)
|
||||
@@ -108,7 +143,6 @@ if [[ -n "${EXISTING_ID}" ]]; then
|
||||
echo "[release] deleting existing release id=${EXISTING_ID} tag=${RELEASE_TAG}"
|
||||
curl -fsSL -X DELETE -H "$AUTH" \
|
||||
"${API}/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/${EXISTING_ID}" >/dev/null
|
||||
# Delete tag so we can recreate (rolling)
|
||||
curl -fsSL -X DELETE -H "$AUTH" \
|
||||
"${API}/repos/${GITEA_OWNER}/${GITEA_REPO}/tags/${RELEASE_TAG}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
@@ -126,9 +160,11 @@ upload() {
|
||||
local name
|
||||
name="$(basename "$file")"
|
||||
echo "[release] upload ${name}"
|
||||
local enc
|
||||
enc=$(node -e "console.log(encodeURIComponent(process.argv[1]))" "$name")
|
||||
curl -fsSL -X POST -H "$AUTH" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
"${API}/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/${RELEASE_ID}/assets?name=$(printf '%s' "$name" | jq -sRr @uri 2>/dev/null || node -e "console.log(encodeURIComponent(process.argv[1]))" "$name")" \
|
||||
"${API}/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/${RELEASE_ID}/assets?name=${enc}" \
|
||||
--data-binary @"$file" >/dev/null
|
||||
}
|
||||
|
||||
|
||||
+62
-33
@@ -1,14 +1,15 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Host-aware Bare standalone builder for peardock.
|
||||
* Delegates to scripts/bare-standalone.cjs (bare-pack + bare-build embed).
|
||||
* peardock build orchestrator
|
||||
*
|
||||
* server → Bare standalone (bare-pack + bare-build embed)
|
||||
* client → full Pear GUI via Electron Forge (hello-pear-electron pattern)
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/make.cjs server # native host
|
||||
* node scripts/make.cjs client
|
||||
* node scripts/make.cjs server [host]
|
||||
* node scripts/make.cjs client [platform arch]
|
||||
* node scripts/make.cjs both
|
||||
* node scripts/make.cjs server linux-x64
|
||||
* node scripts/make.cjs all # all desktop hosts × both products
|
||||
* node scripts/make.cjs all
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
@@ -18,9 +19,9 @@ const { spawnSync } = require('child_process')
|
||||
|
||||
const root = path.resolve(__dirname, '..')
|
||||
const nativeHost = `${os.platform()}-${os.arch()}`
|
||||
const builder = path.join(root, 'scripts/bare-standalone.cjs')
|
||||
const serverBuilder = path.join(root, 'scripts/bare-standalone.cjs')
|
||||
|
||||
const HOSTS = [
|
||||
const SERVER_HOSTS = [
|
||||
'darwin-arm64',
|
||||
'darwin-x64',
|
||||
'linux-arm64',
|
||||
@@ -29,16 +30,13 @@ const HOSTS = [
|
||||
'win32-x64',
|
||||
]
|
||||
|
||||
function run(product, hosts) {
|
||||
const args = [builder, '--product', product]
|
||||
for (const h of hosts) {
|
||||
args.push('--host', h)
|
||||
}
|
||||
console.log(`[make] node ${args.map((a) => (a.includes(' ') ? JSON.stringify(a) : a)).join(' ')}`)
|
||||
const res = spawnSync(process.execPath, args, {
|
||||
function run(cmd, args, opts = {}) {
|
||||
console.log(`[make] ${cmd} ${args.join(' ')}`)
|
||||
const res = spawnSync(cmd, args, {
|
||||
cwd: root,
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
shell: opts.shell || false,
|
||||
})
|
||||
if (res.error) {
|
||||
console.error(res.error.message)
|
||||
@@ -47,33 +45,64 @@ function run(product, hosts) {
|
||||
if (res.status !== 0) process.exit(res.status || 1)
|
||||
}
|
||||
|
||||
function makeServer(hosts) {
|
||||
const args = [serverBuilder, '--product', 'server']
|
||||
for (const h of hosts) {
|
||||
args.push('--host', h)
|
||||
}
|
||||
run(process.execPath, args)
|
||||
}
|
||||
|
||||
function makeClient(platform, arch) {
|
||||
// electron-forge make on current OS; optional platform/arch for cross where supported
|
||||
const args = ['electron-forge', 'make']
|
||||
if (platform) args.push('--platform', platform)
|
||||
if (arch) args.push('--arch', arch)
|
||||
run('npx', args, { shell: os.platform() === 'win32' })
|
||||
}
|
||||
|
||||
function main() {
|
||||
const argv = process.argv.slice(2)
|
||||
const target = argv[0] || 'both'
|
||||
|
||||
let products = []
|
||||
let hosts = []
|
||||
if (target === 'server') {
|
||||
const host = argv[1] || nativeHost
|
||||
makeServer([host])
|
||||
return
|
||||
}
|
||||
|
||||
if (target === 'client') {
|
||||
// argv[1] may be host like linux-x64 or platform
|
||||
const spec = argv[1] || `${os.platform()}-${os.arch()}`
|
||||
if (spec.includes('-')) {
|
||||
const [platform, arch] = spec.split('-')
|
||||
makeClient(platform, arch)
|
||||
} else {
|
||||
makeClient(spec, argv[2])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (target === 'both') {
|
||||
makeServer([argv[1] || nativeHost])
|
||||
makeClient()
|
||||
return
|
||||
}
|
||||
|
||||
if (target === 'all') {
|
||||
products = ['server', 'client']
|
||||
hosts = [...HOSTS]
|
||||
} else if (target === 'both') {
|
||||
products = ['server', 'client']
|
||||
hosts = [argv[1] || nativeHost]
|
||||
} else if (target === 'server' || target === 'client') {
|
||||
products = [target]
|
||||
hosts = [argv[1] || nativeHost]
|
||||
} else if (HOSTS.includes(target)) {
|
||||
products = ['server', 'client']
|
||||
hosts = [target]
|
||||
} else {
|
||||
console.error('Usage: node scripts/make.cjs <server|client|both|all|host> [host]')
|
||||
process.exit(1)
|
||||
makeServer(SERVER_HOSTS)
|
||||
// Client forge packages for the runner OS only (cross-compile Electron is limited)
|
||||
makeClient()
|
||||
return
|
||||
}
|
||||
|
||||
for (const product of products) {
|
||||
run(product, hosts)
|
||||
if (SERVER_HOSTS.includes(target)) {
|
||||
makeServer([target])
|
||||
return
|
||||
}
|
||||
|
||||
console.error('Usage: node scripts/make.cjs <server|client|both|all|host> [host|platform]')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user