Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/_build-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ jobs:
run: |
SHORT=$(git rev-parse --short HEAD)
echo "short=$SHORT" >> $GITHUB_OUTPUT
echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "Commit: $SHORT"

- name: Setup Node.js
Expand Down Expand Up @@ -782,8 +783,7 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN || github.token }}
EVAOS_DESKTOP_BRIDGE_REQUIRE_REAL: '1'
EVAOS_DESKTOP_BRIDGE_SOURCE_REF: ${{ vars.EVAOS_DESKTOP_BRIDGE_SOURCE_REF }}
EVAOS_DESKTOP_BRIDGE_SOURCE_TOKEN: ${{ secrets.EVAOS_DESKTOP_BRIDGE_SOURCE_TOKEN || secrets.GH_TOKEN || secrets.GITHUB_TOKEN || github.token }}
EVAOS_DESKTOP_BRIDGE_SOURCE_REF: ${{ steps.commit.outputs.sha }}

- name: Validate macOS app staple inside DMG
if: startsWith(matrix.platform, 'macos')
Expand Down
188 changes: 187 additions & 1 deletion .github/workflows/evaos-beta-rc-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
beta_rc_ack:
description: 'Type evaos-beta-rc to acknowledge this canary installs and launches release-candidate macOS apps.'
description: 'Type evaos-beta-rc to acknowledge this canary installs and launches the candidate, then locally starts Full Access and Ask Permission without performing desktop actions.'
required: true
type: string
tag:
Expand Down Expand Up @@ -299,6 +299,114 @@ jobs:
NODE
node scripts/evaosBetaReleaseGate.js write-rc-proof-template "$PROOF_DIR" "$TAG"

