README-dev.md
Censorship circumvention tool available for free download on any operating system
Lantern is a censorship circumvention tool built with Flutter on the frontend and Go on the backend. The two layers communicate through a bridge that uses either FFI (macOS, Windows, Linux) or platform channels (iOS, Android).
Core stack:
| Layer | Technology |
|---|---|
| UI | Flutter + Dart |
| State management | Riverpod |
| Navigation | AutoRoute |
| Dependency injection | GetIt |
| Native bridge | Go via gomobile (FFI / platform channels) |
| Wire protocol | Protobuf |
The following tools must be installed and available on your PATH before building any platform target.
| Tool | Required Version | Notes |
|---|---|---|
| Flutter | 3.41.0 (stable) | flutter.dev or fvm |
| Go | 1.25.4 | go.dev/dl or mise |
| Git | any recent | system package manager |
| IDE | — | Android Studio or VS Code with the Flutter extension |
| Xcode | 26.x | Required only for iOS and macOS targets. Install from the Mac App Store. |
| gomobile | latest | Required for all platforms. Install via make install-gomobile. |
The Flutter version is pinned in
pubspec.yamland the Go version is declared ingo.mod. Using mismatched versions will cause build errors.
Verify your setup:
flutter doctor
go version
Platform-specific dependencies (Xcode, Android SDK, Visual Studio, etc.) are listed in each platform section below.
After cloning the repository, install Flutter package dependencies:
flutter pub get
[!IMPORTANT] The app requires an
app.envfile at the repo root to configure API keys and environment-specific settings. Obtainapp.envfrom 1Password and place it at the root of the repository before building.
This resolves the Dart/Flutter packages declared in pubspec.yaml and writes dependency metadata to .dart_tool/. It must be run at least once before any build, and re-run whenever pubspec.yaml or pubspec.lock changes (e.g. after pulling commits that add or update packages).
lantern-core/ is the Go backend that powers all VPN and networking functionality. It is compiled into a native library and embedded into each platform target — as an .xcframework on Apple platforms, an .aar on Android, and a shared .so/.dll on desktop.
The Go backend is compiled into a platform-native library using gomobile. Dart communicates with it through one of two mechanisms depending on the platform:
dart:ffi) — used on desktop platforms (macOS, Windows, Linux). Dart calls C-exported Go functions directly in-process. Lower overhead, synchronous call support.Both paths go through LanternService in Dart, which delegates to either LanternFFIService (desktop) or LanternPlatformService (mobile) based on the current platform.
| Platform | Bridge mechanism | Output artifact |
|---|---|---|
| macOS | FFI via dart:ffi | Liblantern.xcframework |
| iOS | Platform channels (gomobile) | Lantern.xcframework |
| Android | Platform channels (gomobile) | liblantern.aar |
| Windows | FFI via dart:ffi | liblantern.dll |
| Linux | FFI via dart:ffi | liblantern.so |
lantern-core/| Directory | Purpose |
|---|---|
core.go | Entry point — initialises Radiance and wires up subsystems |
ffi/ | FFI entry points exposed to Dart on desktop platforms |
mobile/ | gomobile bindings for iOS and Android |
vpn_tunnel/ | Cross-platform VPN tunnel management |
private-server/ | Private server provisioning and management |
apps/ | Per-platform app-level helpers |
cmd/ | CLI entry points (service binaries) |
stub/ | Stub implementations used in tests |
After making changes inside lantern-core/, rebuild the native library for your target platform before running the Flutter app:
# macOS
make macos
# iOS
make ios
# Android
make android-debug
# Windows
make windows
# Linux
make linux
Xcode 26.x with macOS platform components installed
After installing or updating Xcode, initialize the command-line tools once:
sudo xcodebuild -runFirstLaunch
[!IMPORTANT] A valid provisioning profile is required to build and sign the macOS app. Find the credentials in 1Password under BNS Apple Developer ID, then download and import the profile to Xcode (by going to Signing & Capabilities).
Build the native Go framework (outputs to macos/Frameworks/):
make macos
Run the Flutter desktop app:
flutter run -d macos
Xcode alternative: Open
macos/Runner.xcworkspacedirectly in Xcode to build and run without the CLI.
[!IMPORTANT] A valid provisioning profile is required to build and run on a physical device. Find the credentials in 1Password under BNS Apple Developer ID, then download and import the profile to Xcode (by going to Signing & Capabilities).
Build the native iOS framework (outputs to ios/Frameworks/):
make ios
List available devices and simulators:
flutter devices
Run on a specific device using its ID:
flutter run -d <deviceID>
Xcode alternative: Open
ios/Runner.xcworkspacedirectly in Xcode to build and run without the CLI.
Java 17 or newer — Required by Gradle. Install a JDK distribution such as Eclipse Temurin and ensure JAVA_HOME points to it.
java -version # should print 17.x or higher
Android Studio (or the standalone Android command-line tools) — provides the sdkmanager utility used in the next step.
make install-android-sdk
This installs the following components and accepts all SDK licenses automatically:
| Component | Version |
|---|---|
| Platform | android-35 (API 35) |
| Build tools | 35.0.0 |
| NDK | 27.0.12077973 |
| CMake | 3.22.1 |
After the NDK is installed, set the following environment variables so the build tools can locate it:
export ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/27.0.12077973
export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME
export NDK_HOME=$ANDROID_NDK_HOME
Rather than adding these to your global shell profile, manage them with a repo-local config file:
Installs the necessary libraries and packages required for Android development:
make install-android-deps
Build the native Go AAR library (outputs to android/app/libs/):
make android
List connected devices:
flutter devices
Run on a connected Android device or emulator:
flutter run -d <deviceID>
To build a debug APK directly:
make android-debug
Output APK location:
build/app/outputs/flutter-apk/app-debug.apk
The Windows build separates the backend (a Windows Service binary) from the Flutter UI. During development you can run the backend in console mode instead of registering it as a real service, which makes for a faster iteration loop.
Build the Windows service binary (from an elevated PowerShell):
make windows-service-build
Start the backend in console mode:
.\bin\windows-amd64\lanternsvc.exe --console
Build the native shared library:
make windows
Run the Flutter desktop app:
flutter run -d windows
The Flutter app communicates with the service via a named pipe.
To run the backend as a real Windows Service during development, use the helper scripts from an elevated PowerShell:
| Script | Purpose |
|---|---|
service_install.ps1 | Install and start the service |
service_stop.ps1 | Stop the service |
service_remove.ps1 | Remove the service |
apt package manager for the install stepmake install-linux-deps
Build the Linux release artifacts (.deb package):
make linux-release
Install the .deb package (requires root only for this step):
sudo apt install ./lantern-installer-*.deb
Check the daemon is running:
systemctl status lanternd.service
Run the Flutter app as your normal user:
flutter run -d linux
View daemon logs:
journalctl -u lanternd.service -n 200 --no-pager
sudo systemctl disable --now lanternd.service
sudo apt remove lantern
sudo rm -f /usr/lib/systemd/system/lanternd.service /usr/lib/lantern/lanternd
sudo systemctl daemon-reload
If you want to generate a build for your changes to test, you can trigger a nightly build manually from GitHub Actions against your branch.
nightlyandroid, ios, or all)Note: Triggering from a non-default branch creates a draft release that is automatically deleted after the artifacts are uploaded. You can download the artifacts directly from the workflow run summary before they are cleaned up.
Run all unit and widget tests:
flutter test test/
Run a single test file:
flutter test test/features/vpn/vpn_test.dart
Run with coverage:
flutter test --coverage
Integration tests use the integration_test package with headless widget tests and in-memory fakes.
Run all integration tests:
flutter test integration_test
Run a single integration test file:
flutter test integration_test/private_server_flow_test.dart
End-to-end VPN connect/disconnect test on Linux:
flutter test integration_test/vpn/linux_connect_smoke_test.dart \
-d linux \
--dart-define=DISABLE_SYSTEM_TRAY=true \
--dart-define=ENABLE_IP_CHECK=true
Releases are triggered by pushing a Git tag. CI picks up the tag, determines the build type and target platforms from the tag format, builds all relevant platform artifacts, and publishes a GitHub release.
| Tag | Build type | Platforms |
|---|---|---|
v1.2.3 | Production | All |
v1.2.3-beta | Beta | All |
v1.2.3-android | Production | Android only |
v1.2.3-macos | Production | macOS only |
v1.2.3-ios | Production | iOS only |
v1.2.3-windows | Production | Windows only |
v1.2.3-linux | Production | Linux only |
All platforms — production:
git tag v1.2.3
git push origin v1.2.3
All platforms — beta:
git tag v1.2.3-beta
git push origin v1.2.3-beta
Single platform:
git tag v1.2.3-android
git push origin v1.2.3-android
A nightly build runs automatically every day at 04:00 UTC from the default branch, building all platforms with BUILD_TYPE=nightly. No tag is required. The draft release is deleted after artifacts are uploaded to S3.
The app supports automatic updates on macOS and Windows using the auto_updater package, which is a Flutter-friendly wrapper around the Sparkle update framework.
On startup, the app downloads the appcast.xml feed hosted in the repo and on S3. This file lists the latest version and the signed .dmg or .zip update files. The updater downloads the update and installs it via Sparkle.
The appcast.xml is generated dynamically as part of the release process using a Python script:
python3 scripts/generate_appcast.py
The script:
.dmg and .exe files via the GitHub APIauto_updater:sign_update Dart CLI tool