Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 118 additions & 21 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,28 +46,51 @@ jobs:
restore-keys: |
${{ runner.os }}-flutter-

- name: Cache APT archives
uses: actions/cache@v4
timeout-minutes: 5
continue-on-error: true
with:
path: ~/.cache/apt/archives
key: ${{ runner.os }}-apt-archives-v1-${{ hashFiles('.github/workflows/build-linux.yml') }}
restore-keys: |
${{ runner.os }}-apt-archives-v1-

- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
curl \
git \
unzip \
clang \
cmake \
pkg-config \
libgtk-3-dev \
liblzma-dev \
xz-utils \
ninja-build \
lld \
libstdc++-12-dev \
libgl1-mesa-dev \
libegl1-mesa-dev \
libayatana-appindicator3-dev \
libcurl4-openssl-dev \
libwebkit2gtk-4.1-dev
set -euxo pipefail
APT_ARCHIVE_DIR="$HOME/.cache/apt/archives"
mkdir -p "$APT_ARCHIVE_DIR/partial"
required_packages=(
clang
cmake
pkg-config
libgtk-3-dev
liblzma-dev
libgl1-mesa-dev
libegl1-mesa-dev
libayatana-appindicator3-dev
libcurl4-openssl-dev
libwebkit2gtk-4.1-dev
patchelf
xvfb
)

missing_packages=()
for pkg in "${required_packages[@]}"; do
if ! dpkg -s "$pkg" >/dev/null 2>&1; then
missing_packages+=("$pkg")
fi
done

if [ "${#missing_packages[@]}" -gt 0 ]; then
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
-o dir::cache::archives="$APT_ARCHIVE_DIR" \
"${missing_packages[@]}"
else
echo "All Linux build dependencies are already installed"
fi

- name: Install the ninja build tool
uses: seanmiddleditch/gha-setup-ninja@master
Expand All @@ -77,6 +100,7 @@ jobs:
with:
channel: stable
flutter-version-file: pubspec.yaml
cache: true

- name: Install dependencies
run: make install-linux-deps
Expand All @@ -93,12 +117,85 @@ jobs:
encodedString: ${{ secrets.APP_ENV }}

- name: Build Linux release
run: make linux-release
run: make linux-release-ci
env:
BUILD_TYPE: ${{ inputs.build_type }}
VERSION: ${{ inputs.version }}
INSTALLER_NAME: ${{ inputs.installer_base_name }}

- name: Verify Linux package contents
run: |
./scripts/ci/verify_linux_package.sh "${{ inputs.installer_base_name }}${{ inputs.build_type != 'production' && format('-{0}', inputs.build_type) || '' }}.deb"

- name: Install .deb and verify postinst started daemon
shell: bash
run: |
set -euxo pipefail
deb="./${{ inputs.installer_base_name }}${{ inputs.build_type != 'production' && format('-{0}', inputs.build_type) || '' }}.deb"
test -f "$deb"
sudo apt-get install -y "$deb"

for i in $(seq 1 30); do
if systemctl is-active --quiet lanternd.service; then
break
fi
sleep 1
done

if ! systemctl is-active --quiet lanternd.service; then
sudo systemctl status lanternd.service --no-pager || true
sudo journalctl -u lanternd.service -n 200 --no-pager || true
echo "lanternd.service is not active after package install"
exit 1
fi

if ! systemctl is-enabled --quiet lanternd.service; then
echo "lanternd.service is not enabled after package install"
exit 1
fi

sudo usermod -aG lantern "$USER" || true

systemctl is-active --quiet lanternd.service
test -S /run/lantern/lanternd.sock
sudo stat -c "%a %U %G %n" /run/lantern/lanternd.sock

- name: Installed binary launch smoke
shell: bash
run: |
set -euxo pipefail
code=0
sg lantern -c "env HOME=$HOME PATH=$PATH xvfb-run -a timeout 15s /usr/bin/lantern >/tmp/lantern-installed-smoke.log 2>&1" || code=$?
if [[ "$code" -ne 124 ]]; then
cat /tmp/lantern-installed-smoke.log || true
echo "Installed /usr/bin/lantern did not stay up under xvfb"
exit 1
fi

- name: Linux UI connect/disconnect integration
shell: bash
env:
LANG: en_US.UTF-8
LC_ALL: en_US.UTF-8
run: |
set -euxo pipefail
TEST_START_UTC="$(date -u '+%Y-%m-%d %H:%M:%S')"
sg lantern -c "env PATH=$PATH HOME=$HOME xvfb-run -a flutter test integration_test/vpn/linux_connect_smoke_test.dart -d linux --dart-define=DISABLE_SYSTEM_TRAY=true --dart-define=ENABLE_IP_CHECK=true"
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

