Skip to content

Commit 0d3023d

Browse files
committed
Merge branch 'main' into jigar/affiliate-flow
2 parents 010f990 + 3d765cb commit 0d3023d

19 files changed

Lines changed: 1319 additions & 71 deletions

File tree

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
TEST_PATH="${TEST_PATH:-integration_test/vpn/macos_connect_smoke_test.dart}"
5+
ARTIFACT_DIR="${ARTIFACT_DIR:-smoke-artifacts/macos}"
6+
RUN_CONNECT_SMOKE="${RUN_CONNECT_SMOKE:-true}"
7+
ENABLE_IP_CHECK="${ENABLE_IP_CHECK:-false}"
8+
FORCE_FULL_TUNNEL="${FORCE_FULL_TUNNEL:-true}"
9+
EXTENSION_TIMEOUT_SECONDS="${EXTENSION_TIMEOUT_SECONDS:-120}"
10+
APP_INSTALL_DIR="${APP_INSTALL_DIR:-/Applications/Lantern.app}"
11+
LANTERN_LOG_DIR="${LANTERN_LOG_DIR:-/Users/Shared/Lantern/Logs}"
12+
DMG_MOUNT_DIR=""
13+
14+
log_step() {
15+
printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*" >&2
16+
}
17+
18+
find_first_name() {
19+
local root="$1"
20+
local name="$2"
21+
22+
if [[ -z "$root" || ! -e "$root" ]]; then
23+
return 1
24+
fi
25+
26+
find "$root" -maxdepth 4 -name "$name" -print -quit
27+
}
28+
29+
copy_app_bundle() {
30+
local source="$1"
31+
local destination="$2"
32+
33+
log_step "Copying Lantern app from $source"
34+
rm -rf "$destination"
35+
mkdir -p "$(dirname "$destination")"
36+
ditto "$source" "$destination"
37+
xattr -dr com.apple.quarantine "$destination" 2>/dev/null || true
38+
printf '%s\n' "$destination"
39+
}
40+
41+
extract_app_zip() {
42+
local zip_path="$1"
43+
local tmp_dir
44+
tmp_dir="$(mktemp -d)"
45+
trap 'rm -rf "$tmp_dir"' RETURN
46+
47+
log_step "Extracting Lantern app from $zip_path"
48+
ditto -x -k "$zip_path" "$tmp_dir"
49+
50+
local app_path
51+
app_path="$(find_first_name "$tmp_dir" "Lantern.app" || true)"
52+
if [[ -z "$app_path" ]]; then
53+
printf 'Lantern.app not found inside %s\n' "$zip_path" >&2
54+
return 1
55+
fi
56+
57+
copy_app_bundle "$app_path" "$APP_INSTALL_DIR"
58+
}
59+
60+
detach_dmg() {
61+
if [[ -n "$DMG_MOUNT_DIR" && -d "$DMG_MOUNT_DIR" ]]; then
62+
local mount_dir="$DMG_MOUNT_DIR"
63+
hdiutil detach "$mount_dir" -quiet || true
64+
rmdir "$mount_dir" 2>/dev/null || true
65+
DMG_MOUNT_DIR=""
66+
fi
67+
}
68+
69+
copy_app_from_dmg() {
70+
local dmg_path="$1"
71+
local mount_dir
72+
mount_dir="$(mktemp -d)"
73+
DMG_MOUNT_DIR="$mount_dir"
74+
75+
log_step "Mounting Lantern DMG $dmg_path"
76+
hdiutil attach "$dmg_path" -nobrowse -readonly -mountpoint "$mount_dir" >/dev/null
77+
78+
local app_path
79+
app_path="$(find_first_name "$mount_dir" "Lantern.app" || true)"
80+
if [[ -z "$app_path" ]]; then
81+
printf 'Lantern.app not found inside %s\n' "$dmg_path" >&2
82+
detach_dmg
83+
return 1
84+
fi
85+
86+
copy_app_bundle "$app_path" "$APP_INSTALL_DIR"
87+
detach_dmg
88+
}
89+
90+
resolve_app_path() {
91+
if [[ -n "${APP_PATH:-}" && -x "$APP_PATH/Contents/MacOS/Lantern" ]]; then
92+
printf '%s\n' "$APP_PATH"
93+
return
94+
fi
95+
96+
local dmg_path
97+
dmg_path="$(find_first_name "${DMG_ARTIFACT_DIR:-}" "*.dmg" || true)"
98+
if [[ -n "$dmg_path" ]]; then
99+
copy_app_from_dmg "$dmg_path"
100+
return
101+
fi
102+
103+
local app_zip
104+
app_zip="$(find_first_name "${APP_ARTIFACT_DIR:-}" "Lantern.app.zip" || true)"
105+
if [[ -n "$app_zip" ]]; then
106+
extract_app_zip "$app_zip"
107+
return
108+
fi
109+
110+
local app_artifact
111+
app_artifact="$(find_first_name "${APP_ARTIFACT_DIR:-}" "Lantern.app" || true)"
112+
if [[ -n "$app_artifact" ]]; then
113+
copy_app_bundle "$app_artifact" "$APP_INSTALL_DIR"
114+
return
115+
fi
116+
117+
local candidates=(
118+
"build/macos/Build/Products/Release/Lantern.app"
119+
"build/macos/Build/Products/Debug/Lantern.app"
120+
"build/macos/Runner.app"
121+
)
122+
123+
for candidate in "${candidates[@]}"; do
124+
if [[ -d "$candidate" ]]; then
125+
printf '%s\n' "$candidate"
126+
return
127+
fi
128+
done
129+
130+
printf 'Lantern.app was not found. Set APP_PATH, or provide APP_ARTIFACT_DIR/DMG_ARTIFACT_DIR.\n' >&2
131+
return 1
132+
}
133+
134+
capture_command() {
135+
local name="$1"
136+
shift
137+
138+
log_step "Capturing $name"
139+
"$@" >"$ARTIFACT_DIR/$name.txt" 2>&1 || true
140+
}
141+
142+
capture_lantern_logs() {
143+
local output_dir="$ARTIFACT_DIR/lantern-logs"
144+
mkdir -p "$output_dir"
145+
146+
if [[ -d "$LANTERN_LOG_DIR" ]]; then
147+
cp -R "$LANTERN_LOG_DIR/." "$output_dir/" 2>/dev/null || true
148+
else
149+
printf 'No Lantern log directory found at %s\n' "$LANTERN_LOG_DIR" \
150+
>"$output_dir/missing.txt"
151+
fi
152+
}
153+
154+
reset_lantern_logs() {
155+
log_step "Resetting Lantern logs at $LANTERN_LOG_DIR"
156+
rm -rf "$LANTERN_LOG_DIR"
157+
mkdir -p "$LANTERN_LOG_DIR"
158+
}
159+
160+
capture_unified_logs() {
161+
log_step "Capturing unified logs"
162+
log show \
163+
--last 30m \
164+
--style syslog \
165+
--predicate 'subsystem == "org.getlantern.lantern" OR subsystem == "org.getlantern.lantern.PacketTunnel"' \
166+
>"$ARTIFACT_DIR/unified-lantern.log" 2>&1 || true
167+
}
168+
169+
capture_screenshot() {
170+
log_step "Capturing screenshot"
171+
screencapture -x "$ARTIFACT_DIR/screenshot.png" 2>/dev/null || true
172+
}
173+
174+
capture_diagnostics() {
175+
local reason="$1"
176+
177+
mkdir -p "$ARTIFACT_DIR"
178+
log_step "Capturing diagnostics: $reason"
179+
{
180+
printf 'reason=%s\n' "$reason"
181+
date
182+
} >"$ARTIFACT_DIR/diagnostics.txt"
183+
184+
capture_command "systemextensionsctl-list" systemextensionsctl list
185+
capture_command "process-list" ps aux
186+
capture_command "packet-tunnel-processes" pgrep -fl "org.getlantern.lantern.PacketTunnel"
187+
capture_lantern_logs
188+
capture_unified_logs
189+
capture_screenshot
190+
}
191+
192+
quit_lantern() {
193+
log_step "Asking Lantern to quit"
194+
osascript -e 'tell application id "org.getlantern.lantern" to quit' >/dev/null 2>&1 || true
195+
osascript -e 'tell application "Lantern" to quit' >/dev/null 2>&1 || true
196+
sleep 2
197+
}
198+
199+
packet_tunnel_processes() {
200+
pgrep -fl "org.getlantern.lantern.PacketTunnel" 2>/dev/null || true
201+
}
202+
203+
wait_for_packet_tunnel_exit() {
204+
local timeout_seconds="${1:-30}"
205+
206+
for ((i = 0; i < timeout_seconds; i++)); do
207+
if [[ -z "$(packet_tunnel_processes)" ]]; then
208+
log_step "PacketTunnel is not running"
209+
return 0
210+
fi
211+
sleep 1
212+
done
213+
214+
packet_tunnel_processes >"$ARTIFACT_DIR/packet-tunnel-still-running.txt"
215+
printf 'PacketTunnel was still running after disconnect/quit\n' >&2
216+
return 1
217+
}
218+
219+
run_system_extension_preflight() {
220+
local app_executable="$1"
221+
local output="$ARTIFACT_DIR/system-extension-preflight.jsonl"
222+
223+
log_step "Running macOS system extension preflight"
224+
set +e
225+
"$app_executable" \
226+
--smoke-activate-system-extension \
227+
--timeout-seconds "$EXTENSION_TIMEOUT_SECONDS" \
228+
>"$output" 2>&1
229+
local exit_code=$?
230+
set -e
231+
232+
cat "$output"
233+
case "$exit_code" in
234+
0)
235+
return 0
236+
;;
237+
20)
238+
printf 'System extension approval is missing on this runner. Approve Lantern in System Settings or install the MDM approval profile, then rerun this smoke test.\n' >&2
239+
;;
240+
21)
241+
printf 'System extension activation requires a reboot before this smoke test can connect.\n' >&2
242+
;;
243+
124)
244+
printf 'System extension preflight timed out after %s seconds.\n' "$EXTENSION_TIMEOUT_SECONDS" >&2
245+
;;
246+
*)
247+
printf 'System extension preflight failed with exit code %s.\n' "$exit_code" >&2
248+
;;
249+
esac
250+
251+
return "$exit_code"
252+
}
253+
254+
run_flutter_connect_smoke() {
255+
local args=(
256+
"test"
257+
"$TEST_PATH"
258+
"-d"
259+
"macos"
260+
"--reporter=expanded"
261+
"--dart-define=DISABLE_SYSTEM_TRAY=true"
262+
)
263+
264+
if [[ "$ENABLE_IP_CHECK" == "true" ]]; then
265+
args+=("--dart-define=ENABLE_IP_CHECK=true")
266+
fi
267+
268+
if [[ "$FORCE_FULL_TUNNEL" == "true" ]]; then
269+
args+=("--dart-define=SMOKE_FORCE_FULL_TUNNEL=true")
270+
fi
271+
272+
log_step "Running macOS connect smoke: flutter ${args[*]}"
273+
flutter "${args[@]}"
274+
}
275+
276+
on_exit() {
277+
local status=$?
278+
279+
quit_lantern
280+
if [[ "$status" -ne 0 ]]; then
281+
capture_diagnostics "failure"
282+
fi
283+
detach_dmg
284+
285+
exit "$status"
286+
}
287+
288+
trap on_exit EXIT
289+
290+
mkdir -p "$ARTIFACT_DIR"
291+
reset_lantern_logs
292+
capture_command "systemextensionsctl-list-initial" systemextensionsctl list
293+
294+
app_path="$(resolve_app_path)"
295+
app_executable="$app_path/Contents/MacOS/Lantern"
296+
if [[ ! -x "$app_executable" ]]; then
297+
printf 'Lantern executable not found at %s\n' "$app_executable" >&2
298+
exit 1
299+
fi
300+
301+
if [[ "$RUN_CONNECT_SMOKE" == "true" ]]; then
302+
run_system_extension_preflight "$app_executable"
303+
run_flutter_connect_smoke
304+
else
305+
log_step "Skipping macOS connect smoke test."
306+
fi
307+
308+
quit_lantern
309+
wait_for_packet_tunnel_exit 30
310+
capture_diagnostics "success"