- name: Verify updater ZIP signature and notarization
env:
TAG: ${{ github.event.inputs.tag }}
TAG_COMMIT: ${{ steps.provenance.outputs.tag_commit }}
run: |
set -euo pipefail
ZIP_NAME=$(node - release-assets/latest-arm64-mac.yml <<'NODE'
const fs = require('fs');
const metadataPath = process.argv[2];
const text = fs.readFileSync(metadataPath, 'utf8');
const refs = [];
for (const line of text.split(/\r?\n/)) {
const match = line.match(/^\s*(?:-\s*)?(?:path|url):\s*(.+?)\s*$/);
if (!match) continue;
let ref = match[1].trim().replace(/^['"]|['"]$/g, '');
if (/^https?:\/\//i.test(ref)) ref = new URL(ref).pathname.split('/').pop();
if (ref?.endsWith('.zip')) refs.push(ref);
}
const unique = [...new Set(refs)];
if (unique.length !== 1 || !/arm64/i.test(unique[0])) {
throw new Error('latest-arm64-mac.yml must identify exactly one arm64 updater ZIP.');
}
process.stdout.write(unique[0]);
NODE
)
UPDATER_ZIP="release-assets/$ZIP_NAME"
if [ ! -f "$UPDATER_ZIP" ]; then
echo "::error::Updater metadata references missing arm64 ZIP: $ZIP_NAME"
exit 1
fi
EXPECTED_SHA=$(node - release-assets/evaos-beta-release-manifest.json "$ZIP_NAME" <<'NODE'
const fs = require('fs');
const [manifestPath, assetName] = process.argv.slice(2);
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
const asset = (manifest.assets || []).find((candidate) => candidate.name === assetName);
if (!asset || !/^[0-9a-f]{64}$/i.test(String(asset.sha256 || ''))) {
throw new Error(`Trusted manifest does not bind updater ZIP ${assetName}.`);
}
process.stdout.write(asset.sha256);
NODE
)
ACTUAL_SHA=$(shasum -a 256 "$UPDATER_ZIP" | awk '{print $1}')
if [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then
echo "::error::Updater ZIP checksum does not match the trusted release manifest."
exit 1
fi

EXTRACT_DIR="$RUNNER_TEMP/evaos-updater-zip-arm64"
rm -rf "$EXTRACT_DIR"
mkdir -p "$EXTRACT_DIR"
ditto -x -k "$UPDATER_ZIP" "$EXTRACT_DIR"
ZIP_APP="$EXTRACT_DIR/$BETA_APP_NAME"
APP_ROOT_COUNT=$(find "$EXTRACT_DIR" -mindepth 1 -maxdepth 1 -type d -name '*.app' -print | wc -l | tr -d ' ')
if [ ! -d "$ZIP_APP" ] || [ "$APP_ROOT_COUNT" != "1" ]; then
echo "::error::Updater ZIP did not contain $BETA_APP_NAME."
exit 1
fi
INFO_PLIST="$ZIP_APP/Contents/Info.plist"
if [ ! -f "$INFO_PLIST" ]; then
echo "::error::Updater ZIP is missing the canonical app Info.plist."
exit 1
fi
/usr/libexec/PlistBuddy -c Print "$INFO_PLIST" >/dev/null
if [[ ! "$TAG" =~ ^evaos-beta-v?([0-9]+\.[0-9]+\.[0-9]+)-evaos-beta(\.[0-9]+)?$ ]]; then
echo "::error::Unable to derive the updater app version from tag $TAG."
exit 1
fi
EXPECTED_VERSION="${BASH_REMATCH[1]}"
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$INFO_PLIST")
PRODUCT_NAME=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleName' "$INFO_PLIST")
SHORT_VERSION=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$INFO_PLIST")
BUNDLE_VERSION=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$INFO_PLIST")
if [ "$BUNDLE_ID" != "com.evaos.workbench" ] || [ "$PRODUCT_NAME" != "evaOS Workbench" ] || [ "$SHORT_VERSION" != "$EXPECTED_VERSION" ] || [ "$BUNDLE_VERSION" != "$EXPECTED_VERSION" ]; then
echo "::error::Updater ZIP app identity does not match the RC tag and canonical Workbench identity."
exit 1
fi

codesign --verify --deep --strict --verbose=2 "$ZIP_APP" > "$PROOF_DIR/codesign-updater-zip-macos-arm64.txt" 2>&1
xcrun stapler validate "$ZIP_APP" > "$PROOF_DIR/stapler-updater-zip-macos-arm64.txt" 2>&1
spctl --assess --type execute --verbose "$ZIP_APP" > "$PROOF_DIR/spctl-updater-zip-macos-arm64.txt" 2>&1
node - "$PROOF_DIR/updater-zip-macos-arm64.json" "$TAG" "$TAG_COMMIT" "$ZIP_NAME" "$ACTUAL_SHA" "$BETA_APP_NAME" "$BUNDLE_ID" "$PRODUCT_NAME" "$SHORT_VERSION" "$BUNDLE_VERSION" <<'NODE'
const fs = require('fs');
const [outputPath, tag, releaseCommit, assetName, sha256, appName, bundleId, productName, shortVersion, bundleVersion] = process.argv.slice(2);
fs.writeFileSync(
outputPath,
`${JSON.stringify(
{
schema: 'evaos-updater-zip-trust/v2',
tag,
releaseCommit,
assetName,
sha256,
appName,
bundleId,
productName,
shortVersion,
bundleVersion,
codesignVerified: true,
staplerVerified: true,
gatekeeperVerified: true,
},
null,
2
)}\n`
);
NODE
rm -rf "$EXTRACT_DIR"

- name: Install fallback and beta apps
env:
FALLBACK_APP_NAME: ${{ github.event.inputs.fallback_app_name }}
Expand Down Expand Up @@ -340,6 +448,7 @@ jobs:
fi
rm -rf "/Applications/$app_name"
ditto "$app_path" "/Applications/$app_name"
rm -rf "$extract_dir"
}

install_fallback_app() {
Expand Down Expand Up @@ -427,6 +536,8 @@ jobs:
spctl --assess --type execute --verbose "$BETA_APP" > "$PROOF_DIR/spctl-macos-arm64.txt" 2>&1

- name: Launch beta and audit feed isolation
env:
TAG_COMMIT: ${{ steps.provenance.outputs.tag_commit }}
run: |
set -euo pipefail
BETA_APP="/Applications/$BETA_APP_NAME"
Expand Down Expand Up @@ -534,6 +645,80 @@ jobs:
printf '%s\n' "$STALE_RUNNING_APPS"
exit 1
fi

BRIDGE_COMMAND="$BETA_APP/Contents/Resources/Bridge/evaos-desktop-bridge"
if [ ! -x "$BRIDGE_COMMAND" ]; then
echo "::error::Installed Workbench bridge launcher is missing or not executable."
exit 1
fi
PRE_CANARY_DIR="$PROOF_DIR/installed-candidate-pre-canary"
CONNECTOR_CANARY_DIR="$PROOF_DIR/installed-candidate-connector"
rm -rf "$PRE_CANARY_DIR" "$CONNECTOR_CANARY_DIR"
"$BRIDGE_COMMAND" pre-canary \
--json \
--control-surface bridge-peekaboo \
--expected-version "$SHORT_VERSION" \
--expected-build "$BUNDLE_VERSION" \
--expected-source-commit "$TAG_COMMIT" \
--artifact-dir "$PRE_CANARY_DIR" \
> "$PROOF_DIR/installed-candidate-pre-canary.stdout.json" \
2> "$PROOF_DIR/installed-candidate-pre-canary.stderr.txt"
cp "$PRE_CANARY_DIR/qa-report.json" "$PROOF_DIR/installed-candidate-pre-canary.json"

TOKEN_FILE="$HOME/Library/Application Support/evaos-desktop-bridge/connector.token"
Comment thread
100yenadmin marked this conversation as resolved.
for _attempt in $(seq 1 30); do
if [ -s "$TOKEN_FILE" ] && curl --fail --silent --show-error --max-time 2 http://127.0.0.1:8765/health >/dev/null; then
Comment thread
100yenadmin marked this conversation as resolved.
break
fi
sleep 1
done
if [ ! -s "$TOKEN_FILE" ]; then
echo "::error::Installed Workbench did not create its connector token."
exit 1
fi
if ! curl --fail --silent --show-error --max-time 2 http://127.0.0.1:8765/health >/dev/null; then
echo "::error::Installed Workbench connector did not become reachable."
exit 1
fi
CONNECTOR_TOKEN=$(tr -d '\r\n' < "$TOKEN_FILE")
if [ -z "$CONNECTOR_TOKEN" ]; then
echo "::error::Installed Workbench connector token is empty."
exit 1
fi
set +e
EVAOS_DESKTOP_BRIDGE_TOKEN="$CONNECTOR_TOKEN" "$BRIDGE_COMMAND" qa-canary \
--connector-url http://127.0.0.1:8765 \
--artifact-dir "$CONNECTOR_CANARY_DIR" \
--version-under-test "$SHORT_VERSION" \
--build-under-test "$BUNDLE_VERSION" \
--source-commit-under-test "$TAG_COMMIT" \
--surface connector \
--suite control_start \
--operator-ack-live-control \
> "$PROOF_DIR/installed-candidate-connector.stdout.json" \
2> "$PROOF_DIR/installed-candidate-connector.stderr.txt"
QA_CANARY_EXIT=$?
set -e
if [ -f "$CONNECTOR_CANARY_DIR/qa-report.json" ]; then
cp "$CONNECTOR_CANARY_DIR/qa-report.json" "$PROOF_DIR/installed-candidate-connector.json"
fi
TOKEN_SCAN_EXIT=0
LC_ALL=C grep -R -F -- "$CONNECTOR_TOKEN" "$PROOF_DIR" >/dev/null 2>&1 || TOKEN_SCAN_EXIT=$?
if [ "$TOKEN_SCAN_EXIT" -eq 0 ]; then
echo "::error::Installed candidate proof leaked the connector token."
unset CONNECTOR_TOKEN
exit 1
fi
if [ "$TOKEN_SCAN_EXIT" -ne 1 ]; then
echo "::error::Installed candidate proof token scan could not complete safely."
unset CONNECTOR_TOKEN
exit 1
fi
Comment thread
100yenadmin marked this conversation as resolved.
unset CONNECTOR_TOKEN
if [ "$QA_CANARY_EXIT" -ne 0 ]; then
echo "::error::Installed candidate connector canary failed; inspect the sanitized proof artifact."
exit 1
fi
pkill -f "EvaOSWorkbench|evaOS Workbench" || true
Comment thread
100yenadmin marked this conversation as resolved.

{
Expand Down Expand Up @@ -667,6 +852,7 @@ jobs:
TAG: ${{ github.event.inputs.tag }}
EXPECTED_RELEASE_COMMIT: ${{ steps.provenance.outputs.tag_commit }}
EVAOS_BETA_LOCAL_SIGNED_DMG_FALLBACK_ACK: ${{ github.event.inputs.local_signed_dmg_fallback_ack }}
EVAOS_BETA_RC_RELEASE_ASSETS_DIR: release-assets
run: |
set -euo pipefail
{
Expand Down
21 changes: 10 additions & 11 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,6 @@ jobs:
CI: true
GH_TOKEN: ${{ github.token }}
EVAOS_DESKTOP_BRIDGE_ALLOW_PLACEHOLDER: '1'
EVAOS_DESKTOP_BRIDGE_SOURCE_TOKEN: ${{ secrets.EVAOS_DESKTOP_BRIDGE_SOURCE_TOKEN || github.token }}

- name: Verify build artifacts exist
shell: bash
Expand Down Expand Up @@ -749,10 +748,10 @@ jobs:
EVAOS_BETA_RELEASE_WORKFLOW: Build and Release
GITHUB_RUN_ID: '12345'
GITHUB_RUN_ATTEMPT: '1'
EVAOS_BETA_RELEASE_COMMIT: mock-sha
EVAOS_BETA_RELEASE_COMMIT: ${{ github.sha }}
EVAOS_BETA_RELEASE_BRANCH: evaos/release-public-beta
EVAOS_BETA_RELEASE_PUBLISH_ENABLED: 'true'
EXPECTED_RELEASE_COMMIT: mock-sha
EXPECTED_RELEASE_COMMIT: ${{ github.sha }}
EVAOS_BETA_SKIP_GITHUB_RUN_VERIFY: '1'
run: |
node scripts/evaosBetaReleaseGate.js write-manifest release-assets evaos-beta-v1.0.0-evaos-beta.0
Expand All @@ -772,11 +771,11 @@ jobs:
EVAOS_BETA_RELEASE_WORKFLOW: Build and Release
GITHUB_RUN_ID: '12345'
GITHUB_RUN_ATTEMPT: '1'
EVAOS_BETA_RELEASE_COMMIT: mock-sha
EVAOS_BETA_RELEASE_COMMIT: ${{ github.sha }}
EVAOS_BETA_RELEASE_BRANCH: evaos/release-public-beta
EVAOS_BETA_RELEASE_PUBLISH_ENABLED: 'true'
EVAOS_BETA_SKIP_GITHUB_RUN_VERIFY: '1'
EXPECTED_RELEASE_COMMIT: mock-sha
EXPECTED_RELEASE_COMMIT: ${{ github.sha }}
INCLUDE_WEB_CLI_ASSETS: '0'
EVAOS_RELEASE_TARGET_PLATFORMS: macos
MOCK_VERSION: '1.0.0'
Expand Down Expand Up @@ -807,11 +806,11 @@ jobs:
EVAOS_BETA_RELEASE_WORKFLOW: Build and Release
GITHUB_RUN_ID: '12345'
GITHUB_RUN_ATTEMPT: '1'
EVAOS_BETA_RELEASE_COMMIT: mock-sha
EVAOS_BETA_RELEASE_COMMIT: ${{ github.sha }}
EVAOS_BETA_RELEASE_BRANCH: evaos/release-public-beta
EVAOS_BETA_RELEASE_PUBLISH_ENABLED: 'true'
EVAOS_BETA_SKIP_GITHUB_RUN_VERIFY: '1'
EXPECTED_RELEASE_COMMIT: mock-sha
EXPECTED_RELEASE_COMMIT: ${{ github.sha }}
INCLUDE_WEB_CLI_ASSETS: '0'
EVAOS_RELEASE_TARGET_PLATFORMS: macos-arm64
MOCK_VERSION: '1.0.0'
Expand Down Expand Up @@ -867,11 +866,11 @@ jobs:
EVAOS_BETA_RELEASE_WORKFLOW: Build and Release
GITHUB_RUN_ID: '12345'
GITHUB_RUN_ATTEMPT: '1'
EVAOS_BETA_RELEASE_COMMIT: mock-sha
EVAOS_BETA_RELEASE_COMMIT: ${{ github.sha }}
EVAOS_BETA_RELEASE_BRANCH: evaos/release-public-beta
EVAOS_BETA_RELEASE_PUBLISH_ENABLED: 'true'
EVAOS_BETA_SKIP_GITHUB_RUN_VERIFY: '1'
EXPECTED_RELEASE_COMMIT: mock-sha
EXPECTED_RELEASE_COMMIT: ${{ github.sha }}
INCLUDE_WEB_CLI_ASSETS: '0'
EVAOS_RELEASE_TARGET_PLATFORMS: windows
MOCK_VERSION: '1.0.0'
Expand Down Expand Up @@ -902,11 +901,11 @@ jobs:
EVAOS_BETA_RELEASE_WORKFLOW: Build and Release
GITHUB_RUN_ID: '12345'
GITHUB_RUN_ATTEMPT: '1'
EVAOS_BETA_RELEASE_COMMIT: mock-sha
EVAOS_BETA_RELEASE_COMMIT: ${{ github.sha }}
EVAOS_BETA_RELEASE_BRANCH: evaos/release-public-beta
EVAOS_BETA_RELEASE_PUBLISH_ENABLED: 'true'
EVAOS_BETA_SKIP_GITHUB_RUN_VERIFY: '1'
EXPECTED_RELEASE_COMMIT: mock-sha
EXPECTED_RELEASE_COMMIT: ${{ github.sha }}
INCLUDE_WEB_CLI_ASSETS: '0'
EVAOS_RELEASE_TARGET_PLATFORMS: macos
EVAOS_MOCK_MACOS_DMG_ONLY: '1'
Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/release-distribute.yml
Original file line number Diff line number Diff line change
Expand Up @@ -269,16 +269,18 @@ jobs:
TAG: ${{ steps.version.outputs.tag }}
EXPECTED_RELEASE_COMMIT: ${{ steps.provenance.outputs.tag_commit }}
EVAOS_BETA_LOCAL_SIGNED_DMG_FALLBACK_ACK: ${{ github.event.inputs.local_signed_dmg_fallback_ack }}
EVAOS_BETA_RC_RELEASE_ASSETS_DIR: dist
run: |
set -euo pipefail
if [ -z "$RC_PROOF_RUN_ID" ]; then
echo "::error::rc_proof_run_id is required before public beta distribution."
exit 1
fi

RUN_JSON=$(gh run view "$RC_PROOF_RUN_ID" --repo "${{ github.repository }}" --json conclusion,event,workflowName)
node - "$RUN_JSON" <<'NODE'
RUN_JSON=$(gh run view "$RC_PROOF_RUN_ID" --repo "${{ github.repository }}" --json conclusion,event,workflowName,headSha,createdAt)
node - "$RUN_JSON" "$EXPECTED_RELEASE_COMMIT" <<'NODE'
const run = JSON.parse(process.argv[2]);
const expectedHead = process.argv[3];
if (run.conclusion !== 'success') {
throw new Error(`RC proof run did not succeed: ${run.conclusion}`);
}
Expand All @@ -288,6 +290,14 @@ jobs:
if (run.event !== 'workflow_dispatch') {
throw new Error(`RC proof run was not manually dispatched: ${run.event}`);
}
if (run.headSha !== expectedHead) {
throw new Error(`RC proof head ${run.headSha} does not match release commit ${expectedHead}.`);
}
const createdAt = Date.parse(run.createdAt || '');
const ageMs = Date.now() - createdAt;
if (!Number.isFinite(createdAt) || ageMs < -5 * 60 * 1000 || ageMs > 24 * 60 * 60 * 1000) {
throw new Error(`RC proof run is outside the 24-hour publication window: ${run.createdAt || 'missing'}.`);
}
NODE

rm -rf rc-proof-download rc-proof
Expand Down
Loading
Loading