Files
hermes-agent/.github/workflows/install-e2e.yml
T
ethernet e74f44d7ae ci(windows): desktop install/update e2e - published installer to this commit via fake git remote
windows sibling of install-e2e-run.yml. no bubblewrap on windows, so the
git proxying is git's own transport rewrite: an isolated GIT_CONFIG_GLOBAL
with multi-valued url.<file://fake.git>.insteadOf for both hardcoded repo
URLs, so the published Hermes-Setup.exe's install.ps1 clone, hermes update's
fetch, and the desktop's ls-remote all land on a local bare repo whose main
the driver controls - installer and updater run verbatim.

one run: seed fake.git from the checkout, force fake main to the newest
release tag, drive the real published bootstrap installer with AutoHotkey
(GUI, no headless mode), promote fake main to HEAD, then apply the desktop
app's builtin update route (scripts/desktop-update.ps1 -NoUi when the
installed base ships it, staged hermes-setup.exe --update otherwise) and
assert HEAD == target with a working hermes.

TODO routes: bare hermes update, and re-running the bootstrap installer
over the existing checkout.
2026-08-10 17:59:16 -04:00

127 lines
5.0 KiB
YAML

name: Install & Update E2E
# Can a user on a released version get to this commit?
#
# For each release we sample, a leg installs that release through the real
# `curl | install.sh` one-liner (uv, a managed Python, Node, the venv) inside
# scripts/dev-sandbox.sh, then applies one update route and requires the
# checkout to land on this commit with a working `hermes`.
#
# The starting versions are chosen at runtime from the repo's release tags
# (scripts/sandbox/pick-release-tags.sh): newest, oldest, and a spread between.
# A hardcoded list would stop covering the newest release the day after it
# ships, and would pin an "oldest" that nobody still runs.
#
# A separate Windows axis (install-e2e-windows-run.yml) installs with the
# latest PUBLISHED bootstrap installer and applies the desktop app's builtin
# update — no tag matrix there, because the published exe's own build pin is
# the starting point users actually have.
#
# Triggers:
# * every 12 hours, so upstream drift (a new uv, a Node bump, a PyPI change)
# surfaces on a schedule rather than in someone's review cycle;
# * when a release tag is created -- the moment the set of versions users can
# update FROM changes, and the moment a broken updater would strand them;
# * manually, where you can pick the route and how many releases to sample.
#
# Deliberately NOT on pull_request: a leg takes ~11 minutes of real toolchain
# installation, and the matrix multiplies that. Updating is release-shaped work,
# so it is gated on releases and the clock instead.
on:
workflow_dispatch:
inputs:
route:
description: 'Which update route to exercise. all/both include the Windows desktop leg.'
required: false
type: choice
default: all
options: [all, both, update, installer, windows-desktop]
tag-count:
description: 'How many release tags to sample (newest, oldest, and a spread between).'
required: false
type: string
default: '5'
schedule:
# Every 12 hours, off the hour to avoid the top-of-hour runner crunch.
- cron: '20 7,19 * * *'
push:
tags:
# Release tags only: the repo also carries backup/* and one-off tags.
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+.[0-9]+'
permissions:
contents: read
concurrency:
group: install-e2e-${{ github.ref }}
cancel-in-progress: true
jobs:
# Which released versions do we test updating FROM? Resolved once and shared
# by both route matrices, so the two routes cover the same set.
pick-releases:
name: Pick release tags
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
tags: ${{ steps.pick.outputs.tags }}
steps:
# This job only reads tag names and runs one script, so take the cheap
# checkout: no blobs (filter), no other files (sparse), but DO fetch tags
# -- they are the whole input, and the default shallow checkout has none.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
filter: blob:none
fetch-tags: true
sparse-checkout: scripts/sandbox/pick-release-tags.sh
sparse-checkout-cone-mode: false
- id: pick
run: |
set -euo pipefail
tags="$(scripts/sandbox/pick-release-tags.sh --count '${{ inputs.tag-count || 5 }}')"
echo "Testing updates from: $tags"
echo "tags=$tags" >> "$GITHUB_OUTPUT"
# `hermes update` -- the route most users take.
update:
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all", "both", "update"]'), inputs.route)
needs: pick-releases
strategy:
# One release breaking is worth knowing about even if another already
# failed, so let every leg report.
fail-fast: false
matrix:
install-ref: ${{ fromJSON(needs.pick-releases.outputs.tags) }}
uses: ./.github/workflows/install-e2e-run.yml
with:
route: update
install-ref: ${{ matrix.install-ref }}
# Re-running the curl one-liner over an existing checkout: autostash + pull
# rather than the updater's own git handling.
installer:
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all", "both", "installer"]'), inputs.route)
needs: pick-releases
strategy:
fail-fast: false
max-parallel: 3
matrix:
install-ref: ${{ fromJSON(needs.pick-releases.outputs.tags) }}
uses: ./.github/workflows/install-e2e-run.yml
with:
route: installer
install-ref: ${{ matrix.install-ref }}
# Windows desktop: install with the latest PUBLISHED bootstrap installer
# (Hermes-Setup.exe — the exact bits a user downloads today), then apply the
# desktop app's builtin update. No release-tag matrix: the published exe's
# build pin decides the starting base, which is precisely the "user on the
# current installer" scenario this axis exists to cover.
windows-desktop:
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all", "windows-desktop"]'), inputs.route)
uses: ./.github/workflows/install-e2e-windows-run.yml
with:
route: desktop