.github/workflows/app-smoke-tests.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ on:
1010
options:
1111
- all
1212
- linux
13+
- macos
1314
- windows
1415
default: all
1516
linux_arch:
@@ -26,6 +27,11 @@ on:
2627
required: false
2728
type: boolean
2829
default: true
30+
macos_connect_smoke:
31+
description: "Include macOS connect/disconnect smoke when platforms=all (requires self-hosted runner with approved system extension)"
32+
required: false
33+
type: boolean
34+
default: false
2935
windows_split_tunnel_website_smoke:
3036
description: "Run Windows split-tunneling website smoke"
3137
required: false
@@ -105,6 +111,35 @@ jobs:
105111
force_full_tunnel_smoke: ${{ inputs.force_full_tunnel_smoke }}
106112
run_auth_smoke: ${{ inputs.auth_smoke }}
107113

114+
macos:
115+
needs: prepare
116+
if: ${{ inputs.platforms == 'macos' || (inputs.platforms == 'all' && inputs.macos_connect_smoke) }}
117+
uses: ./.github/workflows/build-macos.yml
118+
secrets:
119+
AC_USERNAME: ${{ secrets.AC_USERNAME }}
120+
AC_PASSWORD: ${{ secrets.AC_PASSWORD }}
121+
APP_ENV: ${{ secrets.APP_ENV }}
122+
MACOS_BNS_CERT: ${{ secrets.MACOS_BNS_CERT }}
123+
MACOS_BNS_CERT_PASS: ${{ secrets.MACOS_BNS_CERT_PASS }}
124+
MACOS_PROVISION_PROFILE_BASE64: ${{ secrets.MACOS_PROVISION_PROFILE_BASE64 }}
125+
MACOS_PROVISION_TUNNEL_BASE64: ${{ secrets.MACOS_PROVISION_TUNNEL_BASE64 }}
126+
with:
127+
version: ${{ needs.prepare.outputs.version }}
128+
build_type: nightly
129+
installer_base_name: ${{ needs.prepare.outputs.installer_base_name }}
130+
131+
macos-connect-smoke:
132+
needs: [prepare, macos]
133+
if: ${{ inputs.platforms == 'macos' || (inputs.platforms == 'all' && inputs.macos_connect_smoke) }}
134+
uses: ./.github/workflows/macos-connect-smoke.yml
135+
secrets:
136+
APP_ENV: ${{ secrets.APP_ENV }}
137+
with:
138+
version: ${{ needs.prepare.outputs.version }}
139+
build_type: nightly
140+
enable_ip_check: ${{ inputs.enable_ip_check }}
141+
force_full_tunnel_smoke: ${{ inputs.force_full_tunnel_smoke }}
142+
108143
windows:
109144
needs: prepare
110145
if: ${{ inputs.platforms == 'all' || inputs.platforms == 'windows' }}

0 commit comments

Comments
 (0)