Skip to content

Commit a0748e1

Browse files
Fix macOS installer release fallback
1 parent 3c2bac8 commit a0748e1

4 files changed

Lines changed: 156 additions & 83 deletions

File tree

.github/workflows/release.yml

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@ on:
44
push:
55
tags:
66
- 'v*'
7+
workflow_dispatch:
8+
inputs:
9+
tag:
10+
description: 'Release tag to build and upload the macOS artifact for (for example, v0.1.0)'
11+
required: true
12+
type: string
713

814
permissions:
915
contents: write
1016

1117
jobs:
1218
release:
19+
if: github.event_name == 'push'
1320
runs-on: [self-hosted, linux, x64, kvm]
1421
steps:
1522
- name: Checkout
@@ -37,34 +44,68 @@ jobs:
3744

3845
# The macOS server links cgo against Virtualization.framework, so it can't be
3946
# cross-compiled from the Linux release job — it builds on a macOS arm64 runner.
47+
# The Linux release job creates a draft release first; this job uploads the
48+
# macOS archive and publishes the release only after all artifacts are present.
49+
# workflow_dispatch lets maintainers backfill the macOS artifact for an
50+
# existing release tag if a prior release was published without it.
4051
release-darwin:
4152
needs: release
42-
runs-on: macos-14
53+
if: ${{ always() && ((github.event_name == 'push' && needs.release.result == 'success') || github.event_name == 'workflow_dispatch') }}
54+
runs-on: [self-hosted, macos, arm64]
55+
env:
56+
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
4357
steps:
4458
- name: Checkout
4559
uses: actions/checkout@v4
4660
with:
4761
fetch-depth: 0
62+
ref: ${{ env.RELEASE_TAG }}
4863

4964
- name: Set up Go
5065
uses: actions/setup-go@v5
5166
with:
67+
cache: false
5268
go-version: '1.25.4'
5369

70+
- name: Verify release exists
71+
env:
72+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
73+
run: gh release view "${RELEASE_TAG}" >/dev/null
74+
5475
- name: Build server and token tool
5576
run: |
56-
make build-darwin
77+
make sign-darwin
5778
go build -o bin/hypeman-token ./cmd/gen-jwt
5879
5980
- name: Package and upload darwin_arm64 archive
6081
env:
6182
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6283
run: |
63-
VERSION="${GITHUB_REF_NAME#v}"
84+
set -euo pipefail
85+
86+
VERSION="${RELEASE_TAG#v}"
87+
ARCHIVE_NAME="hypeman_${VERSION}_darwin_arm64.tar.gz"
6488
STAGE="$(mktemp -d)"
89+
OUT="$(mktemp -d)"
90+
EXISTING="$(mktemp -d)"
91+
6592
cp bin/hypeman "${STAGE}/hypeman-api"
6693
cp bin/hypeman-token "${STAGE}/hypeman-token"
6794
cp config.example.darwin.yaml "${STAGE}/"
68-
ARCHIVE="hypeman_${VERSION}_darwin_arm64.tar.gz"
69-
tar -czf "${ARCHIVE}" -C "${STAGE}" .
70-
gh release upload "${GITHUB_REF_NAME}" "${ARCHIVE}" --clobber
95+
96+
tar -czf "${OUT}/${ARCHIVE_NAME}" -C "${STAGE}" .
97+
98+
gh release download "${RELEASE_TAG}" -p checksums.txt -D "${EXISTING}"
99+
grep -v " ${ARCHIVE_NAME}$" "${EXISTING}/checksums.txt" > "${OUT}/checksums.next.txt" || true
100+
101+
CHECKSUM="$(shasum -a 256 "${OUT}/${ARCHIVE_NAME}" | awk '{print $1}')"
102+
printf '%s %s\n' "${CHECKSUM}" "${ARCHIVE_NAME}" >> "${OUT}/checksums.next.txt"
103+
sort -k2 "${OUT}/checksums.next.txt" > "${OUT}/checksums.txt"
104+
105+
gh release upload "${RELEASE_TAG}" "${OUT}/${ARCHIVE_NAME}" "${OUT}/checksums.txt" --clobber
106+
107+
- name: Publish completed release
108+
if: github.event_name == 'push'
109+
env:
110+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
111+
run: gh release edit "${RELEASE_TAG}" --draft=false

.goreleaser.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,4 @@ release:
8181
owner: kernel
8282
name: hypeman
8383
prerelease: auto
84-
draft: false
84+
draft: true

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636