CI runs the Linux UI integration test with ENABLE_IP_CHECK=true, which makes the test depend on an external endpoint (api64.ipify.org). This can introduce flaky failures due to transient network/DNS/service issues unrelated to the app behavior. Consider leaving ENABLE_IP_CHECK=false in CI (or making the IP check best-effort/non-fatal) and relying on the connect/disconnect state assertions as the primary signal.

Suggested change
sg lantern -c "env PATH=$PATH HOME=$HOME xvfb-run -a flutter test integration_test/vpn/linux_connect_smoke_test.dart -d linux --dart-define=DISABLE_SYSTEM_TRAY=true --dart-define=ENABLE_IP_CHECK=true"
sg lantern -c "env PATH=$PATH HOME=$HOME xvfb-run -a flutter test integration_test/vpn/linux_connect_smoke_test.dart -d linux --dart-define=DISABLE_SYSTEM_TRAY=true --dart-define=ENABLE_IP_CHECK=false"

Copilot uses AI. Check for mistakes.

sudo journalctl -u lanternd.service --since "$TEST_START_UTC" --no-pager > /tmp/lanternd-journal-ui-smoke.log

if ! grep -Eq 'IPC request.*path=/service/start' /tmp/lanternd-journal-ui-smoke.log; then
echo "Missing /service/start IPC request in lanternd journal"
tail -n 200 /tmp/lanternd-journal-ui-smoke.log || true
exit 1
fi

if ! grep -Eq 'IPC request.*path=/service/stop' /tmp/lanternd-journal-ui-smoke.log; then
echo "Missing /service/stop IPC request in lanternd journal"
tail -n 200 /tmp/lanternd-journal-ui-smoke.log || true
exit 1
fi

- name: Upload Linux build
uses: actions/upload-artifact@v4
with:
Expand Down
63 changes: 53 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ LINUX_LIB_ARM64 := $(BIN_DIR)/linux-arm64/$(LANTERN_LIB_NAME).so
LINUX_LIB_BUILD := $(BIN_DIR)/linux/$(LINUX_LIB)
LINUX_INSTALLER_DEB := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE)).deb
LINUX_INSTALLER_RPM := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE)).rpm
LINUX_INSTALLER_ARCH := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE)).pkg.tar.zst
LINUX_SERVICE_NAME := lanternd
LINUX_SERVICE_SRC := $(RADIANCE_REPO)/cmd/lanternd
LINUX_SERVICE_BUILD_AMD64 := $(BIN_DIR)/linux-amd64/$(LINUX_SERVICE_NAME)
LINUX_SERVICE_BUILD_ARM64 := $(BIN_DIR)/linux-arm64/$(LINUX_SERVICE_NAME)
LINUX_PKG_ROOT := linux/packaging
LINUX_SERVICE_DST := $(LINUX_PKG_ROOT)/usr/sbin
LINUX_PKG_SYSTEMD_DIR := $(LINUX_PKG_ROOT)/usr/lib/systemd/system
LINUX_SYSTEMD_UNIT_SRC := $(shell go list -m -f '{{.Dir}}' $(RADIANCE_REPO))/cmd/lanternd/lanternd.service
LINUX_SYSTEMD_UNIT_DST := $(LINUX_PKG_SYSTEMD_DIR)/lanternd.service

ifeq ($(OS),Windows_NT)
PS := powershell -NoProfile -ExecutionPolicy Bypass -Command
Expand Down Expand Up @@ -168,7 +178,6 @@ install-macos-deps: install-gomobile
brew tap joshdk/tap
brew install joshdk/tap/retry
brew install imagemagick || true
dart pub global activate flutter_distributor

.PHONY: macos
macos: $(MACOS_FRAMEWORK_BUILD)
Expand Down Expand Up @@ -242,7 +251,8 @@ macos-release: clean macos pubget gen build-macos-release sign-app package-macos
.PHONY: install-linux-deps

install-linux-deps:
dart pub global activate flutter_distributor
@command -v nfpm >/dev/null 2>&1 || \
{ echo "Installing nfpm..."; go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.45.0; }

.PHONY: linux-arm64
linux-arm64: $(LINUX_LIB_ARM64)
Expand All @@ -261,23 +271,58 @@ linux: linux-amd64
mkdir -p $(BIN_DIR)/linux
cp $(LINUX_LIB_AMD64) $(LINUX_LIB_BUILD)

.PHONY: linux-service-amd64 linux-service-arm64 stage-linux-service

