Skip to content
Merged
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
154 changes: 89 additions & 65 deletions .github/workflows/android-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,103 +6,127 @@ on:
pull_request:
branches: [main]

permissions:
contents: read

concurrency:
group: android-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}

env:
LIBBOX_URL: https://github.com/Vauth/sing-box/releases/download/v1.15.0-foxy-masque.2/libbox.aar
LIBBOX_SHA256: f1b59f4fab207b4c19f8ea3d145bc4dd0e834a530081d0dc045f4b795d6eac0b

jobs:
build:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
persist-credentials: false
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Fetch and verify pinned core
shell: bash
run: |
set -euo pipefail
mkdir -p app/libs
curl --fail --location --retry 3 --connect-timeout 15 --max-time 300 \
--proto '=https' --proto-redir '=https' -o app/libs/libbox.aar "$LIBBOX_URL"
echo "$LIBBOX_SHA256 app/libs/libbox.aar" | sha256sum --check --strict
- uses: gradle/actions/setup-gradle@v4
with:
packages: "platform-tools"

- name: Install NDK
run: sdkmanager --install "ndk;26.1.10909125"

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Grant execute permission for gradlew
run: chmod +x gradlew || true

- name: Generate Gradle wrapper (if missing)
run: gradle wrapper --gradle-version 8.10.2 || true

- name: Assemble debug APK
run: ./gradlew assembleDebug --no-daemon

