Every leg installed a release tag and updated to HEAD, so nothing ran HEAD's installer on an empty machine (where all four 2026-09-23 install.ps1 breaks lived) and nothing exercised the updater we ship today -- tag legs run the OLD build's updater handing off to HEAD. The generator appends a HEAD -> NEXT start after the sampled tags, so the column runs wherever the update legs run (dispatch and stable-release). NEXT is a reserved update ref: the drivers mint a child of the install commit that adds one marker file, written to the object store only, which the local bare clone carries into serve.git. On Windows the HEAD leg takes every git.exe dir off PATH and installs no remote get-url shim: Get-PinnedGit returns any git on PATH, so either one skipped pinned-git staging. launch-from-spec's HEAD observer now uses the driver's real git so it cannot poll '' forever on that leg.
181 lines
7.9 KiB
YAML
181 lines
7.9 KiB
YAML
name: Install & Update E2E (reusable)
|
|
|
|
# Runs ONE {install-method, update-method} combination against ONE starting
|
|
# commit, with a real install (uv, a managed Python, Node, the venv) behind
|
|
# it.
|
|
#
|
|
# Reusable so callers can fan out over the combinations that matter --
|
|
# update from the tip vs. from an older release, `hermes update` vs.
|
|
# re-running the installer -- without duplicating the runner setup. Each leg
|
|
# is independent: its own isolated HOME, its own install, nothing rewound
|
|
# or shared.
|
|
#
|
|
# No sandbox: tests/install/installer-script-e2e.sh points every git
|
|
# process at a local bare clone (url.<file://serve.git>.insteadOf in a
|
|
# driver-owned GIT_CONFIG_GLOBAL) and isolates HOME, so the installer and
|
|
# updater run byte-for-byte against their real URLs on the bare runner --
|
|
# which is disposable, and therefore IS the sandbox. That also makes this
|
|
# workflow OS-agnostic: the same driver runs on ubuntu and macos runners.
|
|
#
|
|
# Method ids come from scripts/sandbox/generate-e2e-matrix.mjs. Supported
|
|
# today: install via installer-script, update via hermes-update or
|
|
# installer-script (re-run the one-liner).
|
|
# Anything else NATIVELY SKIPS (grey check, no runner): capability
|
|
# knowledge lives here, next to the driver, so the caller can dispatch
|
|
# every declared combination without knowing which ones work.
|
|
#
|
|
# Call it:
|
|
#
|
|
# jobs:
|
|
# tip:
|
|
# uses: ./.github/workflows/install-e2e-run.yml
|
|
# with:
|
|
# install-method: installer-script
|
|
# update-method: hermes-update
|
|
# install-ref: refs/heads/main
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
install-method:
|
|
description: 'How the starting version gets installed. Supported: installer-script (the real curl | install.sh one-liner) and installer-script+desktop (the same one-liner with --include-desktop).'
|
|
required: true
|
|
type: string
|
|
update-method:
|
|
description: 'How the install updates to HEAD. Supported: hermes-update (the updater), installer-script (re-run the one-liner), installer-script+desktop (re-run with --include-desktop), hermes-desktop-app-update (launch via hermes desktop under Playwright, click Update now). open-app-update runs only where an OS entry point exists (see the per-OS run workflows); pairs without one skip.'
|
|
required: true
|
|
type: string
|
|
install-ref:
|
|
description: 'What to install before updating: a branch, a tag (v2026.7.7), or a SHA reachable from main.'
|
|
required: false
|
|
type: string
|
|
default: refs/heads/main
|
|
update-ref:
|
|
description: 'What to update TO: HEAD (the commit under test), or NEXT -- a synthetic child of install-ref that the driver mints, so a leg that installs HEAD still has an update to take (HEAD''s own updater).'
|
|
required: false
|
|
type: string
|
|
default: HEAD
|
|
leg-id:
|
|
description: 'Artifact-safe matrix leg id (from generate-e2e-matrix.mjs legId). Names this leg''s logs + player artifacts so the report job can link a row to its zip.'
|
|
required: true
|
|
type: string
|
|
tag-has-desktop:
|
|
description: "Whether install-ref ships the desktop app (apps/desktop). The caller annotates this from the tag's own tree; desktop-method legs from pre-desktop releases natively skip."
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
runner:
|
|
description: 'Runner label.'
|
|
required: false
|
|
type: string
|
|
default: ubuntu-latest
|
|
timeout-minutes:
|
|
description: 'Job timeout. A cold run installs real toolchains twice, and app-update legs add a full Electron build + launch.'
|
|
required: false
|
|
type: number
|
|
default: 60
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
e2e:
|
|
name: install & update
|
|
# The pairs the driver can run today; anything else natively skips.
|
|
# Desktop-surface methods (+desktop installs,
|
|
# hermes-desktop-app-update) also need the starting tag to ship
|
|
# apps/desktop (their flags shipped with it).
|
|
if: >-
|
|
(inputs.install-method == 'installer-script'
|
|
|| (inputs.install-method == 'installer-script+desktop' && inputs.tag-has-desktop))
|
|
&& (contains(fromJSON('["hermes-update", "installer-script"]'), inputs.update-method)
|
|
|| (contains(fromJSON('["installer-script+desktop", "hermes-desktop-app-update"]'), inputs.update-method) && inputs.tag-has-desktop))
|
|
runs-on: ${{ inputs.runner }}
|
|
timeout-minutes: ${{ inputs.timeout-minutes }}
|
|
|
|
steps:
|
|
# Full history: the driver bare-clones this checkout as the repo the
|
|
# installer/updater talk to, and both OLD and HEAD must be reachable
|
|
# in that clone. A shallow clone cannot serve either need.
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 0
|
|
|
|
# The checkout owns the chat driver, not OLD's installed Node/runtime.
|
|
- name: Set up driver tools only
|
|
uses: ./.github/actions/setup-pm
|
|
with:
|
|
toolchain: all
|
|
packages: ffmpeg
|
|
cache: false
|
|
cache-python: false
|
|
cache-node: false
|
|
|
|
- name: Install locked chat driver dependencies
|
|
shell: bash
|
|
run: npm ci --workspace tests-js --include-workspace-root --omit=dev --ignore-scripts --no-audit --no-fund
|
|
|
|
# One recording mechanism on every OS (Xvfb gives headless linux a
|
|
# display; the same display serves any app the driver launches).
|
|
- name: Start screen recording
|
|
uses: ./.github/actions/e2e-screen-record
|
|
with:
|
|
mode: start
|
|
output: ${{ runner.temp }}/e2e-logs/recording.mkv
|
|
|
|
- name: Run install + update E2E
|
|
run: |
|
|
set -euo pipefail
|
|
tests/install/installer-script-e2e.sh \
|
|
--install-method '${{ inputs.install-method }}' \
|
|
--update-method '${{ inputs.update-method }}' \
|
|
--install-ref '${{ inputs.install-ref }}' \
|
|
--update-ref '${{ inputs.update-ref }}'
|
|
env:
|
|
# Outside the workspace on purpose: logs written into the repo
|
|
# would trip the driver's own dirty-tree guard.
|
|
HERMES_E2E_LOG_DIR: ${{ runner.temp }}/e2e-logs
|
|
|
|
- name: Stop screen recording
|
|
if: always()
|
|
uses: ./.github/actions/e2e-screen-record
|
|
with:
|
|
mode: stop
|
|
output: ${{ runner.temp }}/e2e-logs/recording.mkv
|
|
|
|
# Browsers cannot play Matroska: remux (copy codec, no re-encode) so
|
|
# the artifact zip feeds the static playback.html leg player directly.
|
|
- name: Remux recording for browser playback
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -f "${{ runner.temp }}/e2e-logs/recording.mkv" ]; then
|
|
ffmpeg -y -hide_banner -loglevel error -i "${{ runner.temp }}/e2e-logs/recording.mkv" \
|
|
-c copy "${{ runner.temp }}/e2e-logs/recording.mp4"
|
|
fi
|
|
|
|
# The leg player: ONE static HTML per run, uploaded up front by the
|
|
# leg-player job in install-e2e.yml (archive: false, so GitHub names
|
|
# the artifact after the file: playback.html). The report job links
|
|
# every ran leg to it with the leg's zip as a #zip= hash param.
|
|
- name: Build artifact name
|
|
if: always()
|
|
id: artifact
|
|
run: |
|
|
set -euo pipefail
|
|
echo "name=install-e2e-logs-${{ inputs.leg-id }}" >> "$GITHUB_OUTPUT"
|
|
|
|
# The installer's own transcripts say far more than the assertion that
|
|
# tripped when a real install breaks.
|
|
- name: Upload installer logs
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
# Unique per leg: a matrix over releases runs this workflow several
|
|
# times per route, and same-named artifacts collide.
|
|
name: ${{ steps.artifact.outputs.name }}-${{ github.sha }}
|
|
path: ${{ runner.temp }}/e2e-logs
|
|
retention-days: 14
|
|
if-no-files-found: ignore
|