Skip to content

test: add a hosted-runner trezor-emu path alongside the self-hosted one - #692

Open
mdozhdev wants to merge 5 commits into
masterfrom
test/trezor-emu-remote
Open

test: add a hosted-runner trezor-emu path alongside the self-hosted one#692
mdozhdev wants to merge 5 commits into
masterfrom
test/trezor-emu-remote

Conversation

@mdozhdev

Copy link
Copy Markdown
Collaborator

Description

trezor-emu needs 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 suite choice: emulator stack on ubuntu-latest, Simulator on macos-latest, joined over Tailscale. Same shape as #689.

The suite reaches Bridge, the User Env controller, electrs and bitcoind as 127.0.0.1 from inside the Simulator, so the Mac job relays those ports onto loopback rather than making the suite stack-aware. No Swift or Info.plist changes.

trezor-emu is untouched and still dispatchable. Dispatch-only, so nothing runs unless asked.

Linked Issues/Tasks

  • synonymdev/pubky-stack#275

mdozhdev and others added 3 commits August 27, 2026 15:45
`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>
mdozhdev added a commit that referenced this pull request Aug 28, 2026
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>
@mdozhdev
mdozhdev marked this pull request as ready for review August 31, 2026 12:30
@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown

Greptile Summary

The 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.

  • Adds independent Linux stack and macOS test jobs with coordinated lifecycle polling.
  • Funds a fresh regtest wallet and relays stack services onto the Mac runner's loopback interface.
  • Collects diagnostics from both runners.

Confidence Score: 3/5

The 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

Security Review

The Linux job executes scripts from the mutable bitkit-docker main branch after becoming a tailnet member, allowing an upstream compromise to execute in a credentialed and network-connected CI context.

Important Files Changed

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
Loading

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 }}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants