Skip to content

Commit ac3f847

Browse files
committed
Merge remote-tracking branch 'origin/main' into atavism/windows-arm
2 parents 44a7915 + f37d138 commit ac3f847

9 files changed

Lines changed: 637 additions & 341 deletions

File tree

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
param(
2+
[string]$ServiceName = "LanternSvc",
3+
[string]$ServiceExe = "build/windows/x64/runner/Release/lanternsvc.exe",
4+
[string]$InstallerPath = "",
5+
[string]$TokenPath = "C:\ProgramData\Lantern\ipc-token",
6+
[string]$TestPath = "integration_test/vpn/windows_connect_smoke_test.dart",
7+
[int]$WaitSeconds = 30,
8+
[int]$InstallerTimeoutSeconds = 180,
9+
[int]$UninstallTimeoutSeconds = 180,
10+
[int]$HeartbeatSeconds = 15,
11+
[switch]$EnableIpCheck,
12+
[switch]$UseInstaller
13+
)
14+
15+
$ErrorActionPreference = "Stop"
16+
17+
function Write-Step {
18+
param([string]$Message)
19+
Write-Host ("[{0}] {1}" -f (Get-Date -Format "HH:mm:ss"), $Message)
20+
}
21+
22+
function Wait-ProcessWithTimeout {
23+
param(
24+
[Parameter(Mandatory = $true)]
25+
[System.Diagnostics.Process]$Process,
26+
[int]$TimeoutSeconds,
27+
[int]$PulseSeconds,
28+
[string]$Description
29+
)
30+
31+
$elapsedSeconds = 0
32+
while (-not $Process.HasExited) {
33+
if ($elapsedSeconds -ge $TimeoutSeconds) {
34+
try {
35+
Stop-Process -Id $Process.Id -Force -ErrorAction SilentlyContinue
36+
} catch {
37+
}
38+
throw "$Description timed out after $TimeoutSeconds seconds"
39+
}
40+
41+
if ($elapsedSeconds -gt 0 -and ($elapsedSeconds % $PulseSeconds) -eq 0) {
42+
Write-Step "$Description still running ($elapsedSeconds/$TimeoutSeconds s)"
43+
}
44+
45+
Start-Sleep -Seconds 1
46+
$elapsedSeconds++
47+
$Process.Refresh()
48+
}
49+
}
50+
51+
function Invoke-ProcessWithTimeout {
52+
param(
53+
[string]$FilePath,
54+
[string[]]$ArgumentList,
55+
[int]$TimeoutSeconds,
56+
[int]$PulseSeconds,
57+
[string]$Description
58+
)
59+
60+
$arguments = $ArgumentList -join " "
61+
Write-Step ("{0}: {1} {2}" -f $Description, $FilePath, $arguments)
62+
$proc = Start-Process -FilePath $FilePath -ArgumentList $ArgumentList -PassThru
63+
Wait-ProcessWithTimeout -Process $proc -TimeoutSeconds $TimeoutSeconds -PulseSeconds $PulseSeconds -Description $Description
64+
$proc.Refresh()
65+
if ($proc.ExitCode -ne 0) {
66+
throw "$Description failed with exit code $($proc.ExitCode)"
67+
}
68+
}
69+
70+
function Get-ServicePathName {
71+
param([string]$Name)
72+
73+
$svc = Get-CimInstance Win32_Service -Filter "Name='$Name'" -ErrorAction SilentlyContinue
74+
if (-not $svc) {
75+
return $null
76+
}
77+
return $svc.PathName
78+
}
79+
80+
function Get-ServiceExecutablePath {
81+
param([string]$PathName)
82+
83+
if ([string]::IsNullOrWhiteSpace($PathName)) {
84+
return $null
85+
}
86+
$trimmed = $PathName.Trim()
87+
if ($trimmed.StartsWith('"')) {
88+
$end = $trimmed.IndexOf('"', 1)
89+
if ($end -gt 1) {
90+
return $trimmed.Substring(1, $end - 1)
91+
}
92+
}
93+
return ($trimmed -split '\s+')[0]
94+
}
95+
96+
function Remove-ServiceIfPresent {
97+
param([string]$Name)
98+
99+
if (Get-Service -Name $Name -ErrorAction SilentlyContinue) {
100+
Write-Step "Stopping existing Windows service $Name"
101+
sc.exe stop $Name | Out-Null
102+
Start-Sleep -Seconds 2
103+
Write-Step "Deleting existing Windows service $Name"
104+
sc.exe delete $Name | Out-Null
105+
Start-Sleep -Seconds 2
106+
}
107+
}
108+
109+
function Wait-ServiceRunning {
110+
param(
111+
[string]$Name,
112+
[int]$TimeoutSeconds
113+
)
114+
115+
for ($i = 0; $i -lt $TimeoutSeconds; $i++) {
116+
$service = Get-Service -Name $Name -ErrorAction SilentlyContinue
117+
if ($service -and $service.Status -eq "Running") {
118+
Write-Step "Windows service $Name is Running"
119+
return
120+
}
121+
if ($i -gt 0 -and ($i % 5) -eq 0) {
122+
Write-Step "Waiting for service $Name to be Running ($i/$TimeoutSeconds s)"
123+
}
124+
Start-Sleep -Seconds 1
125+
}
126+
127+
sc.exe query $Name
128+
throw "Windows service did not reach Running state"
129+
}
130+
131+
function Wait-TokenFile {
132+
param(
133+
[string]$Path,
134+
[int]$TimeoutSeconds
135+
)
136+
137+
for ($i = 0; $i -lt $TimeoutSeconds; $i++) {
138+
if (Test-Path $Path) {
139+
Write-Step "IPC token detected at $Path"
140+
return
141+
}
142+
if ($i -gt 0 -and ($i % 5) -eq 0) {
143+
Write-Step "Waiting for IPC token at $Path ($i/$TimeoutSeconds s)"
144+
}
145+
Start-Sleep -Seconds 1
146+
}
147+
throw "IPC token file not found at $Path"
148+
}
149+
150+
function Install-FromInstaller {
151+
param(
152+
[string]$Path,
153+
[int]$TimeoutSeconds,
154+
[string]$Name
155+
)
156+
157+
if ([string]::IsNullOrWhiteSpace($Path)) {
158+
throw "InstallerPath must be set when -UseInstaller is enabled"
159+
}
160+
$resolvedInstaller = (Resolve-Path $Path).Path
161+
Invoke-ProcessWithTimeout `
162+
-FilePath $resolvedInstaller `
163+
-ArgumentList @("/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/SP-") `
164+
-TimeoutSeconds $InstallerTimeoutSeconds `
165+
-PulseSeconds $HeartbeatSeconds `
166+
-Description "Running installer"
167+
168+
Write-Step "Waiting for Windows service after installer"
169+
Wait-ServiceRunning -Name $Name -TimeoutSeconds $TimeoutSeconds
170+
}
171+
172+
function Uninstall-FromInstalledService {
173+
param(
174+
[string]$Name
175+
)
176+
177+
$pathName = Get-ServicePathName -Name $Name
178+
$svcExe = Get-ServiceExecutablePath -PathName $pathName
179+
if (-not $svcExe) {
180+
return
181+
}
182+
183+
$installDir = Split-Path -Path $svcExe -Parent
184+
if (-not (Test-Path $installDir)) {
185+
return
186+
}
187+
188+
$uninstaller = Get-ChildItem -Path $installDir -Filter "unins*.exe" -ErrorAction SilentlyContinue |
189+
Sort-Object -Property Name |
190+
Select-Object -First 1
191+
192+
if (-not $uninstaller) {
193+
return
194+
}
195+
196+
Invoke-ProcessWithTimeout `
197+
-FilePath $uninstaller.FullName `
198+
-ArgumentList @("/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/SP-") `
199+
-TimeoutSeconds $UninstallTimeoutSeconds `
200+
-PulseSeconds $HeartbeatSeconds `
201+
-Description "Running uninstaller"
202+
}
203+
204+
try {
205+
if ($UseInstaller) {
206+
Write-Step "Smoke setup mode: installer"
207+
Install-FromInstaller -Path $InstallerPath -TimeoutSeconds $WaitSeconds -Name $ServiceName
208+
} else {
209+
Write-Step "Smoke setup mode: direct service binary"
210+
$resolvedServiceExe = (Resolve-Path $ServiceExe).Path
211+
Remove-ServiceIfPresent -Name $ServiceName
212+
Write-Step "Creating Windows service from $resolvedServiceExe"
213+
sc.exe create $ServiceName binPath= "`"$resolvedServiceExe`"" start= demand DisplayName= "Lantern Service (CI)" | Out-Null
214+
Write-Step "Starting Windows service $ServiceName"
215+
sc.exe start $ServiceName | Out-Null
216+
Wait-ServiceRunning -Name $ServiceName -TimeoutSeconds $WaitSeconds
217+
}
218+
219+
Wait-TokenFile -Path $TokenPath -TimeoutSeconds $WaitSeconds
220+
221+
$flutterArgs = @(
222+
"test",
223+
$TestPath,
224+
"-d",
225+
"windows",
226+
"--reporter=expanded",
227+
"--dart-define=DISABLE_SYSTEM_TRAY=true"
228+
)
229+
if ($EnableIpCheck) {
230+
$flutterArgs += "--dart-define=ENABLE_IP_CHECK=true"
231+
}
232+
233+
Write-Step ("Running Windows connect smoke test: flutter {0}" -f ($flutterArgs -join " "))
234+
& flutter @flutterArgs
235+
if ($LASTEXITCODE -ne 0) {
236+
throw "Windows connect smoke test failed with exit code $LASTEXITCODE"
237+
}
238+
}
239+
finally {
240+
try {
241+
Write-Step "Starting cleanup"
242+
if ($UseInstaller) {
243+
Uninstall-FromInstalledService -Name $ServiceName
244+
} else {
245+
Remove-ServiceIfPresent -Name $ServiceName
246+
}
247+
Write-Step "Cleanup finished"
248+
} catch {
249+
Write-Warning ("Failed to clean up service {0}: {1}" -f $ServiceName, $_)
250+
}
251+
}

.github/workflows/build-windows.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ on:
1717
required: false
1818
type: boolean
1919
default: false # when true, uses self-signed certificate
20+
enable_ip_check:
21+
description: "Enable public IP change validation in Windows smoke test"
22+
required: false
23+
type: boolean
24+
default: false
2025

2126
jobs:
2227
build-windows:
@@ -183,6 +188,22 @@ jobs:
183188
184189
Move-Item "dist/$env:APP_VERSION/$env:APP_NAME-$env:APP_VERSION-windows-setup.exe" "$env:FULL_INSTALLER_NAME.exe"
185190
191+
- name: Windows installer + UI connect/disconnect integration
192+
shell: pwsh
193+
timeout-minutes: 20
194+
env:
195+
FULL_INSTALLER_NAME: ${{ inputs.installer_base_name }}${{ inputs.build_type != 'production' && format('-{0}', inputs.build_type) || '' }}
196+
run: |
197+
$smokeArgs = @(
198+
"-UseInstaller",
199+
"-InstallerPath", "$env:FULL_INSTALLER_NAME.exe"
200+
)
201+
if ("${{ inputs.enable_ip_check }}" -eq "true") {
202+
$smokeArgs += "-EnableIpCheck"
203+
}
204+
205+
./.github/scripts/windows_connect_smoke.ps1 @smokeArgs
206+
186207
- name: Sign installer
187208
shell: pwsh
188209
env:

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ jobs:
250250
version: ${{ needs.set-metadata.outputs.version }}
251251
build_type: ${{ needs.set-metadata.outputs.build_type }}
252252
installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }}
253+
enable_ip_check: ${{ needs.set-metadata.outputs.build_type == 'nightly' }}
253254

254255
build-linux:
255256
needs: [set-metadata, release-create, release-approval]

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ $(MACOS_FRAMEWORK_BUILD): $(GO_SOURCES)
199199
-tags=$(TAGS),netgo -trimpath \
200200
-target=macos \
201201
-o $(MACOS_FRAMEWORK_BUILD) \
202-
-ldflags="-w -s -checklinkname=0" \
202+
-ldflags="-w -s -checklinkname=0 $(EXTRA_LDFLAGS)" \
203203
$(GOMOBILE_REPOS)
204204
@echo "Built macOS Framework: $(MACOS_FRAMEWORK_BUILD)"
205205
rm -rf $(MACOS_FRAMEWORK_DIR)/$(MACOS_FRAMEWORK)
@@ -506,7 +506,7 @@ build-android: check-gomobile
506506
-javapkg=lantern.io \
507507
-tags=$(TAGS) -trimpath \
508508
-o=$(ANDROID_LIB_BUILD) \
509-
-ldflags="$(ANDROID_GOMOBILE_LDFLAGS)" \
509+
-ldflags="$(ANDROID_GOMOBILE_LDFLAGS) $(EXTRA_LDFLAGS)" \
510510
$(GOMOBILE_REPOS)
511511

512512
cp $(ANDROID_LIB_BUILD) $(ANDROID_LIBS_DIR)
@@ -568,7 +568,7 @@ build-ios:
568568
-tags=$(TAGS),with_low_memory, -trimpath \
569569
-target=ios \
570570
-o $(IOS_FRAMEWORK_BUILD) \
571-
-ldflags="-w -s -checklinkname=0" \
571+
-ldflags="-w -s -checklinkname=0 $(EXTRA_LDFLAGS)" \
572572
$(GOMOBILE_REPOS)
573573
@echo "Built iOS Framework: $(IOS_FRAMEWORK_BUILD)"
574574
mv $(IOS_FRAMEWORK_BUILD) $(IOS_FRAMEWORK_DIR)

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ replace github.com/refraction-networking/water => github.com/getlantern/water v0
2323
require (
2424
github.com/Microsoft/go-winio v0.6.2
2525
github.com/alecthomas/assert/v2 v2.3.0
26+
github.com/getlantern/common v1.2.1-0.20260224184656-5aefb9c21c85
2627
github.com/getlantern/lantern-server-provisioner v0.0.0-20251031121934-8ea031fccfa9
27-
github.com/getlantern/radiance v0.0.0-20260318132022-04e4c564c823
28+
github.com/getlantern/radiance v0.0.0-20260318170544-725ad379e90b
2829
github.com/sagernet/sing-box v1.12.22
2930
golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0
3031
golang.org/x/sys v0.40.0
@@ -172,7 +173,6 @@ require (
172173
github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52 // indirect
173174
github.com/getlantern/amp v0.0.0-20260305201851-782bc8045e58 // indirect
174175
github.com/getlantern/appdir v0.0.0-20250324200952-507a0625eb01 // indirect
175-
github.com/getlantern/common v1.2.1-0.20260224184656-5aefb9c21c85 // indirect
176176
github.com/getlantern/dnstt v0.0.0-20260112160750-05100563bd0d // indirect
177177
github.com/getlantern/fronted v0.0.0-20260316001628-14e5f21104b5 // indirect
178178
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect

go.sum

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,8 @@ github.com/getlantern/osversion v0.0.0-20240418205916-2e84a4a4e175 h1:JWH5BB2o0e
277277
github.com/getlantern/osversion v0.0.0-20240418205916-2e84a4a4e175/go.mod h1:h3S9LBmmzN/xM+lwYZHE4abzTtCTtidKtG+nxZcCZX0=
278278
github.com/getlantern/pluriconfig v0.0.0-20251126214241-8cc8bc561535 h1:rtDmW8YLAuT8r51ApR5z0d8/qjhHu3TW+divQ2C98Ac=
279279
github.com/getlantern/pluriconfig v0.0.0-20251126214241-8cc8bc561535/go.mod h1:WKJEdjMOD4IuTRYwjQHjT4bmqDl5J82RShMLxPAvi0Q=
280-
github.com/getlantern/radiance v0.0.0-20260318124306-1e77cc653668 h1:KnSUKcpA3nVf/Xd4gnRAjb5NIJGwUFtC59pMgqz4NlE=
281-
github.com/getlantern/radiance v0.0.0-20260318124306-1e77cc653668/go.mod h1:QG9fHG/GresYdPa3Tr5gYeg8yDkE+crdyvg6KXChQdQ=
282-
github.com/getlantern/radiance v0.0.0-20260318132022-04e4c564c823 h1:DaqyTQI7o4pp/Wgr87k5C7CYrZBTZxDdYKWDjeQBWiw=
283-
github.com/getlantern/radiance v0.0.0-20260318132022-04e4c564c823/go.mod h1:QG9fHG/GresYdPa3Tr5gYeg8yDkE+crdyvg6KXChQdQ=
280+
github.com/getlantern/radiance v0.0.0-20260318170544-725ad379e90b h1:TYLWfqi5L3ijG0TdonpA4M0kgzWRKgndy7OIUqn8WMs=
281+
github.com/getlantern/radiance v0.0.0-20260318170544-725ad379e90b/go.mod h1:QG9fHG/GresYdPa3Tr5gYeg8yDkE+crdyvg6KXChQdQ=
284282
github.com/getlantern/samizdat v0.0.3-0.20260310125445-325cf1bd1b60 h1:m9eXjDK9vllbVH467+QXbrxUFFM9Yp7YJ90wZLw4dwU=
285283
github.com/getlantern/samizdat v0.0.3-0.20260310125445-325cf1bd1b60/go.mod h1:uEeykQSW2/6rTjfPlj3MTTo59poSHXfAHTGgzYDkbr0=
286284
github.com/getlantern/sing v0.7.18-lantern h1:QKGgIUA3LwmKYP/7JlQTRkxj9jnP4cX2Q/B+nd8XEjo=

0 commit comments

Comments
 (0)