#!/usr/bin/env bash # Build bare-os-seeder + bare-os-booter for all 64-bit hosts and publish a rolling Gitea release. # Also assembles by-arch tarballs for operators / offline pear stage. # # Required (unless DRY_RUN=1): # RELEASE_TOKEN — Gitea PAT with repository release write # Optional: # GITEA_URL / GITEA_OWNER / GITEA_REPO # BARE_OS_HOSTS / BARE_OS_SEEDER_HOSTS / BARE_OS_BOOTER_HOSTS # RELEASE_TAG (default: rolling) # SKIP_PEAR_STAGE=1 — default in CI; skip pear-ci (stage offline via pear-stage-by-arch.sh) # DRY_RUN=1 set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" VERSION="$(node -p "require('./package.json').version")" STAMP="$(date -u +%Y%m%dT%H%M%SZ)" SHA="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)" TAG="${RELEASE_TAG:-rolling}" RELEASE_TITLE="${RELEASE_NAME:-bare-os rolling}" DIST="$ROOT/dist/release" DEFAULT_HOSTS="$(node -p "require('./scripts/hosts.cjs').ALL_64.join(',')")" rm -rf "$DIST" mkdir -p "$DIST" log() { echo "[release] $*"; } detect_remote() { local url url="$(git remote get-url origin 2>/dev/null || true)" if [[ "$url" =~ git@([^:]+):([^/]+)/([^/.]+) ]]; then local host="${BASH_REMATCH[1]}" # git@ssh.surf:owner/repo → https://git.ssh.surf if [[ "$host" == "ssh.surf" ]]; then host="git.ssh.surf"; fi echo "https://${host}" "${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:-bare-operating-system}}" export BARE_OS_HOSTS="${BARE_OS_HOSTS:-$DEFAULT_HOSTS}" export BARE_OS_SEEDER_HOSTS="${BARE_OS_SEEDER_HOSTS:-$BARE_OS_HOSTS}" export BARE_OS_BOOTER_HOSTS="${BARE_OS_BOOTER_HOSTS:-$BARE_OS_HOSTS}" # --- build kernel image into seeder mirror --- log "building kernel image (coreutils / openssh / bare-libs / parity)" npm run maintainer:kernel-image # --- binaries --- log "building seeder+booter binaries for: $BARE_OS_HOSTS" node scripts/make.cjs both sha_file() { local f="$1" if command -v sha256sum >/dev/null 2>&1; then sha256sum "$f" else shasum -a 256 "$f" fi } stage_product() { local product="$1" local host="$2" local name="bare-os-${product}" local dir="$ROOT/out/${name}-${host}" local bin="$name" [[ "$host" == win32-* ]] && bin="${name}.exe" if [[ ! -d "$dir" ]]; then log "WARN: missing $dir" return 1 fi if [[ ! -f "$dir/$bin" ]]; then local f f="$(find "$dir" -maxdepth 2 -type f \( -name "$name" -o -name "${name}.exe" \) | head -1 || true)" if [[ -n "$f" ]]; then bin="$(basename "$f")" cp -f "$f" "$dir/$bin" 2>/dev/null || true else log "WARN: no binary in $dir" return 1 fi fi local archive="${name}-${VERSION}-${host}.tar.gz" tar -C "$dir" -czf "$DIST/$archive" . (cd "$DIST" && sha_file "$archive" >"${archive}.sha256") log "staged $archive" } SEEDER_OK=0 BOOTER_OK=0 SEEDER_FAIL=0 BOOTER_FAIL=0 IFS=',' read -ra SHOSTS <<<"$BARE_OS_SEEDER_HOSTS" for h in "${SHOSTS[@]}"; do h="${h// /}" [[ -z "$h" ]] && continue if stage_product seeder "$h"; then SEEDER_OK=$((SEEDER_OK + 1)); else SEEDER_FAIL=$((SEEDER_FAIL + 1)); fi done IFS=',' read -ra BHOSTS <<<"$BARE_OS_BOOTER_HOSTS" for h in "${BHOSTS[@]}"; do h="${h// /}" [[ -z "$h" ]] && continue if stage_product booter "$h"; then BOOTER_OK=$((BOOTER_OK + 1)); else BOOTER_FAIL=$((BOOTER_FAIL + 1)); fi done # --- by-arch tarballs (pear-ci stage/seed is offline — see scripts/pear-stage-by-arch.sh) --- export SKIP_PEAR_STAGE="${SKIP_PEAR_STAGE:-1}" log "assembling by-arch (SKIP_PEAR_STAGE=$SKIP_PEAR_STAGE)" chmod +x scripts/pear-stage-by-arch.sh bash scripts/pear-stage-by-arch.sh cat >"$DIST/RELEASE_NOTES.md" </dev/null || true)" if [[ -n "$REL_JSON" ]]; then REL_ID="$(node -e "try{const j=JSON.parse(process.argv[1]);console.log(j.id||'')}catch{console.log('')}" "$REL_JSON")" if [[ -n "$REL_ID" ]]; then log "deleting previous release id=$REL_ID" curl -fsSL -X DELETE -H "$AUTH" "$API/releases/$REL_ID" >/dev/null || true fi fi curl -fsSL -X DELETE -H "$AUTH" "$API/tags/${TAG}" >/dev/null 2>&1 || true CREATE_BODY="$(node -e " const notes=require('fs').readFileSync(process.argv[1],'utf8'); console.log(JSON.stringify({ tag_name: process.argv[2], name: process.argv[3], body: notes, draft: false, prerelease: true, target_commitish: process.env.GITHUB_SHA || process.env.GITEA_SHA || 'main' })); " "$DIST/RELEASE_NOTES.md" "$TAG" "${RELEASE_TITLE} ${VERSION} ${SHA}")" CREATE_RESP="$(curl -fsSL -X POST -H "$AUTH" -H 'Content-Type: application/json' \ -d "$CREATE_BODY" "$API/releases")" REL_ID="$(node -e "console.log(JSON.parse(process.argv[1]).id)" "$CREATE_RESP")" log "created release id=$REL_ID" shopt -s nullglob for f in "$DIST"/*; do [[ -f "$f" ]] || continue base="$(basename "$f")" case "$base" in *.tar.gz|*.sha256|*.md) ;; *) continue ;; esac log "upload $base" name_q="$(node -e "console.log(encodeURIComponent(process.argv[1]))" "$base")" curl -fsSL -X POST -H "$AUTH" \ -F "attachment=@${f}" \ "$API/releases/${REL_ID}/assets?name=${name_q}" \ >/dev/null done log "release published: ${GITEA_URL}/${GITEA_OWNER}/${GITEA_REPO}/releases/tag/${TAG}"