-
Notifications
You must be signed in to change notification settings - Fork 0
339 lines (320 loc) · 13 KB
/
simdeck-provider.yml
File metadata and controls
339 lines (320 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
name: SimDeck Provider
on:
workflow_dispatch:
inputs:
preview_id:
description: SimDeck Cloud preview deployment id
required: true
provider_token:
description: One-time provider registration token
required: true
simdeck_cloud_url:
description: SimDeck Cloud control plane URL
required: true
artifact_id:
description: GitHub Actions artifact id containing a simulator .app
required: false
source_run_id:
description: Source workflow run id that uploaded the simulator artifact
required: false
bundle_id:
description: iOS app bundle id to launch after install
required: false
simdeck_artifact_url:
description: Optional URL to a prebuilt simdeck binary tarball
required: false
simdeck_repository:
description: GitHub repository to clone when SimDeck is not present in the target repo
required: false
default: NativeScript/SimDeck
simdeck_ref:
description: Optional SimDeck branch, tag, or SHA to check out
required: false
stream_quality:
description: SimDeck realtime stream quality profile
required: false
default: ci-software
webrtc_ice_servers:
description: Comma-separated ICE server URLs for browser and provider WebRTC
required: false
default: stun:stun.l.google.com:19302
webrtc_ice_username:
description: Optional TURN username
required: false
webrtc_ice_credential:
description: Optional TURN credential
required: false
webrtc_ice_transport_policy:
description: Browser ICE transport policy
required: false
default: all
permissions:
actions: read
contents: read
jobs:
provider:
runs-on: macos-latest
timeout-minutes: 35
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Download simulator artifact
if: ${{ inputs.artifact_id != '' }}
uses: actions/download-artifact@v8
with:
artifact-ids: ${{ inputs.artifact_id }}
run-id: ${{ inputs.source_run_id || github.run_id }}
github-token: ${{ github.token }}
path: simdeck-artifact
- name: Register provider workflow
shell: bash
env:
SIMDECK_CLOUD_URL: ${{ inputs.simdeck_cloud_url }}
PREVIEW_ID: ${{ inputs.preview_id }}
PROVIDER_TOKEN: ${{ inputs.provider_token }}
run: |
set -euo pipefail
curl -fsS \
-H 'content-type: application/json' \
-d "{
\"previewId\":\"$PREVIEW_ID\",
\"providerToken\":\"$PROVIDER_TOKEN\",
\"providerRunId\":\"$GITHUB_RUN_ID\",
\"providerRunUrl\":\"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\",
\"status\":\"building\",
\"simulatorName\":\"GitHub Actions macOS runner\"
}" \
"$SIMDECK_CLOUD_URL/api/actions/providers/register"
- name: Build or download SimDeck
shell: bash
env:
SIMDECK_ARTIFACT_URL: ${{ inputs.simdeck_artifact_url }}
SIMDECK_REPOSITORY: ${{ inputs.simdeck_repository || 'NativeScript/SimDeck' }}
SIMDECK_REF: ${{ inputs.simdeck_ref }}
run: |
set -euo pipefail
if [[ -n "${SIMDECK_ARTIFACT_URL:-}" ]]; then
mkdir -p build
curl -fsSL "$SIMDECK_ARTIFACT_URL" | tar -xz -C build
chmod +x build/simdeck
elif [[ -x ./scripts/build-client.sh && -x ./scripts/build-cli.sh ]]; then
./scripts/build-client.sh
./scripts/build-cli.sh
else
simdeck_repo="${SIMDECK_REPOSITORY:-NativeScript/SimDeck}"
if [[ ! "$simdeck_repo" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
echo "Invalid SimDeck repository: $simdeck_repo"
exit 1
fi
clone_args=(--depth 1)
if [[ -n "${SIMDECK_REF:-}" ]]; then
clone_args+=(--branch "$SIMDECK_REF")
fi
git clone "${clone_args[@]}" "https://github.com/$simdeck_repo.git" simdeck-src
./simdeck-src/scripts/build-client.sh
./simdeck-src/scripts/build-cli.sh
mkdir -p build
mkdir -p client
cp simdeck-src/build/simdeck build/simdeck
cp simdeck-src/build/simdeck-bin build/simdeck-bin
rm -rf client/dist
cp -R simdeck-src/client/dist client/dist
chmod +x build/simdeck
chmod +x build/simdeck-bin
fi
- name: Start simulator provider
shell: bash
env:
SIMDECK_CLOUD_URL: ${{ inputs.simdeck_cloud_url }}
PREVIEW_ID: ${{ inputs.preview_id }}
PROVIDER_TOKEN: ${{ inputs.provider_token }}
BUNDLE_ID: ${{ inputs.bundle_id }}
SIMDECK_ALLOWED_ORIGINS: "*"
SIMDECK_WEBRTC_ICE_SERVERS: ${{ inputs.webrtc_ice_servers }}
SIMDECK_WEBRTC_ICE_USERNAME: ${{ inputs.webrtc_ice_username }}
SIMDECK_WEBRTC_ICE_CREDENTIAL: ${{ inputs.webrtc_ice_credential }}
SIMDECK_WEBRTC_ICE_TRANSPORT_POLICY: ${{ inputs.webrtc_ice_transport_policy }}
SIMDECK_STREAM_QUALITY: ${{ inputs.stream_quality || 'ci-software' }}
RUST_LOG: info,wtransport=warn,quinn=warn
RUST_BACKTRACE: full
NSUnbufferedIO: YES
run: |
set -euo pipefail
run_with_timeout() {
local limit="$1"
shift
"$@" &
local pid="$!"
local elapsed=0
while kill -0 "$pid" 2>/dev/null; do
if (( elapsed >= limit )); then
kill "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
return 124
fi
sleep 1
elapsed=$((elapsed + 1))
done
wait "$pid"
}
ios_udid="$(xcrun simctl list devices available --json | python3 -c '
import json, sys
data = json.load(sys.stdin)
for runtime, devices in data.get("devices", {}).items():
if "iOS" not in runtime:
continue
for device in devices:
if device.get("isAvailable"):
print(device["udid"])
raise SystemExit(0)
raise SystemExit("No available iOS simulator")
')"
xcrun simctl boot "$ios_udid" || true
if ! run_with_timeout 180 xcrun simctl bootstatus "$ios_udid" -b; then
echo "simctl bootstatus timed out"
xcrun simctl list devices "$ios_udid" || true
exit 1
fi
dump_logs() {
echo "--- simdeck.log ---"
cat simdeck.log || true
echo "--- private bridge log ---"
cat /tmp/simdeck-private-bridge.log || true
echo "--- crash reports ---"
find "$HOME/Library/Logs/DiagnosticReports" -maxdepth 1 -type f \( -name 'simdeck*.ips' -o -name 'simdeck*.crash' \) -print -exec cat {} \; || true
}
wait_for_simdeck_health() {
healthy=0
for _ in {1..120}; do
if ! kill -0 "$simdeck_pid" 2>/dev/null; then
echo "simdeck exited before health check passed"
dump_logs
return 1
fi
if curl -fsS http://127.0.0.1:4310/api/health >/dev/null; then
healthy=1
break
fi
sleep 1
done
if [[ "$healthy" != "1" ]]; then
echo "simdeck did not become healthy"
dump_logs
return 1
fi
}
start_simdeck_server() {
echo "Starting SimDeck server"
./build/simdeck serve \
--port 4310 \
--bind 127.0.0.1 \
--access-token "$PROVIDER_TOKEN" \
--client-root "$PWD/client/dist" \
--video-codec software \
--stream-quality "${SIMDECK_STREAM_QUALITY:-ci-software}" \
>> simdeck.log 2>&1 &
simdeck_pid="$!"
echo "SimDeck server pid: $simdeck_pid"
wait_for_simdeck_health
}
: > simdeck.log
simdeck_pid=""
start_simdeck_server
echo "Skipping private stream prewarm; the first browser WebRTC connection will attach the stream."
provider_base_url="$SIMDECK_CLOUD_URL/simulator/$PREVIEW_ID"
bridge_script="scripts/studio-provider-bridge.mjs"
if [[ ! -f "$bridge_script" && -f "simdeck-src/scripts/studio-provider-bridge.mjs" ]]; then
bridge_script="simdeck-src/scripts/studio-provider-bridge.mjs"
fi
if [[ ! -f "$bridge_script" ]]; then
echo "SimDeck Studio provider bridge script was not found"
exit 1
fi
SIMDECK_LOCAL_URL="http://127.0.0.1:4310" \
SIMDECK_STUDIO_URL="$provider_base_url" \
node "$bridge_script" \
> simdeck-provider-bridge.log 2>&1 &
bridge_pid="$!"
(
while true; do
sleep 15
echo "::group::SimDeck diagnostics $(date -u +%Y-%m-%dT%H:%M:%SZ)"
curl -fsS "http://127.0.0.1:4310/api/metrics" || true
echo
echo "--- simdeck webrtc log tail ---"
grep -E 'WebRTC|ICE|candidate|peer connection|H264|h264|frame write|bootstrap' simdeck.log | tail -n 120 || true
echo "--- private bridge log tail ---"
tail -n 80 /tmp/simdeck-private-bridge.log || true
echo "--- provider bridge log tail ---"
tail -n 40 simdeck-provider-bridge.log || true
echo "::endgroup::"
done
) &
diagnostics_pid="$!"
register() {
curl -fsS \
-H 'content-type: application/json' \
-d "{
\"previewId\":\"$PREVIEW_ID\",
\"providerToken\":\"$PROVIDER_TOKEN\",
\"providerRunId\":\"$GITHUB_RUN_ID\",
\"providerRunUrl\":\"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\",
\"baseUrl\":\"$provider_base_url\",
\"simulatorUdid\":\"$ios_udid\",
\"simulatorName\":\"GitHub Actions macOS runner\",
\"runtimeName\":\"$(xcrun simctl getenv "$ios_udid" SIMULATOR_RUNTIME_VERSION 2>/dev/null || true)\",
\"status\":\"ready\",
\"simulatorCount\":1
}" \
"$SIMDECK_CLOUD_URL/api/actions/providers/register"
}
register
echo "SimDeck Studio URL: $provider_base_url"
app_path=""
if [[ -d simdeck-artifact ]]; then
app_path="$(find simdeck-artifact -maxdepth 6 -type d -name '*.app' | head -n 1 || true)"
fi
if [[ -n "$app_path" ]]; then
echo "Installing simulator artifact from $app_path"
run_with_timeout 120 xcrun simctl install "$ios_udid" "$app_path" || echo "simctl install timed out or failed"
fi
if [[ -n "${BUNDLE_ID:-}" ]]; then
echo "Launching $BUNDLE_ID"
run_with_timeout 30 xcrun simctl launch "$ios_udid" "$BUNDLE_ID" || true
run_with_timeout 5 xcrun simctl io "$ios_udid" screenshot /tmp/simdeck-provider-app-screen.png >/tmp/simdeck-app-screenshot.log 2>&1 || cat /tmp/simdeck-app-screenshot.log || true
fi
deadline=$((SECONDS + 1800))
simdeck_restarts=0
while true; do
sleep 30
if (( SECONDS >= deadline )); then
curl -fsS \
-H 'content-type: application/json' \
-d "{\"previewId\":\"$PREVIEW_ID\",\"providerToken\":\"$PROVIDER_TOKEN\",\"providerRunId\":\"$GITHUB_RUN_ID\",\"providerRunUrl\":\"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\",\"status\":\"expired\"}" \
"$SIMDECK_CLOUD_URL/api/actions/providers/register" || true
echo "SimDeck provider hard timeout reached"
exit 0
fi
if ! kill -0 "$simdeck_pid" 2>/dev/null; then
echo "simdeck exited"
dump_logs
if (( simdeck_restarts >= 2 )); then
echo "simdeck restart limit reached"
exit 1
fi
simdeck_restarts=$((simdeck_restarts + 1))
echo "Restarting SimDeck server ($simdeck_restarts/2)"
start_simdeck_server
register || true
continue
fi
if ! kill -0 "$bridge_pid" 2>/dev/null; then
echo "provider bridge exited"
cat simdeck-provider-bridge.log || true
exit 1
fi
if ! kill -0 "$diagnostics_pid" 2>/dev/null; then
echo "diagnostics loop exited"
fi
register
done