- name: Upload debug APK
uses: actions/upload-artifact@v4
gradle-version: 8.10.2
- name: Lint and assemble debug
run: gradle :app:lintDebug :app:assembleDebug --no-daemon --console=plain
- uses: actions/upload-artifact@v4
with:
name: foxyvpn-debug-apk
path: app/build/outputs/apk/debug/*.apk
if-no-files-found: error
- name: Upload diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: lint-reports
path: app/build/reports/lint-results-*
if-no-files-found: ignore

release:
needs: build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: write
steps:
- name: Configure keystore environment
run: echo "SIGNING_KEYSTORE_PATH=${{ runner.temp }}/foxyvpn-release.jks" >> "$GITHUB_ENV"
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
persist-credentials: false
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Fetch and verify pinned core
shell: bash
run: |
set -euo pipefail
mkdir -p app/libs
curl --fail --location --retry 3 --connect-timeout 15 --max-time 300 \
--proto '=https' --proto-redir '=https' -o app/libs/libbox.aar "$LIBBOX_URL"
echo "$LIBBOX_SHA256 app/libs/libbox.aar" | sha256sum --check --strict
- uses: gradle/actions/setup-gradle@v4
with:
packages: "platform-tools"

- name: Install NDK
run: sdkmanager --install "ndk;26.1.10909125"

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Grant execute permission for gradlew
run: chmod +x gradlew || true

- name: Generate Gradle wrapper (if missing)
run: gradle wrapper --gradle-version 8.10.2 || true

- name: Decode signing keystore
id: signing
gradle-version: 8.10.2
- name: Require release signing credentials
env:
SIGNING_KEYSTORE_BASE64: ${{ secrets.SIGNING_KEYSTORE_BASE64 }}
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
shell: bash
run: |
if [ -n "$SIGNING_KEYSTORE_BASE64" ]; then
printf '%s' "$SIGNING_KEYSTORE_BASE64" | base64 -d > release-keystore.jks
echo "signed=true" >> "$GITHUB_OUTPUT"
echo "Signing keystore decoded; the release APK will be signed."
else
echo "signed=false" >> "$GITHUB_OUTPUT"
echo "No SIGNING_KEYSTORE_BASE64 secret; the release APK will be unsigned."
fi

- name: Assemble release APK
set -euo pipefail
: "${SIGNING_KEYSTORE_BASE64:?Missing keystore secret}"
: "${SIGNING_KEY_ALIAS:?Missing key alias}"
: "${SIGNING_KEY_PASSWORD:?Missing key password}"
: "${SIGNING_STORE_PASSWORD:?Missing store password}"
umask 077
printf '%s' "$SIGNING_KEYSTORE_BASE64" | base64 --decode > "$SIGNING_KEYSTORE_PATH"
- name: Assemble signed release
env:
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
run: ./gradlew assembleRelease --no-daemon

- name: Remove the decoded keystore
run: gradle :app:assembleRelease --no-daemon --console=plain
- name: Verify APK signatures and generate checksums
shell: bash
run: |
set -euo pipefail
APKSIGNER=$(find "$ANDROID_HOME/build-tools" -name "apksigner" | sort -V | tail -n 1)
for apk in app/build/outputs/apk/release/*.apk; do
"$APKSIGNER" verify --verbose "$apk"
done
cd app/build/outputs/apk/release
sha256sum *.apk > SHA256SUMS
- name: Remove keystore
if: always()
run: rm -f release-keystore.jks

- name: Create GitHub Release
run: rm -f "$SIGNING_KEYSTORE_PATH"
- uses: actions/upload-artifact@v4
with:
name: release-mapping
path: app/build/outputs/mapping/release/mapping.txt
if-no-files-found: error
- name: Publish signed APKs
uses: softprops/action-gh-release@v2
with:
files: app/build/outputs/apk/release/*.apk
files: |
app/build/outputs/apk/release/*.apk
app/build/outputs/apk/release/SHA256SUMS
fail_on_unmatched_files: true
generate_release_notes: true
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ app/build/
*.keystore
*.jks
keystore.properties
app/src/main/jni/
app/libs/*.aar


.kotlin/
115 changes: 60 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div align="center">

# FoxyVPN - Unofficial Firefox VPN client
# FoxyVPN - Unofficial Firefox VPN Client

<p align="center">
<img src="https://i.ibb.co/8D9KdmNk/6a07263d16c0.png" alt="FoxyVPN Logo" width="150"/>
Expand All @@ -9,37 +9,46 @@
![Android](https://img.shields.io/badge/Android-212121?style=for-the-badge&logo=android&logoColor=white)
![Kotlin](https://img.shields.io/badge/Kotlin-212121?style=for-the-badge&logo=kotlin&logoColor=white)
![Compose](https://img.shields.io/badge/Jetpack%20Compose-212121?style=for-the-badge&logo=android&logoColor=white)
![Sing-Box](https://img.shields.io/badge/Sing--Box-212121?style=for-the-badge&logo=go&logoColor=white)
![License](https://img.shields.io/badge/License_MIT-212121?style=for-the-badge)


*FoxyVPN signs in with a Firefox account, obtains a proxy pass from Mozilla's
Guardian service, and carries the whole device's traffic through Firefox VPN's
Fastly edge over an HTTP/2 tunnel. It is a native Kotlin app with a
Jetpack Compose (Material 3) UI.*
*FoxyVPN is a modern, privacy-focused Android client for Mozilla's Firefox VPN service. Powered by a customized sing-box core, it connects directly to Mozilla's global Fastly edge network using high-performance MASQUE protocol with HTTP/2 and HTTP/3 support.*

</div>

<br>

## 🔎 Features

- **System-wide VPN**
- **Firefox account sign-in**
- **Multiple Servers**
- **Split tunneling**
- **Private DNS by default**
- **Encrypted DNS (DNS over HTTPS)**
- **TLS fragmentation**
## ✨ Features

### Core & Privacy
- **Firefox Account Integration**
- **System-Wide Tunneling**
- **Split Tunneling**
- **DNS-over-HTTPS (DoH)**
- **Local Network Bypass**
- **Proxy-Only Mode**

### Performance & Censorship Resistance
- **TLS Fragmentation**
- **uTLS Fingerprint Emulation**
- **Upstream Proxy Chaining**
- **Custom Edge Override**
- **TCP Fast Open (TFO)**
- **Multiplexing Controls**
- **Custom Tunnel MTU**
- **Upstream proxy chaining**
- **Proxy-only mode**
- **Dark / light / follow-system theme**
- **Exit verification**
- **In-app logs**

### User Experience & System Integration
- **Real-Time Diagnostics**
- **Public & Exit IP Detection**
- **Global Locations**
- **Quick Settings Tile & Widget**
- **In-App Auto-Updates**
- **Diagnostic Logging**

<br>

## 📸 Screenshots

<div align="center">
<img src="https://i.ibb.co/cK1L8wGk/90a25df7e3c6.jpg" alt="Screenshot 1" width="30%">
<img src="https://i.ibb.co/tMff3B7C/198e4f622a58.jpg" alt="Screenshot 2" width="30%">
Expand All @@ -48,64 +57,60 @@ Jetpack Compose (Material 3) UI.*

<br>

## 🚀 How to use?
## 🚀 Getting Started

1. **Get a Firefox account.** If you do not have one, create it for free at
[accounts.firefox.com](https://accounts.firefox.com/signup).
2. **Install the APK** from the [Releases](https://github.com/vauth/foxyvpn/releases)
page.
3. **Sign in** with that account's email and password.
4. **Pick a location** and tap the power button.
1. **Get a Firefox account**: If you don't already have one, create an account at [accounts.firefox.com](https://accounts.firefox.com).
2. **Download & Install**: Grab the latest APK matching your device (`arm64-v8a`, `armeabi-v7a`, or `universal`) from the [Releases](https://github.com/Vauth/FoxyVPN/releases) page.
3. **Sign In**: Tap "Sign in with Firefox" to authenticate via the in-app browser.
4. **Connect**: Choose a server location and tap the power button.

> [!NOTE]
> **No subscription is required.** FoxyVPN runs on the free **50 GB of monthly
VPN traffic Mozilla includes with a Firefox account** for browser users, which
is the same allowance the Firefox browser's built-in VPN uses. Once the
allowance is used up for the month, connections stop working until it resets.
> Access, entitlement, quota, and geographic availability are controlled by Mozilla. FoxyVPN connects directly to official endpoints and respects account quotas.

<br>

## 🗃 Requirements
## 🛠 Building from Source

- Android 8.0 (API 26) or newer
### Prerequisites
- JDK 17
- Android SDK 35 and NDK `26.1.10909125`
- `git` on `PATH`

<br>

## ⚙️ Build
* #### Clone Repository:
```bash
git clone https://github.com/vauth/foxyvpn.git
cd foxyvpn
- Android SDK (API 35, Build-Tools 35.0.0)
- Gradle 8.10.2

### Build Instructions
1. Clone the repository:
```bash
git clone https://github.com/Vauth/FoxyVPN.git
cd FoxyVPN
```
* #### Build and Install:
```bash
gradle :app:assembleRelease --no-daemon
```

<br>
2. Download and verify the pinned sing-box core (`libbox.aar`):
```bash
mkdir -p app/libs
curl -L -o app/libs/libbox.aar https://github.com/Vauth/sing-box/releases/download/v1.15.0-foxy-masque.2/libbox.aar
echo "f1b59f4fab207b4c19f8ea3d145bc4dd0e834a530081d0dc045f4b795d6eac0b app/libs/libbox.aar" | sha256sum --check --strict
```

### 📝 To-Do List
- [x] Improve stability.
- [x] Better Logo.
- [ ] HTTP/3 implementation (Not Planned Yet).
3. Build the APKs:
```bash
gradle :app:assembleDebug
```
Output APKs will be located in `app/build/outputs/apk/debug/`.

<br>

## ✍️ Acknowledgements

- [firefox-vpn-client](https://github.com/UjuiUjuMandan/firefox-vpn-client) - the Go reference client this app is a port of
- [hev-socks5-tunnel](https://github.com/heiher/hev-socks5-tunnel) - the native tun2socks engine
- [Conscrypt](https://github.com/google/conscrypt) - ALPN-capable TLS on every supported API level
- [sing-box](https://github.com/SagerNet/sing-box) - the universal proxy platform
- [firefox-vpn-client](https://github.com/UjuiUjuMandan/firefox-vpn-client) - the Go reference client

<br>

## 🛠 Contributing

Contributions are welcome! Feel free to submit a pull request or report an issue.

<br>

## 📄 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
Loading
Loading