linux-service-amd64: $(GO_SOURCES)
$(call MKDIR_P,$(dir $(LINUX_SERVICE_BUILD_AMD64)))
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 \
go build -v -trimpath -tags "$(TAGS)" \
-ldflags "-w -s $(EXTRA_LDFLAGS)" \
-o $(LINUX_SERVICE_BUILD_AMD64) $(LINUX_SERVICE_SRC)
@echo "Built Linux service: $(LINUX_SERVICE_BUILD_AMD64)"

linux-service-arm64: $(GO_SOURCES)
$(call MKDIR_P,$(dir $(LINUX_SERVICE_BUILD_ARM64)))
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 \
go build -v -trimpath -tags "$(TAGS)" \
-ldflags "-w -s $(EXTRA_LDFLAGS)" \
-o $(LINUX_SERVICE_BUILD_ARM64) $(LINUX_SERVICE_SRC)
@echo "Built Linux service: $(LINUX_SERVICE_BUILD_ARM64)"

stage-linux-service: linux-service-amd64
@echo "Staging systemd unit + service binary $(LINUX_PKG_ROOT)..."
$(call MKDIR_P,$(LINUX_SERVICE_DST))
$(call COPY_FILE,$(LINUX_SERVICE_BUILD_AMD64),$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME))
$(call MKDIR_P,$(LINUX_PKG_SYSTEMD_DIR))
$(call COPY_FILE,$(LINUX_SYSTEMD_UNIT_SRC),$(LINUX_SYSTEMD_UNIT_DST))

.PHONY: linux-debug
linux-debug:
@echo "Building Flutter app (debug) for Linux..."
flutter build linux --debug

.PHONY: linux-release
linux-release: clean linux pubget gen
.PHONY: linux-release linux-release-ci
linux-release: clean linux-release-ci

linux-release-ci: linux pubget gen
@echo "Building Flutter app (release) for Linux..."
flutter build linux --release $(DART_DEFINES)

cp $(LINUX_LIB_BUILD) build/linux/x64/release/bundle
$(MAKE) stage-linux-service
patchelf --set-rpath '$$ORIGIN/lib' build/linux/x64/release/bundle/lantern || true

flutter_distributor package --build-dart-define=BUILD_TYPE=$(BUILD_TYPE) \
--build-dart-define=VERSION=$(VERSION) --platform linux --targets "deb,rpm" --skip-clean
@echo "Packaging deb, rpm, and archlinux with nfpm..."
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \
nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p deb -t $(LINUX_INSTALLER_DEB)
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \
nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p rpm -t $(LINUX_INSTALLER_RPM)
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \
Comment on lines +316 to +320
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The nfpm packaging step forces VERSION=$(APP_VERSION), which can diverge from the VERSION passed in via CI inputs (and from EXTRA_LDFLAGS / Dart defines that use $(VERSION)). This can lead to mismatched versions between the app/lib and the generated .deb/.rpm metadata. Prefer using $(VERSION) when set (fall back to $(APP_VERSION) only when VERSION is empty) so all artifacts share the same version source.

Suggested change
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \
nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p deb -t $(LINUX_INSTALLER_DEB)
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \
nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p rpm -t $(LINUX_INSTALLER_RPM)
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \
VERSION=$${VERSION:-$(APP_VERSION)} LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \
nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p deb -t $(LINUX_INSTALLER_DEB)
VERSION=$${VERSION:-$(APP_VERSION)} LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \
nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p rpm -t $(LINUX_INSTALLER_RPM)
VERSION=$${VERSION:-$(APP_VERSION)} LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \

Copilot uses AI. Check for mistakes.
Comment on lines +316 to +320
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

nfpm.yaml uses arch: "${GOARCH}", but these nfpm invocations don’t set GOARCH in the environment. Since GOARCH set in other recipes (e.g., linux-amd64) won’t carry over here, packaging can end up with an empty/incorrect arch. Set GOARCH=amd64/arm64 explicitly for each nfpm call (or remove the env substitution).

Suggested change
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \
nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p deb -t $(LINUX_INSTALLER_DEB)
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \
nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p rpm -t $(LINUX_INSTALLER_RPM)
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) \
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) GOARCH=amd64 \
nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p deb -t $(LINUX_INSTALLER_DEB)
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) GOARCH=amd64 \
nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p rpm -t $(LINUX_INSTALLER_RPM)
VERSION=$(APP_VERSION) LANTERND_SRC=$(LINUX_SERVICE_DST)/$(LINUX_SERVICE_NAME) SYSTEMD_UNIT_SRC=$(LINUX_SYSTEMD_UNIT_DST) GOARCH=amd64 \