3737
### macOS
3838
**macOS 11.0+** on Apple Silicon. Uses Apple's Virtualization.framework via the `vz` hypervisor.
39+
40+
The full server installer uses a prebuilt macOS artifact when one is available. If not, it builds the server from source; install Xcode Command Line Tools, Go, and Make first. Docker is optional and only needed for build workloads.
41+
3942
Install Rosetta to run `linux/amd64` images on Apple Silicon:
4043

4144
```bash

scripts/install.sh

Lines changed: 105 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
# DATA_DIR - Data directory (default: /var/lib/hypeman on Linux, ~/Library/Application Support/hypeman on macOS)
1616
# CONFIG_DIR - Config directory (default: /etc/hypeman on Linux, ~/.config/hypeman on macOS)
1717
#
18+
# On macOS, when no prebuilt server artifact exists for the selected release,
19+
# the installer falls back to building that release from source.
20+
#
1821

1922
set -e
2023

@@ -67,6 +70,67 @@ find_release_with_artifact() {
6770
return 1
6871
}
6972

73+
find_latest_release_tag() {
74+
local repo="$1"
75+
76+
curl -fsSL "https://api.github.com/repos/${repo}/releases/latest" 2>/dev/null | grep '"tag_name"' | head -1 | cut -d'"' -f4
77+
}
78+
79+
require_source_build_tools() {
80+
command -v git >/dev/null 2>&1 || error "git is required to build from source but not installed"
81+
command -v go >/dev/null 2>&1 || error "go is required to build from source but not installed"
82+
command -v make >/dev/null 2>&1 || error "make is required to build from source but not installed"
83+
}
84+
85+
build_server_from_source() {
86+
local source_ref="$1"
87+
local build_dir="${TMP_DIR}/hypeman"
88+
local build_log="${TMP_DIR}/build.log"
89+
90+
require_source_build_tools
91+
92+
info "Building server from source (ref: ${source_ref})..."
93+
: > "$build_log"
94+
95+
if ! git clone --branch "$source_ref" --depth 1 -q "https://github.com/${REPO}.git" "$build_dir" >> "$build_log" 2>&1; then
96+
error "Failed to clone repository. Build log:\n$(cat "$build_log")"
97+
fi
98+
99+
info "Building binaries (this may take a few minutes)..."
100+
if [ "$OS" = "darwin" ]; then
101+
if ! (cd "$build_dir" && make sign-darwin >> "$build_log" 2>&1); then
102+
echo ""
103+
echo -e "${RED}Build/signing failed. Full build log:${NC}"
104+
cat "$build_log"
105+
error "Build/signing failed"
106+
fi
107+
cp "${build_dir}/config.example.darwin.yaml" "${TMP_DIR}/config.example.darwin.yaml"
108+
else
109+
if ! (cd "$build_dir" && make build >> "$build_log" 2>&1); then
110+
echo ""
111+
echo -e "${RED}Build failed. Full build log:${NC}"
112+
cat "$build_log"
113+
error "Build failed"
114+
fi
115+
cp "${build_dir}/config.example.yaml" "${TMP_DIR}/config.example.yaml"
116+
fi
117+
118+
cp "${build_dir}/bin/hypeman" "${TMP_DIR}/${BINARY_NAME}"
119+
if [ "$OS" = "linux" ]; then
120+
cp "${build_dir}/bin/${UFFD_PAGER_BINARY_NAME}" "${TMP_DIR}/${UFFD_PAGER_BINARY_NAME}"
121+
fi
122+
123+
if ! (cd "$build_dir" && go build -o "${TMP_DIR}/hypeman-token" ./cmd/gen-jwt >> "$build_log" 2>&1); then
124+
echo ""
125+
echo -e "${RED}Build failed. Full build log:${NC}"
126+
cat "$build_log"
127+
error "Failed to build hypeman-token"
128+
fi
129+
130+
VERSION="${source_ref} (source)"
131+
info "Build complete"
132+
}
133+
70134
# =============================================================================
71135
# Detect OS and architecture (before pre-flight checks)
72136
# =============================================================================
@@ -119,7 +183,6 @@ if [ "$OS" = "darwin" ]; then
119183
error "Intel Macs not supported"
120184
fi
121185
command -v codesign >/dev/null 2>&1 || error "codesign is required but not installed (install Xcode Command Line Tools)"
122-
command -v docker >/dev/null 2>&1 || error "Docker CLI is required but not found. Install Docker via Colima or Docker Desktop."
123186
# Check if we need sudo for INSTALL_DIR
124187
if [ ! -w "$INSTALL_DIR" ] 2>/dev/null && [ ! -w "$(dirname "$INSTALL_DIR")" ] 2>/dev/null; then
125188
if command -v sudo >/dev/null 2>&1; then
@@ -164,11 +227,13 @@ if [ "$count" -gt 1 ]; then
164227
error "BRANCH, VERSION, and BINARY_DIR are mutually exclusive"
165228
fi
166229

230+
if [ "${VERSION:-}" = "latest" ]; then
231+
VERSION=""
232+
fi
233+
167234
# Additional checks for build-from-source mode
168235
if [ -n "$BRANCH" ]; then
169-
command -v git >/dev/null 2>&1 || error "git is required for BRANCH mode but not installed"
170-
command -v go >/dev/null 2>&1 || error "go is required for BRANCH mode but not installed"
171-
command -v make >/dev/null 2>&1 || error "make is required for BRANCH mode but not installed"
236+
require_source_build_tools
172237
fi
173238

174239
# Additional checks for BINARY_DIR mode
@@ -276,83 +341,39 @@ if [ -n "$BINARY_DIR" ]; then
276341

277342
VERSION="custom (from binary)"
278343
elif [ -n "$BRANCH" ]; then
279-
# Build from source mode
280-
info "Building from source (branch: $BRANCH)..."
281-
282-
BUILD_DIR="${TMP_DIR}/hypeman"
283-
BUILD_LOG="${TMP_DIR}/build.log"
284-
285-
# Clone repo (quiet)
286-
if ! git clone --branch "$BRANCH" --depth 1 -q "https://github.com/${REPO}.git" "$BUILD_DIR" 2>&1 | tee -a "$BUILD_LOG"; then
287-
error "Failed to clone repository. Build log:\n$(cat "$BUILD_LOG")"
288-
fi
289-
290-
info "Building binaries (this may take a few minutes)..."
291-
cd "$BUILD_DIR"
292-
293-
if ! make build >> "$BUILD_LOG" 2>&1; then
294-
echo ""
295-
echo -e "${RED}Build failed. Full build log:${NC}"
296-
cat "$BUILD_LOG"
297-
error "Build failed"
298-
fi
299-
if [ "$OS" = "darwin" ]; then
300-
if ! make sign-darwin >> "$BUILD_LOG" 2>&1; then
301-
echo ""
302-
echo -e "${RED}Signing failed. Full build log:${NC}"
303-
cat "$BUILD_LOG"
304-
error "Signing failed"
305-
fi
306-
cp "config.example.darwin.yaml" "${TMP_DIR}/config.example.darwin.yaml"
307-
else
308-
cp "config.example.yaml" "${TMP_DIR}/config.example.yaml"
309-
fi
310-
cp "bin/hypeman" "${TMP_DIR}/${BINARY_NAME}"
311-
if [ "$OS" = "linux" ]; then
312-
cp "bin/${UFFD_PAGER_BINARY_NAME}" "${TMP_DIR}/${UFFD_PAGER_BINARY_NAME}"
313-
fi
314-
315-
# Build hypeman-token (not included in make build)
316-
if ! go build -o "${TMP_DIR}/hypeman-token" ./cmd/gen-jwt >> "$BUILD_LOG" 2>&1; then
317-
echo ""
318-
echo -e "${RED}Build failed. Full build log:${NC}"
319-
cat "$BUILD_LOG"
320-
error "Failed to build hypeman-token"
321-
fi
322-
323-
VERSION="$BRANCH (source)"
324-
cd - > /dev/null
325-
326-
info "Build complete"
344+
build_server_from_source "$BRANCH"
327345
else
328346
# Download release mode
329347
if [ -z "$VERSION" ]; then
330-
info "Fetching latest version with available artifacts..."
331-
VERSION=$(find_release_with_artifact "$REPO" "hypeman" "$OS" "$ARCH")
332-
if [ -z "$VERSION" ]; then
333-
error "Failed to find a release with artifacts for ${OS}/${ARCH}"
348+
if [ "$OS" = "darwin" ]; then
349+
info "Fetching latest release..."
350+
VERSION=$(find_latest_release_tag "$REPO" || true)
351+
[ -n "$VERSION" ] || error "Failed to find the latest release"
352+
else
353+
info "Fetching latest version with available artifacts..."
354+
VERSION=$(find_release_with_artifact "$REPO" "hypeman" "$OS" "$ARCH")
355+
[ -n "$VERSION" ] || error "Failed to find a release with artifacts for ${OS}/${ARCH}"
334356
fi
335357
fi
336-
info "Installing version: $VERSION"
337358

338-
# Construct download URL
339-
VERSION_NUM="${VERSION#v}"
340-
ARCHIVE_NAME="hypeman_${VERSION_NUM}_${OS}_${ARCH}.tar.gz"
341-
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${ARCHIVE_NAME}"
359+
if [ ! -f "${TMP_DIR}/${BINARY_NAME}" ]; then
360+
info "Installing version: $VERSION"
342361

343-
info "Downloading ${ARCHIVE_NAME}..."
344-
if ! curl -fsSL "$DOWNLOAD_URL" -o "${TMP_DIR}/${ARCHIVE_NAME}"; then
345-
error "Failed to download from ${DOWNLOAD_URL}"
346-
fi
362+
# Construct download URL
363+
VERSION_NUM="${VERSION#v}"
364+
ARCHIVE_NAME="hypeman_${VERSION_NUM}_${OS}_${ARCH}.tar.gz"
365+
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${ARCHIVE_NAME}"
347366

348-
info "Extracting..."
349-
tar -xzf "${TMP_DIR}/${ARCHIVE_NAME}" -C "$TMP_DIR"
367+
info "Downloading ${ARCHIVE_NAME}..."
368+
if curl -fsSL "$DOWNLOAD_URL" -o "${TMP_DIR}/${ARCHIVE_NAME}"; then
369+
info "Extracting..."
370+
tar -xzf "${TMP_DIR}/${ARCHIVE_NAME}" -C "$TMP_DIR"
350371

351-
# On macOS, codesign after extraction with virtualization entitlements
352-
if [ "$OS" = "darwin" ]; then
353-
info "Signing binaries..."
354-
ENTITLEMENTS_TMP="${TMP_DIR}/vz.entitlements"
355-
cat > "$ENTITLEMENTS_TMP" << 'ENTITLEMENTS'
372+
# On macOS, codesign after extraction with virtualization entitlements
373+
if [ "$OS" = "darwin" ]; then
374+
info "Signing binaries..."
375+
ENTITLEMENTS_TMP="${TMP_DIR}/vz.entitlements"
376+
cat > "$ENTITLEMENTS_TMP" << 'ENTITLEMENTS'
356377
<?xml version="1.0" encoding="UTF-8"?>
357378
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
358379
<plist version="1.0">
@@ -366,10 +387,18 @@ else
366387
</dict>
367388
</plist>
368389
ENTITLEMENTS
369-
if ! codesign --force --sign - --entitlements "$ENTITLEMENTS_TMP" "${TMP_DIR}/${BINARY_NAME}" 2>/dev/null; then
370-
warn "codesign failed — vz hypervisor will not work without virtualization entitlement"
390+
if ! codesign --force --sign - --entitlements "$ENTITLEMENTS_TMP" "${TMP_DIR}/${BINARY_NAME}" 2>/dev/null; then
391+
warn "codesign failed — vz hypervisor will not work without virtualization entitlement"
392+
fi
393+
rm -f "$ENTITLEMENTS_TMP"
394+
fi
395+
elif [ "$OS" = "darwin" ]; then
396+
rm -f "${TMP_DIR}/${ARCHIVE_NAME}"
397+
warn "Prebuilt macOS server artifact ${ARCHIVE_NAME} could not be downloaded; building ${VERSION} from source instead"
398+
build_server_from_source "$VERSION"
399+
else
400+
error "Failed to download from ${DOWNLOAD_URL}"
371401
fi
372-
rm -f "$ENTITLEMENTS_TMP"
373402
fi
374403
fi
375404

@@ -646,7 +675,7 @@ fi
646675
if [ "$OS" = "darwin" ]; then
647676
info "Attempting to build builder image..."
648677
if command -v docker >/dev/null 2>&1; then
649-
if [ -n "$BRANCH" ] && [ -d "${TMP_DIR}/hypeman" ]; then
678+
if [ -d "${TMP_DIR}/hypeman" ]; then
650679
BUILD_CONTEXT="${TMP_DIR}/hypeman"
651680
else
652681
BUILD_CONTEXT=""

0 commit comments

Comments
 (0)