test: add a hosted-runner trezor-emu path alongside the self-hosted one - #692
test: add a hosted-runner trezor-emu path alongside the self-hosted one#692mdozhdev wants to merge 5 commits into
Conversation
`trezor-emu` needs Docker and the iOS Simulator on one machine, which GitHub-hosted macOS runners cannot provide. It is the last job holding the self-hosted Mac open. Adds the same suite as a second `suite` choice, with the emulator stack on an `ubuntu-latest` runner reached over Tailscale: | job | runner | |---|---| | `trezor-stack` | ubuntu — bitcoind, electrs and Trezor User Env; holds until the tests finish | | `trezor-emu-remote` | macos-latest — the same xcodebuild invocation | The suite addresses Bridge, the User Env controller, electrs and bitcoind as 127.0.0.1 from inside the Simulator, across five call sites in BitkitUITests/TrezorBridgeDashboardUITests.swift that are not all configurable. The Mac job relays those six ports onto loopback instead, so the test and the app are unchanged — no Swift and no Info.plist edits. Verified against the pinned trezor-user-env image on Linux: Bridge binds 0.0.0.0 rather than loopback, origin checking is patched out, and a full acquire -> GetFeatures -> release round trip works off-host and through the relay, as does emulator-press-yes over the controller websocket. Nothing existing changes. `trezor-emu` is untouched and still dispatchable. The two new jobs must not depend on each other: `trezor-stack` only finishes once the tests are done, so a dependency either way deadlocks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
xcbeautify condenses a failed test to one line, which for an assertion carrying no message says only that it failed. Read the file, line and attached accessibility dump back out of the result bundle instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A coinbase matures after 100 confirmations and the compose setup mines a single block, so the wallet holds nothing spendable and the suite cannot fund a Trezor address. A developer machine carries blocks over in the chain volume between runs, which hides it; a runner always starts at height 1. Verified against the same compose: fresh stack is height 1 / balance 0, and sendtoaddress fails; after mining 101 the balance is 100 and the suite's own 0.001 funding call returns a txid. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adopts the approach from #692: rather than teaching every call site the stack address, relay its ports onto 127.0.0.1 on the Mac. The suite, the specs and the app all keep reaching 127.0.0.1, so no address plumbing is needed at all — every LND_HOST/ELECTRUM_HOST/E2E_LOCAL_HOST override is gone, and the credentials are written where lndConfig looks by default. Readiness is checked end to end rather than with a port probe: the relay accepts before it has dialled anything, so nc would pass with the stack unreachable. The self-hosted Mac is unresponsive, so e2e-tests-local is parked and e2e-status now reads the remote path. Both revert before merging. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Greptile SummaryThe PR adds a dispatch-only hosted Trezor emulator path that runs the Docker stack on Ubuntu and the iOS Simulator tests on macOS, connecting them through Tailscale.
Confidence Score: 3/5The PR should not merge until the external stack revision is pinned because the new job executes mutable upstream code after joining the project tailnet. The hosted stack path introduces a concrete supply-chain execution boundary, while its readiness checks also make unused ports a non-blocking source of workflow failure. Files Needing Attention: .github/workflows/ai-device-tests.yml
|
| Filename | Overview |
|---|---|
| .github/workflows/ai-device-tests.yml | Adds the complete two-runner Trezor test path; the mutable external checkout creates a supply-chain boundary issue, and the readiness gate unnecessarily couples the suite to two unused ports. |
Sequence Diagram
sequenceDiagram
participant L as Ubuntu stack job
participant T as Tailscale
participant M as macOS test job
participant S as iOS Simulator
L->>T: Join as trezor-stack-run_id
L->>L: Start and fund emulator stack
M->>T: Join as trezor-tester-run_id
M->>T: Discover stack runner
M->>L: Probe stack ports
M->>M: Relay ports onto 127.0.0.1
S->>M: Connect to loopback services
M->>S: Run Trezor UI tests
L->>L: Poll until test job completes
L->>L: Collect diagnostics and stop stack
Reviews (1): Last reviewed commit: "Merge branch 'master' into test/trezor-e..." | Re-trigger Greptile
| uses: actions/checkout@v7 | ||
| with: | ||
| repository: synonymdev/bitkit-docker | ||
| ref: main |
There was a problem hiding this comment.
Mutable code runs inside tailnet
When bitkit-docker's main branch changes or is compromised before dispatch, this job executes that unreviewed revision after joining the project tailnet, exposing CI resources, reachable tailnet services, and the checkout-persisted GitHub token to arbitrary upstream code. Pin the checkout to a reviewed commit SHA. How this was verified: The workflow checks out mutable main, joins Tailscale with TS_AUTHKEY, and then runs docker compose and ./scripts/trezor-emulator from that checkout.
There was a problem hiding this comment.
🔵 TS_AUTHKEY now lands on GitHub-hosted runners — confirm it is ephemeral, tagged and ACL-scoped
The self-hosted trezor-emu path consumes no secrets at all. Both new jobs put a Tailscale auth key on shared GitHub-hosted runners, and anything that can inject a step into this workflow (or any third-party action running in these jobs) can exfiltrate it and join your tailnet. Confirm TS_AUTHKEY is reusable-but-ephemeral, pre-authorized, and carries a dedicated tag whose ACL grants only the regtest/Trezor stack ports — not a general-purpose key. If it is not ephemeral, every run also leaves a dead node behind, which breaks the HostName == peer lookup on the next run.
There was a problem hiding this comment.
I can’t confirm those properties from this PR. The workflow only references ${{ secrets.TS_AUTHKEY }}; GitHub does not expose whether that key is reusable, ephemeral, pre-authorized, or tagged, and the repository contains no Tailscale ACL configuration.
The required configuration should be reusable (so both jobs can enroll), ephemeral (so each runner node is removed after logout/inactivity), pre-authorized (if device approval is enabled), and assigned a dedicated tag whose grants permit only the required regtest/Trezor endpoints. The ACL should not grant general tailnet access. A reusable but non-ephemeral key would leave nodes behind and make the HostName == peer lookup unreliable.
Please verify the key settings and tag/grant policy in Tailscale administration, or use per-run OIDC/OAuth-generated ephemeral credentials. Until that is confirmed, this security concern remains unresolved.
| done | ||
| echo "STACK_IP=$ip" >> "$GITHUB_ENV" | ||
|
|
||
| for port in 21325 9001 43782 60001 9002 6080; do |
There was a problem hiding this comment.
Unused ports gate test readiness
The readiness loop makes ports 9002 and 6080 mandatory even though the current suite uses only 21325, 9001, 43782, and 60001. Losing either unused endpoint therefore delays the job for up to 30 minutes and fails an otherwise healthy test stack; remove them from both the readiness probe and relay list.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
scrollTo only swiped up, so an element that started above the viewport was
pushed further off by every iteration. The balance lookup input sits at the
top of its scroll view and the result rendered under it moves the content
down, so the second clearAndType on that field swiped away from it eight
times until it reported a null frame and could no longer be tapped:
Failed to scroll to visible (by AX action) TextField,
{{inf, inf}, {0.0, 0.0}}, identifier: 'TrezorLookupInput'
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jvsena42
left a comment
There was a problem hiding this comment.
workflow_dispatch only, no pull_request_target, no untrusted checkout, and the concurrency group already includes inputs.suite, so the two paths cannot collide on the emulator.
One medium: the hold loop can fall out of its deadline and report success. Note greptile raised this exact defect on #689, but quoting this PR's 150/180 numbers — it applies here, not there. #689's sibling job has the guard this one is missing.
| break | ||
| fi | ||
| sleep 30 | ||
| done |
There was a problem hiding this comment.
🟡 Hold loop tears the stack down mid-test and reports success
The loop exits on deadline with no guard, so when it expires while trezor-emu-remote is still running the next steps stop the emulator and docker compose down, and this job goes green. The tester then fails with connection errors and the stack job looks healthy. #689's regtest-stack has exactly this guard (finished flag + exit 1); add it here too. Also raise the hold deadline: 9000s is under the 180-minute timeout-minutes on trezor-emu-remote, so a run that uses its full budget is guaranteed to hit this.
|
|
||
| - name: Hold the stack up until the tests finish | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} |
There was a problem hiding this comment.
🔵 Add permissions: — the jobs API needs actions: read, and the error is swallowed
2>/dev/null || echo 1 turns any gh api failure into 'tests still running', so a broken API call is indistinguishable from a slow suite and the runner idles to its deadline with no signal. Let the failure surface, or at least echo "::warning::jobs API failed" on the fallback. While here, add permissions: { contents: read, actions: read } to this job so it does not depend on the repo-wide default token scope.
| repository: synonymdev/bitkit-docker | ||
| ref: main | ||
|
|
||
| - uses: tailscale/github-action@v3 |
There was a problem hiding this comment.
🔵 Pin tailscale/github-action to a commit SHA
@v3 is a mutable tag on a third-party repo, and this is the one action in the workflow that is handed a credential and rewrites the runner's network stack. Pin it to a full commit SHA (tailscale/github-action@<sha> # v3.x.y). Applies to both occurrences (lines 154 and 296).
Description
trezor-emuneeds Docker and the Simulator on one machine, so it runs on the self-hosted Mac — the last job keeping it alive.Adds the same suite as a second
suitechoice: emulator stack onubuntu-latest, Simulator onmacos-latest, joined over Tailscale. Same shape as #689.The suite reaches Bridge, the User Env controller, electrs and bitcoind as
127.0.0.1from inside the Simulator, so the Mac job relays those ports onto loopback rather than making the suite stack-aware. No Swift orInfo.plistchanges.trezor-emuis untouched and still dispatchable. Dispatch-only, so nothing runs unless asked.Linked Issues/Tasks