Copilot uses AI. Check for mistakes.
nfpm package -f $(LINUX_PKG_ROOT)/nfpm.yaml -p archlinux -t $(LINUX_INSTALLER_ARCH)

mv $(DIST_OUT)/$(APP_VERSION)/lantern-$(APP_VERSION)-linux.rpm $(LINUX_INSTALLER_RPM)
mv $(DIST_OUT)/$(APP_VERSION)/lantern-$(APP_VERSION)-linux.deb $(LINUX_INSTALLER_DEB)
.PHONY: verify-linux-package
verify-linux-package:
./scripts/ci/verify_linux_package.sh $(LINUX_INSTALLER_DEB)

# Windows Build
.PHONY: build-lanternsvc-windows windows-service-build \
Expand Down Expand Up @@ -448,7 +493,6 @@ android-release-ci: android pubget gen android-apk-release android-aab-release

install-ios-deps: install-gomobile
npm install -g appdmg
dart pub global activate flutter_distributor

.PHONY: ios
ios: $(IOS_FRAMEWORK_BUILD)
Expand Down Expand Up @@ -571,4 +615,3 @@ delete-data:
# You can install the dart protoc support by running 'dart pub global activate protoc_plugin'
protos:
@protoc --dart_out=lib/lantern/protos protos/auth.proto

47 changes: 47 additions & 0 deletions README-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,47 @@ make macos
flutter run -d macos
```

# Build and run the app on Linux (systemd daemon, no sudo)

1. Build Linux artifacts

```bash
make linux-release
```

2. Install the `.deb` (requires root only for install)

```bash
sudo apt install ./lantern-installer-*.deb
```

3. Check daemon status

```bash
systemctl status lanternd.service
```

4. Run Lantern app as your normal user

```bash
flutter run -d linux
```

Troubleshooting:

```bash
journalctl -u lanternd.service -n 200 --no-pager
```

Uninstall / cleanup:

```bash
sudo systemctl disable --now lanternd.service
sudo apt remove lantern
sudo rm -f /usr/lib/systemd/system/lanternd.service /usr/lib/lantern/lanternd
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The Linux uninstall/cleanup command removes /usr/lib/lantern/lanternd, but the service binary is being packaged to /usr/sbin/lanternd (and the systemd unit is under /usr/lib/systemd/system). Update the cleanup instructions to remove the correct path so developers don’t leave the daemon behind.

Suggested change
sudo rm -f /usr/lib/systemd/system/lanternd.service /usr/lib/lantern/lanternd
sudo rm -f /usr/lib/systemd/system/lanternd.service /usr/sbin/lanternd

Copilot uses AI. Check for mistakes.
sudo systemctl daemon-reload
```

# Build and run the app on Windows

Quick dev loop (run backend in a console)
Expand Down Expand Up @@ -122,6 +163,12 @@ flutter test integration_test
flutter test integration_test/private_server_flow_test.dart
```

### Run Linux VPN connect/disconnect smoke test

```bash
flutter test integration_test/vpn/linux_connect_smoke_test.dart -d linux --dart-define=DISABLE_SYSTEM_TRAY=true --dart-define=ENABLE_IP_CHECK=true
```

# Auto-Updater Integration

The app supports automatic updates on macOS and Windows, using the [auto_updater](https://pub.dev/packages/auto_updater) package, which is a Flutter-friendly wrapper around the Sparkle update framework.
Expand Down
35 changes: 35 additions & 0 deletions assets/locales/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,41 @@ msgstr "Dark"
msgid "system"
msgstr "System"

msgid "unbounded_settings"
msgstr "Unbounded Settings"

msgid "auto_enable_unbounded"
msgstr "Auto-enable Unbounded"

msgid "auto_enable_unbounded_description"
msgstr "Turn on automatically when Lantern is open"

msgid "hide_unbounded"
msgstr "Hide Unbounded"

msgid "hide_unbounded_description"
msgstr "Removes Unbounded from the UI"

msgid "unbounded_welcome_title"
msgstr "Welcome to Unbounded"

msgid "unbounded_welcome_body_primary"
msgstr "Unbounded lets you share a small amount of your internet bandwidth to help people in censored countries access the open web."

msgid "unbounded_welcome_body_secondary"
msgstr "Your connection stays secure and private. You control when sharing is active."

msgid "help_others_bypass_censorship_securely"
msgstr "Help others bypass censorship by securely sharing your connection."

msgid "status_label"
msgstr "Status"

msgid "people_helping_now"
msgstr "People you are helping right now:"

msgid "total_people_helped"
msgstr "Total people helped to date:"



Expand Down
Loading