name: CI on: push: branches: - main pull_request: branches: - main jobs: ci: name: Build & Test runs-on: ssh steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN }} - name: Check toolchain versions run: | node --version npm --version bare --version 2>/dev/null || echo "bare not in PATH (ok)" - name: Install root dependencies run: npm install - name: Install native-host dependencies run: npm install working-directory: native-host - name: Lint — syntax check extension run: | node --check extension/background.js node --check extension/background/logs.js node --check extension/background/state.js node --check extension/background/proxy.js node --check extension/background/native-messaging.js node --check extension/background/tab-lifecycle.js node --check extension/background/message-router.js node --check extension/dashboard/data/tlds.js node --check extension/dashboard/data/hostname-validator.js node --check extension/dashboard/core/utils.js node --check extension/dashboard/core/state.js node --check extension/dashboard/core/messaging.js node --check extension/dashboard/core/navigation.js node --check extension/dashboard/core/init.js node --check extension/dashboard/ui/toast.js node --check extension/dashboard/ui/modal.js node --check extension/dashboard/ui/state-tag.js node --check extension/dashboard/pages/overview.js node --check extension/dashboard/pages/virtual-hosts.js node --check extension/dashboard/pages/servers.js node --check extension/dashboard/pages/service-tunnels.js node --check extension/dashboard/pages/proxy-ca.js node --check extension/dashboard/pages/backups.js node --check extension/dashboard/pages/settings.js node --check extension/dashboard/pages/ssh.js node --check extension/dashboard/pages/rdp.js node --check extension/dashboard/pages/logs.js node --check extension/dashboard/refresh.js node --check extension/dashboard/events.js - name: Lint — syntax check native host run: | node --check native-host/proxy/connect-proxy.js node --check native-host/proxy/https-proxy.js node --check native-host/managers/certificate-authority.js node --check native-host/managers/backup-manager.js node --check native-host/managers/ssh-manager.js node --check native-host/managers/rdp-manager.js node --check native-host/host/messenger.js node --check native-host/host/paths.js node --check native-host/host/logger.js node --check native-host/host/startup.js node --check native-host/host/message-router.js node --check native-host/holesail-manager/state.js node --check native-host/holesail-manager/settings.js node --check native-host/holesail-manager/connections.js node --check native-host/holesail-manager/port-allocator.js node --check native-host/holesail-manager/servers.js node --check native-host/holesail-manager/virtual-hosts.js node --check native-host/holesail-manager/service-tunnels.js node --check native-host/holesail-manager/index.js - name: Lint — syntax check build scripts run: | node --check scripts/build-distributable.js node --check scripts/pack-extension.js node --check scripts/build-host.js - name: Pack extension run: npm run pack - name: Build all platform binaries run: node scripts/build-distributable.js --all --package - name: Smoke test — run native binary for current platform run: | echo "=== releases/ layout ===" find releases/ -type f | sort echo "" # bare-build names subdirs after the CPU ABI (x86_64, aarch64, arm64, etc.) # rather than the Node-style platform-arch string, so we search by filename. MACHINE=$(uname -m) echo "uname -m: $MACHINE" BIN="" # Prefer a subdir that matches the machine arch, fall back to any native binary. for candidate in \ "releases/${MACHINE}/holesail-browser-host" \ "releases/x86_64/holesail-browser-host" \ "releases/aarch64/holesail-browser-host" \ "releases/arm64/holesail-browser-host" \ "releases/holesail-browser-host"; do if [ -f "$candidate" ]; then BIN="$candidate" break fi done if [ -z "$BIN" ]; then echo "No runnable binary found — skipping smoke test" exit 0 fi echo "Testing binary: $BIN" chmod +x "$BIN" OUTPUT=$(timeout 6 "$BIN" &1 || true) echo "$OUTPUT" if echo "$OUTPUT" | grep -q "\[holesail-browser-host\] ready"; then echo "PASS: binary started successfully" else echo "FAIL: binary did not print ready signal" exit 1 fi if echo "$OUTPUT" | grep -qi "not available"; then echo "WARN: one or more optional modules unavailable (non-fatal):" echo "$OUTPUT" | grep -i "not available" fi echo "PASS: smoke test complete" - name: Generate checksums run: | cd releases find . -name "*.zip" -o -name "*.xpi" | sort | xargs sha256sum > SHA256SUMS.txt cat SHA256SUMS.txt - name: List artifacts run: | echo "=== All release artifacts ===" find releases/ -type f | sort echo "" find releases/ -type f -exec ls -lh {} \; | awk '{print $5, $9}' - name: Publish rolling release if: github.event_name == 'push' run: | SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) TAG="latest-main" TITLE="Latest build (main @ ${SHORT_SHA})" COMMIT_MSG=$(echo "${{ github.event.head_commit.message }}" | sed '/^Made-with:/d' | sed '/^$/d' | head -1) BODY="Automated build from main branch.\n\n**Commit:** ${{ github.sha }}\n**Message:** ${COMMIT_MSG}\n\nThis release is updated on every push to main and always contains the latest artifacts." API="${{ github.server_url }}/api/v1/repos/${{ github.repository }}" AUTH="Authorization: token ${{ secrets.RELEASE_TOKEN }}" # Force-move the git tag using git directly. # Set remote URL with token so we can push. REPO_URL=$(git remote get-url origin | sed 's|https://|https://x-token:${{ secrets.RELEASE_TOKEN }}@|') git remote set-url origin "${REPO_URL}" git config user.email "ci@holesail-browser" git config user.name "CI" git tag -f "${TAG}" "${{ github.sha }}" git push origin "refs/tags/${TAG}" --force echo "Tag ${TAG} force-pushed to ${{ github.sha }}" # Get existing release by tag (if any) EXISTING=$(curl -s -H "$AUTH" "${API}/releases/tags/${TAG}") RELEASE_ID=$(echo "$EXISTING" | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{const r=JSON.parse(d);console.log(r.id||'')}catch{console.log('')}})") echo "Existing release ID: $RELEASE_ID" if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "null" ] && [ "$RELEASE_ID" != "" ]; then # Update the existing release title, body and target commit curl -s -X PATCH \ -H "$AUTH" -H "Content-Type: application/json" \ "${API}/releases/${RELEASE_ID}" \ -d "{ \"name\": \"${TITLE}\", \"body\": \"${BODY}\", \"prerelease\": true, \"target_commitish\": \"${{ github.sha }}\" }" echo "Updated release ${RELEASE_ID}" # Delete all existing assets so we can re-upload fresh ones ASSETS=$(curl -s -H "$AUTH" "${API}/releases/${RELEASE_ID}/assets") echo "$ASSETS" | node -e " let d=''; process.stdin.on('data',c=>d+=c).on('end',()=>{ try { const assets = JSON.parse(d); if (Array.isArray(assets)) assets.forEach(a => console.log(a.id)); } catch(_) {} })" | while read ASSET_ID; do [ -z "$ASSET_ID" ] && continue echo "Deleting asset $ASSET_ID..." curl -s -X DELETE -H "$AUTH" "${API}/releases/${RELEASE_ID}/assets/${ASSET_ID}" done else # Create a brand new release RELEASE_ID=$(curl -s -X POST \ -H "$AUTH" -H "Content-Type: application/json" \ "${API}/releases" \ -d "{ \"tag_name\": \"${TAG}\", \"name\": \"${TITLE}\", \"body\": \"${BODY}\", \"prerelease\": true, \"target_commitish\": \"${{ github.sha }}\" }" | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{console.log(JSON.parse(d).id)}catch{console.log('')}})") echo "Created release ${RELEASE_ID}" fi # Upload all fresh artifacts for FILE in releases/*.zip releases/*.xpi releases/SHA256SUMS.txt; do [ -f "$FILE" ] || continue NAME=$(basename "$FILE") echo "Uploading $NAME..." curl -s -X POST \ -H "$AUTH" \ -H "Content-Type: application/octet-stream" \ "${API}/releases/${RELEASE_ID}/assets?name=${NAME}" \ --data-binary "@${FILE}" echo "" done echo "Done — release ${TAG} updated to ${{ github.sha }}"