outline-apps Documentation

Outline Client and Manager. Outline Manager makes it easy to create your own VPN server. Outline Client lets you share access to your VPN with anyone in...

9,264 stars TypeScript TypeScript 16 files ยท ~12,526 tokens #android#chromeos#cordova#electron#ios#linux#macos#outline
RAW Doc

Technical Documentation: OutlineFoundation/outline-apps

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Overview (`README.md`)

  Outline Apps  

Outline makes it easy for anyone to create a VPN server, allowing you to share access to the free and open internet with those in need. We have two core applications:

      Outline Manager (/server_manager): A graphical user interface for managing Outline servers. It is available for Windows, macOS, and Linux. You can install the manager here. See server_manager/README.md for more information.

      Outline Client (/client): A cross-platform proxy client for Windows, macOS, iOS, Android, and Linux. The Outline Client is designed for use with the server deployed with the Outline Manager, but it is also fully compatible with any Shadowsocks server. You can install the client here. See client/README.md for more information.

Community and Support

Interested in contributing to Outline? See our Contributing Guide for more information.

See AGENTS.md for AI agent and developer guidance.

You can also join the Outline Community by signing up for the IFF Mattermost!

For customer support and to contact us directly, go to https://support.getoutline.org.


Overview (`client/README.md`)

  Outline Client

The Outline Client is a cross-platform VPN or proxy client for Windows, macOS, iOS, Android, and Debian-based Linux. The Outline Client is designed for use with the Outline Server software, but it is fully compatible with any Shadowsocks server.

The client's user interface is implemented in Polymer 2.0. Platform support is provided by Cordova and Electron, with additional native components in this repository.

Requirements for all builds

All builds require Node 22 (lts/hydrogen), and Go 1.22 installed in addition to other per-platform requirements.

๐Ÿ’ก NOTE: if you have nvm installed, run nvm use to switch to the correct node version!

After cloning this repo, install all node dependencies:

sh
npm install

Building the shared web app

Outline clients share the same web app across all platforms. This code is located in the web directory. If you are making changes to the shared web app and do not need to test platform-specific functionality, you can test in a desktop browser by running:

sh
npm run action client/web/start

The latter command will open a browser instance running the app. Browser platform development will use fake servers to test successful and unsuccessful connections.

The app logic is located in web/app. UI components are located in web/ui_components. If you want to work specifically on an individual UI element, try the storybook!:

sh
npm run action storybook

[!NOTE]
Every script in this repository can be run with npm run action -
for a CLI-like experience, add something like

sh
alias outline="npm run action"

(you can call it whatever you like)

to your shell, then try outline www/start!

Passing configuration flags to actions

Certain actions take configuration flags - but since we're running them through npm, you'll have to use the -- seperator to funnel them through to the underlying process. For example, to set up a MacOS project in release mode, you'd run:

sh
SENTRY_DSN=<your sentry dsn> npm run action client/src/cordova/setup macos -- --buildMode=release --versionName=<your version name>

Life of a Packet

How does the Outline Client work?

Accepting a server invite

Looking for instructions on how to accept a server invite?

Platform-specific development

Each platform is handled differently:

  1. Developing for Apple (MacOS and iOS)
  2. Developing for Android
  3. Developing for Electron (Windows and Linux)

Error reporting

To enable error reporting through Sentry for local builds, run:

bash
export SENTRY_DSN=[Sentry development API key]
[platform-specific build command]

Release builds on CI are configured with a production Sentry API key.


Overview (`server_manager/README.md`)

  Outline Manager

Running

To run the Outline Manager Electron app:

text
npm run action server_manager/electron/start ${PLATFORM}

To run the Outline Manager Electron app with a development build (code not minified):

text
BUILD_ENV=development npm run action server_manager/electron/start ${PLATFORM}

Where ${PLATFORM} is one of linux, macos, windows.

Development Server

To run the Outline Manager as a web app on the browser and listen for changes:

text
npm run action server_manager/www/start

Updating Cloud Locations

When new cloud regions/zones appear (especially for GCP), update all of the
following so the location picker shows proper city and country data.

  1. Add geolocation constants in server_manager/model/location.ts.
  2. Map cloud region IDs to GeoLocation in server_manager/model/gcp.ts
    (Zone.LOCATION_MAP).
  3. Add localized city message keys:
    • Source messages: server_manager/messages/master_messages.json using
      geo_* keys.
    • Runtime English messages: server_manager/messages/en.json and
      server_manager/messages/en-GB.json using geo-* keys.
  4. Keep all geo_* / geo-* message keys alphabetically sorted.
  5. Update tests:
    • server_manager/model/gcp.spec.ts region coverage list and assertions.
    • server_manager/www/location_formatting.spec.ts if formatting/sorting
      behavior changes.
  6. Verify:
bash
npx tsc -p server_manager --outDir output/build/js/server_manager --module commonjs
npx jasmine output/build/js/server_manager/model/gcp.spec.js output/build/js/server_manager/www/location_formatting.spec.js
npm run action server_manager/www/test

Notes:

  • Missing entries in Zone.LOCATION_MAP cause unknown location cards (? and
    raw zone IDs) in the picker.
  • Region discovery can be checked programmatically with:
bash
gcloud compute regions list --format="value(name)"
gcloud compute zones list --format="value(name)"

Debug an existing binary

You can run an existing binary in debug mode by setting OUTLINE_DEBUG=true.
This will enable the Developer menu on the application window.

Packaging

To build the app binary:

text
npm run action server_manager/electron/package ${PLATFORM} -- --buildMode=[debug,release]

Where ${PLATFORM} is one of linux, macos, windows.

The per-platform standalone apps will be at output/build/server_manager/electron/static/dist.

  • Windows: An .exe file. Only generated if you have wine installed.
  • Linux: An .AppImage file.
  • macOS: A .dmg and a .zip file as required by auto update.

NOTE: If you are building for macOS, you may need to run security unlock-keychain login.keychain so electron-builder has access to your certificates.

Testing

text
npm run action server_manager/test

Error reporting

To enable error reporting through Sentry for local builds, run:

bash
export SENTRY_DSN=[Sentry DSN URL]
npm run action server_manager/electron/start ${PLATFORM}

Overview (`client/capacitor/README.md`)

Capacitor client

This directory is the Capacitor-based client shell. These instructions cover the browser dev workflow (webpack bundle under client/capacitor/www/) and the native iOS and Android builds.

Run all npm run action commands from the repository root.

Requirements (browser)

  • Node.js 22 (see root package.json engines)

  • Install dependencies after clone:

    sh
    npm ci

    If you use nvm, run nvm use in the repo root so the Node version matches.

Start (development server)

Runs a browser build once, then starts the webpack dev server (HMR):

sh
npm run action client/capacitor/start
  • URL: http://localhost:8080 (server binds to 0.0.0.0:8080).
  • Live reload: edit sources under client/web/ and other bundled paths; the UI updates as you save.

Note: In pure browser mode, Capacitor native plugins are not available. The app uses a browser MethodChannel shim for a subset of behavior (for example parsing common static access keys). Use this for UI and web-layer development.

Build (browser)

The web build action accepts browser (default), ios, or android (see web_build.action.mjs); the native platforms use it as the first step of their full build.

Debug (default):

sh
npm run action client/capacitor/web_build

Release: pass --buildMode=release and a version, with SENTRY_DSN set in the environment. This builds the bundle with webpack in production mode:

sh
SENTRY_DSN=<dsn> npm run action client/capacitor/web_build android -- --buildMode=release --versionName=<version>

Output

Artifacts land in client/capacitor/www/, including for example:

  • index.html, bundle.js
  • environment.json (version and build numbers, and the Sentry DSN if set)
  • Copied assets: messages/, assets/, etc. (see webpack.config.js)

App icons and splash screens

client/capacitor/assets/ is the single source of truth for launcher icons and splash screens on Android and iOS. Everything under android/app/src/main/res/mipmap-*, the drawable-* splash variants, and the iOS asset catalog (ios/App/App/Assets.xcassets) is generated from it โ€” do not edit those files by hand.

The source files follow the @capacitor/assets input convention:

File Purpose
assets/icon.png Base app icon (1024ร—1024)
assets/icon-only.png Icon without background (used where the platform composes its own shape)
assets/icon-foreground.png Android adaptive icon foreground layer
assets/icon-background.png Android adaptive icon background layer
assets/splash.png Splash screen (light mode, 2732ร—2732)
assets/splash-dark.png Splash screen (dark mode)

To regenerate the platform resources after changing any source image:

sh
cd client/capacitor
npm run assets:generate

The script runs capacitor-assets generate once per platform rather than a single --android --ios invocation: combining the flags changes the generated Android PNGs (the platform runs are not isolated inside the tool), which would break reproducibility against the committed res/ files.

then re-add license headers to the regenerated adaptive-icon XMLs (from the repo root):

sh
go tool task addlicense

The web/browser build does not use this directory โ€” its favicon and images come from client/web/ (see webpack.config.js). If a PWA target is ever added, @capacitor/assets can generate its icons too (--pwa).

iOS (device or simulator)

Use this flow when you need the real Outline iOS plugin (VPN, native method channel, and so on). All npx cap commands below are run from client/capacitor (the directory that contains capacitor.config.json and ios/).

Requirements (iOS)

  • A Mac with Xcode installed, including the Xcode command-line tools, and a recent iOS SDK.
  • Native iOS dependencies are managed with Swift Package Manager (see ios/App/CapApp-SPM/Package.swift, generated by cap sync); CocoaPods is not used.
  • A physical device with a valid provisioning profile/signing setup, or an iOS Simulator runtime installed via Xcode.
  • Go on your PATH (npm run capacitor:sync:before runs go tool task to build the tun2socks xcframework; see package.json in this directory).

Build the debug app (one command)

From the repository root:

sh
npm run action client/capacitor/build ios

This runs the full build: the web bundle, cap sync ios (tun2socks + native sync), and an unsigned xcodebuild of ios/App/App.xcodeproj for a generic iOS device. It is what CI runs. Because it disables code signing, the resulting app cannot be installed on a device; to run the app, use the steps below instead.

Build the release

Releases are built and published by the scripts in outline-release, which supply the Sentry DSN and the development team and call this action:

sh
npm run action client/capacitor/build ios -- --buildMode=release --versionName=<version>

It reads SENTRY_DSN and DEVELOPMENT_TEAM from the environment, and needs that team's signing identity set up in Xcode. It runs xcodebuild archive with the given version and the build number (hours since the Unix epoch), and leaves the signed archive at output/client/ios/Outline.xcarchive.

Steps to build and start the app

  1. Build the web bundle (www/), from the repository root:

    sh
    npm run action client/capacitor/web_build ios
  2. Go to the Capacitor project directory:

    sh
    cd client/capacitor
  3. Sync the iOS project (runs capacitor:sync:before: Go tun2socks; then copies the web assets built in step 1 into the native app and refreshes the Swift package manifest for the plugins):

    sh
    npx cap sync ios
  4. Install and launch on the default device or running simulator:

    sh
    npx cap run ios

Optional Capacitor CLI commands (iOS)

  • Open in Xcode (inspect build settings, configure signing, run/debug from the IDE):

    sh
    cd client/capacitor
    npx cap open ios
  • Web-only iteration (faster when you did not change native code or Info.plist): copy assets without refreshing native dependencies. Build www/ first, then copy:

    sh
    npm run action client/capacitor/web_build ios
    cd client/capacitor
    npx cap copy ios

    Use npx cap sync ios again whenever you change native plugins, Swift package dependencies, or Info.plist entries.

  • Sanity check the iOS toolchain from Capacitor's point of view:

    sh
    cd client/capacitor
    npx cap doctor ios

Android (device or emulator)

Use this flow when you need the real Outline Android plugin (VPN, native method channel, and so on). All npx cap commands below are run from client/capacitor (the directory that contains capacitor.config.json and android/).

Requirements (Android)

  • Android Studio with a recent Android SDK (match the versions in client/capacitor/android/variables.gradle and root Gradle files).
  • JDK 21 โ€” the Android Gradle build compiles against Java 21 (see client/capacitor/android/app/build.gradle). This differs from the Cordova Android client, which builds with JDK 17.
  • A physical device with USB debugging or an AVD emulator.
  • Go on your PATH (npm run capacitor:sync:before runs go tool task for tun2socks and Android configure; see package.json in this directory).

Build the debug APK (one command)

From the repository root:

sh
npm run action client/capacitor/build android

This runs the full build: the web bundle, cap sync android (tun2socks + native sync), and gradlew assembleDebug. It is what CI runs. To also install and launch the app on a device, use the steps below instead.

Build the release

Releases are built and published by the scripts in outline-release, which supply the signing keystore and the Sentry DSN and call this action:

sh
npm run action client/capacitor/build android -- --buildMode=release --versionName=<version>

It reads SENTRY_DSN, ANDROID_KEY_STORE_CONTENTS (a base64-encoded PKCS#12 keystore whose key alias is privatekey), ANDROID_KEY_STORE_PASSWORD and JAVA_HOME (JDK 21) from the environment. It leaves the signed app-release.aab and universal.apk (built from the AAB with bundletool, and checked for 16 KB alignment) in client/capacitor/android/app/build/outputs/bundle/release/.

Steps to build and start the app

  1. Build the web bundle (www/), from the repository root:

    sh
    npm run action client/capacitor/web_build android
  2. Go to the Capacitor project directory:

    sh
    cd client/capacitor
  3. Sync the Android project (runs capacitor:sync:before: Go tun2socks, Android configure; then copies the web assets built in step 1 into the native app and refreshes plugins):

    sh
    npx cap sync android
  4. Install and launch on the default device or running emulator:

    sh
    npx cap run android

Optional Capacitor CLI commands (Android)

  • Open in Android Studio (inspect Gradle, run/debug from the IDE):

    sh
    cd client/capacitor
    npx cap open android
  • Web-only iteration (faster when you did not change native code or AndroidManifest.xml): copy assets without refreshing native Gradle dependencies. Build www/ first, then copy :

    sh
    cd client/capacitor
    npm run build:web
    npx cap copy android

    Use npx cap sync android again whenever you change native plugins, Gradle, or manifest entries.

  • Sanity check the Android toolchain from Capacitorโ€™s point of view:

    sh
    cd client/capacitor
    npx cap doctor android

Overview (`client/electron/README.md`)

Electron Development Instructions

Unlike the Android and Apple clients, the Windows and Linux clients use the Electron framework, rather than Cordova.

To build the Electron clients, run (it will also package an installer executable into output/client/electron/build):

sh
npm run action client/electron/build [windows|linux] -- --arch=[x64|ia32|arm64]

To run the Electron clients, run:

sh
npm run action client/electron/start [windows|linux] -- --arch=[x64|ia32|arm64]

Cross-Compiling

To build the app for a platform target on a different host target, you will need a cross-compiler. We use zig to cross-compile with cgo.

Install zig and make sure it's in the PATH.

You can download the binary tarball, or use a package manager, like Homebrew:

sh
brew install zig

On an M-series ARM Macs it is necessary to install Rosetta to allow the chip to run x86_64 commands. (A code-signing step in our windows build uses an intel version of wine.)

sh
softwareupdate --install-rosetta --agree-to-license

Requirements

Windows

  • Visual Studio 2022 or later

If you see an error about node-gyp not finding Visual Studio, try one of the following:

  • (Recommended) Install the VSSetup PowerShell package using Install-Module VSSetup -Scope CurrentUser
  • Change PowerShell LanguageMode using $ExecutionContext.SessionState.LanguageMode="FullLanguage"

Linux

Release

To build the release version of Windows installer, you'll also need:

  • Java 8+ Runtime. This is required for the cross-platform Windows executable signing tool Jsign. If you don't need to sign the executables, feel free to skip this.

Electron Builder

The Electron Builder config uses an asarUnpack entry. This entry
lists files for resources that must be extracted from the ASAR archive. This is necessary for files that the operating system needs to access directly, such as executable binaries that your application runs or other native modules.

To find where unpacked files are used, look for calls to unpackedAppPath() (example).

It's helpful to list the ASAR contents to understand what's in there and what patterns to use.

For the Linux client:

sh
npx asar list 'output/client/electron/build/linux-unpacked/resources/app.asar'

For the Windows client:

sh
npx asar list 'output/client/electron/build/win-ia32-unpacked/resources/app.asar'

Overview (`client/electron/windows/smartdnsblock/README.md`)

`smartdnsblock`

smartdnsblock.exe is a Windows only utility application.

How to Build

We recommend to use Visual Studio 2019+ for building. But if it is not applicable to do so (for example, when in a CI server), feel free to download the standalone build tools without Visual Studio.

The simplest way to build this application is by double clicking tools/smartdnsblock/smartdnsblock.sln which will open the Visual Studio GUI, and then:

  • Note that there are two dropdown buttons in the toolbar area:
    • For "Solution Configurations" dropdown, choose "Release"
    • For "Solution Platforms" dropdown , choose "x86"
  • Use menu item "Build โ†’ Build Solution" to build the project
  • smartdnsblock.exe will be generated in tools/smartdnsblock/bin folder

You can also build it through command line tools without launching Visual Studio:

  • Launch "Developer PowerShell for VS 2019" from Start Menu
  • cd <working-dir>/tools/smartdnsblock
  • msbuild smartdnsblock.sln /p:Configuration=Release /p:Platform=x86
  • smartdnsblock.exe will be generated in tools/smartdnsblock/bin folder

Overview (`client/go/outline/configregistry/README.md`)

Outline Config Registry

Overview

The Outline Config framework is a composable and extensible system to specify network strategies:

  • Composable because it lets strategies to be combined to build more advanced strategies.
    For example, Shadowsocks-over-Websocket, or multi-hop.
  • Extensible because it's possible to register new strategies without having to change the
    framework.

This framework underpins how the Outline client understands and establishes connections through various proxy protocols and combinations.

Configuration Format: YAML

The config framework is based on YAML. JSON is a strict subset of YAML, so JSON notation can be used
as well. YAML was chosen for several reasons that enhance readability and maintainability:

  • Removes the need for quotes in field names and many string values.
  • Allows the use of comments.
  • Has anchors, which can reduce duplication.

We use the github.com/goccy/go-yaml library for parsing. This library was chosen after encountering issues with bugs in corner cases with the gopkg.in/yaml.v3 library.

Core concepts

A ParseFunc[T any] is a generic function that defines how to transform a configuration (typically a YAML config pre-parsed as a map, list or primitive type) into a specific Go type T.

go
type ParseFunc[T any] func(ctx context.Context, input ConfigNode) (OutputType, error)

The Context was added to enable parse-time context and sharing between parsers. This is useful for
implementing features like named objects in the config that can be referred to by other parts of the configuration.

A TypeParser[T any] is a utility that manages the parsing of a specific type T which can have multiple implementations or "subtypes".
It allows for the registration of different subparsers (ParseFuncs), each associated with a unique subtype name.

When a TypeParser encounters a YAML map with a $type attribute, it uses the value of $type to look up the corresponding registered subparser and delegates the parsing of that map to it.

Example of $type usage in YAML:

yaml
endpoint:
  $type: shadowsocks
  address: "127.0.0.1:8080"
  cipher: "chacha20-ietf-poly1305"
  password: "your_password"

Here, a TypeParser for an "endpoint" would see $type: shadowsocks and delegate to the "shadowsocks" subparser.

When you create a TypeParser, a fallback handler must be specified. If the input config is not
a map, or is a map without a $type attribute, the fallback handler is called. This is helpful
for handling cases like parsing strings and the empty value. For example, a stream endpoint
can be specified simply as a string address (e.g., "proxy.example.com:443"). This string would be parsed by the stream dialer TypeParser's fallback handler.

Application Concepts

The Outline config defines a few concepts that are closely tied to the client application.

In types.go, we define a few higher-level types for the Outline config.
Some of these are wrappers around concepts from the Outline SDK, augmented with additional behavior or metadata needed by the application. For instance, we might add information about the first hop of a strategy, indicating whether it's a direct connection or tunneled, and if tunneled, the tunnel address. The use of generics in these types helps minimize code duplication.

One of the central pieces is the TransportPair, an object used by the VPN code to create TCP and UDP tunnels. The NewDefaultTransportProvider function is responsible for creating the TypeParser for TransportPair. This is where different network strategies (like Shadowsocks, HTTP proxies, Websockets, etc.) are registered.

Example of registering subparsers in NewDefaultTransportProvider:

go
// Websocket support.
streamEndpoints.RegisterSubParser("websocket", NewWebsocketStreamEndpointSubParser(streamEndpoints.Parse))
packetEndpoints.RegisterSubParser("websocket", NewWebsocketPacketEndpointSubParser(streamEndpoints.Parse))

Notice how the dependencies between the subparsers and the parsers they depend on are explicit
and will cause compile-time errors if not provided.

Adding a New Strategy or Dialer

To introduce a new network capability (e.g., custom proxy protocol, dialing strategy), follow these steps:

  1. Create the Parser Function:
    Write a parser package or function (SubParser) that transforms the YAML block (map[string]any) into a native connection interface, such as *Dialer[transport.StreamConn]. If your dialer delegates downward to a nested endpoint, require that parser as an argument.

    go
    func NewMyStrategyDialerSubParser(parseEndpoint configyaml.ParseFunc[*Endpoint[transport.StreamConn]]) func(ctx context.Context, input map[string]any) (*Dialer[transport.StreamConn], error) {
        return func(ctx context.Context, input map[string]any) (*Dialer[transport.StreamConn], error) {
            // 1. Unmarshal `input` logic into your custom struct
            // 2. Instantiate your streaming strategy
            // 3. Return a `Dialer` conforming to the interface requirements
        }
    }
  2. Register the Strategy:
    Locate the NewDefaultTransportProvider function in registry.go.
    Register your parsing function into the relevant parser category (streamDialers or packetDialers). The string key you provide will act as the native identifier for the matching $type field inside Outline configuration files.

    go
    streamDialers.RegisterSubParser("my_strategy", NewMyStrategyDialerSubParser(streamEndpoints.Parse))

Overview (`client/src/cordova/android/README.md`)

Android Development Instructions

This document describes how to develop and debug for Android.

The main entrypoint to Android's Java code is client/src/cordova/plugin/android/java/org/outline/OutlinePlugin.java

Set up your environment

Install these pre-requisites:

  • Java Development Kit (JDK) 17+. On macOS:

    shell
    brew install openjdk@17
    
    # Make it visible to `java_home`
    sudo ln -sfn "$(realpath "$(brew --prefix)")/opt/openjdk@17/libexec/openjdk.jdk" /Library/Java/JavaVirtualMachines/openjdk-17.jdk
    
    export JAVA_HOME=$(/usr/libexec/java_home -v 17.0)
  • Gradle 8.14.2+. On macOS: brew install gradle.

Then we need to install and configure the Android components. You can follow the Cordova Android Platform Guide, which uses Android Studio to configure the environment. You need an Android Studio compatible with the Android Gradle Plugin 8.10.1 (see build.gradle) we use (compatibility table).

Alternatively, you can do it on the command-line:

  1. Set environmental variables:

    shell
    export ANDROID_HOME=$HOME/Library/Android/sdk
    export ANDROID_NDK=$ANDROID_HOME/ndk
  2. Install the Android command-line tools and make them available on the command line:

    shell
    curl -o /tmp/commandlinetools.zip https://dl.google.com/android/repository/commandlinetools-mac-13114758_latest.zip
    unzip /tmp/commandlinetools.zip -d /tmp
    mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
    mv /tmp/cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/"
    export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin/"
  3. Install platform and build tools:

    shell
    sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;28.2.13676358"

    The NDK version must match the ndkVersion pin in build-extras.gradle, whose comment explains why we pin it (AGP uses it to strip the packaged native libraries).

  4. Install optional components that help development (source code, emulator and image):

    shell
    sdkmanager "sources;android-36" "system-images;android-36;default;arm64-v8a"

Note: you will need the system-images;android-36;default;x86_64 image on an Intel computer.

For development of the OutlineAndroidLib, we recommend installing Android Studio. That also make it easier to create virtual devices and run the emulator.

You can check your environment with:

sh
npm run action client/src/cordova/setup android
cd client
npx cordova requirements android

Important Versions

Component version constrained by set in
Android API Level 36+ Play Store config.xml, build.gradle
cordova-android 15 Android API Level package.json
JDK 17 cordova-android install instruction
Gradle 8.14.2+ cordova-android gradle-wrapper.properties
Android Gradle Plugin (AGP) 8.10.1 cordova-android build.gradle
Android Build Tools 36.0.0+ cordova-android install instructions
Android NDK 28.2.13676358 native-lib stripping build-extras.gradle, install instructions
Android Studio 2025.1.1 (Narwhal) AGP

Build the app

To build the app for Android, run:

sh
npm run action client/src/cordova/build android

The built apk will be at: client/platforms/android/app/build/outputs/apk/debug/app-debug.apk.

We also support passing a --verbose option on cordova android:

sh
npm run action client/src/cordova/build android -- --verbose

Make sure to rebuild after modifying platform dependent files!

๐Ÿ’ก NOTE: If this command ever gives you unexpected Cordova errors, try runnning npm run reset first.

Run the app

  1. Start the simulator or connect an Android device and enable USB debugging.

  2. From the project root, run:

    shell
    adb install -r -d client/platforms/android/app/build/outputs/apk/debug/app-debug.apk

Alternatively, run with:

sh
cd client
npx cordova run android -- --gradleArg=--include-build=../../src/cordova/android/OutlineAndroidLib

Develop code

Open src/cordova/android/OutlineAndroidLib on Android Studio. That will allow you to leverage the IDE features (auto-complete, warnings, etc) and run unit tests easily.

This project represents a library that implements most of the Outline native functionality and does not depend on Cordova. That library is imported by the "app" module that is generated by Cordova. The only native Android code outside of this library is OutlinePlugin.java, which integrates with CordovaLib.

To make the entire application load properly on Android Studio, you need to edit the generated /client/platforms/android/settings.gradle and add:

gradle
includeBuild "../../src/cordova/android/OutlineAndroidLib"

To debug the web app

Run Outline on your phone with USB debugging enabled, then on Chrome:

  • Go to chrome://inspect
  • Find Outline
  • Click inspect
  • Open the Console
  • Note that all TypeScript code is browserified in a single main.cordova.js

To debug Java code

You can debug the Java code using logcat on Android Studio:

  1. Start the app
  2. Select the "Logcat" tab at the bottom
  3. Select org.outline.android.client in the middle menu (it should not say "no debuggable processes")

It's also possible to use the logcat command.


Overview (`client/src/cordova/apple/README.md`)

Outline Client Apple Development Instructions

This document describes how to develop and debug the Outline Client for iOS and MacOS.

Getting Started

You will need:

  • Node (lts/hydrogen - download),
  • An Apple Device and Developer Account.
  • XCode 15.2 (download)
  • XCode command line tools: xcode-select --install

Initalizing the XCode Project

The XCode project is assembled by Cordova. To initialize and open the iOS project, run the following commands:

sh
npm run action client/src/cordova/setup ios
open ./client/src/cordova/apple/client.xcworkspace

For macOS:

sh
npm run action client/src/cordova/setup macos 
open ./client/src/cordova/apple/client.xcworkspace

[!NOTE]
Should you encounter issues with your build, there may be apple-specific dependencies that are out of date.
Run npm run reset and try again!

Configure code signing

Apple is quite particular when it comes to making sure your app is properly signed, even in development.

  1. In XCode, make sure you are logged into your Apple Developer account and that your personal devices are registered to it. Go to Preferences > Accounts and make sure your account is set.
  2. Select the "Outline" target in the left navigation bar and go to the "Signing & Capabilities" tab.

Internal Contributors

  1. Ensure that the "Jigsaw Operations LLC" option is selected for "Team".
  2. Do the same for the "VPN Extension" target.

External Contributors

For release builds via the command line, set the DEVELOPMENT_TEAM environment variable to override the default team ID without editing any project files:

sh
DEVELOPMENT_TEAM=<your_team_id> npm run action client/src/cordova/build ios -- --buildMode=release
DEVELOPMENT_TEAM=<your_team_id> npm run action client/src/cordova/build macos -- --buildMode=release

For development in Xcode:

  1. Select the "Team" for your own account.
  2. Change the bundle identifier (e.g. org.outline.ios.client) to something unique.
  3. Remove the app group group.org.getoutline.client.
  4. Do the same for the "VPN Extension" target.

Running the App

Specify the Run Destination

For the iOS client, you have a few options:

  • Run on your macOS computer: Product >> Destination >> My Mac (Mac Catalyst)
  • Run on a simulator
    • This is helpful to test the UI, but the VPN doesn't work on simulators, so this option is not recommended.
  • (Best) Run on a physical iOS device
    • This is the best option to evaluate how the app performs on a real device. Remember, you will need to enable development mode on the device and register it in your developer account.

See Devices and Simulator for details on running on a physical iOS device or the simulator.

You may find that your iOS version is too modern for XCode. You'll need to do the following:

  1. Download a folder corresponding to your iOS version from this community-managed repository.
  2. Unzip the file and add it to XCode:
text
Applications >> Xcode >> Right Click >> Show Package Contents >> Contents >> Developer >> Platforms >> iPhoneOS.platform >> DeviceSupport
  1. Restart XCode.

For the macOS client, you can simply select your macOS computer: Product > Destination > My Mac.

Build and Start the App

  1. First clean the build (Product >> Clean Build Folderโ€ฆ or โŒ˜ โ‡ง K)

[!WARNING]
If you don't clean the build first, it will fail with Command CodeSign failed with a nonzero exit code.

  1. Run (Product >> Run or โŒ˜ R), via the menu or the play button:

Development

Most of the Apple-specific development can happen directly on XCode. However, if you edit files in the generated platforms/ios, you will need to copy your changes to the appropriate version-controlled location at src/cordova/apple/xcode or src/cordova/plugin/apple.

Changes to the OutlineAppleLib package don't need to be copied, since the package is linked by the XCode workspace and the changes happen in the original location.

Inspect Logs

The easiest way to inspect logs is to use the log command. To see the client app logs in real time, you can use log stream with a --predicate flag to select the messages sent by the Outline code:

sh
log stream  --info --predicate 'senderImagePath contains "Outline.app"'

In the Console app, select the Action > Include Info Messages manu, and set the filter to "Library Path" "contains" "Outline.app":

๐Ÿ’ก Tip: You can save searches in the MacOS Console app.

For further debugging, you can include relevant messages from the Network Extension subsystem:

sh
log stream  --info --predicate 'senderImagePath contains "Outline.app" or (processImagePath contains "Outline.app" and subsystem contains "com.apple.networkextension")'

To see past logs use log show and the --last flag.

Private values are redacted from Apple system logs by default. To include private debug values while developing, run:

sh
sudo log config --mode "private_data:on"

Restore the default redaction behavior when you are done:

sh
sudo log config --mode "private_data:off"

For details on Apple logging, see Your Friend the System Log and Mac Logging and the log Command: A Guide for Apple Admins.

Debug the Vpn Extension

The VpnExtension runs in a separate process and its output is not logged to the Xcode console. To view its log statements see the "Inspect Logs" section.

XCode doesn't automatically attach to the VpnExtension because it's started on demand by the system.

  • If the Vpn Extension is running:
    • In XCode, select Debug > Attach to Process > VpnExtension
  • If the VpnExtension is not running:
    • In Xcode, select Debug > Attach to Process by PID or Nameโ€ฆ
    • Fill PID or Process Name with VpnExtension and press Attach

You won't see the log messages in the Xcode console. To see the messagges, refer to the "Inspect Logs" instructions.
For more info, see Debug, Profile, and Test Your App Extension.

Debug `VpnStartFailure`

Sometimes the app will refuse to connect, with a VpnStartFailure error:

If that happens, it may be the case that the app is trying to use the wrong version of the plugin.

To fix, run:

sh
pkill -9 VpnExtension
pluginkit -Amv | cut -f 4 | grep Outline | while IFS= read -r p; do pluginkit -r "$p"; done;
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -gc

This will kill any running VPN extension, unregister all versions of the Outline VPN plugin and garbage collect the Launch Services database.

Delete the existing VPN configuration

You may want to also delete the VPN configurations:

  1. Open the System Settings
  2. Go to VPN
  3. Press the Info button for the Outline service
  4. Press Remove Configurationโ€ฆ and confirm

Restart

If all fails, restart your device. That usually takes care of the issue.

Debug the web UI

To debug the webview:

  1. You may need to enable the Develop menu first, by selecting Settings > Advanced > Show Develop menu in menu bar
  2. In your terminal, run defaults write org.outline.ios.client WebKitDeveloperExtras -bool true. This is only needed once, to make the Outline webview debuggable. You may need to re-run the whole Outline app (use Cmd+R).
  3. In the Outline Client app, right click โ†’ Inspect Context. This will open the Safari debugger

To reload the UI without re-running the application, right-click โ†’ Reload.


Overview (`client/src/cordova/plugin/README.md`)

cordova-plugin-outline

Cordova plugin that implements a system-wide VPN to tunnel device traffic over Shadowsocks.

Supported Platforms

This plugin supports Android, iOS, and macOS.

Android

This plugin targets Android devices running Lollipop (API 22), or higher.

Apple

This plugin targets Apple devices running iOS 11.0+ and macOS/OS X 10.11+.

tun2socks

Native platforms consume outline-go-tun2socks as a library to assemble IP traffic and implement the Shadowsocks protocol: it receives all of the deviceโ€™s traffic through the VPN network interface (TUN) and forwards it to a Shadowsocks proxy server.

Additional Apple Dependencies

We use Swift Packages for third party dependencies: sentry-cocoa and CocoaLumberjack.

To upgrade the Swift Package dependencies, update the version field for the corresponding package under the XCRemoteSwiftPackageReference section in the Outline.pbxproj file.

Alternatively, open the xcworkspace file for the curresponding OS, and update the packages via the XCode UI.


Overview (`server_manager/www/ui_components/licenses/README.md`)

HOWTO re-generate `license.txt`

Steps

  • cd to the root of your clone of this repo
  • Ensure node_modules is up to date and only include dependencies of the Electron app by running npm ci && npm run action server_manager/www/build
  • Copy LICENSE and the entire node_modules folder to ./server_manager/LICENSE and ./server_manager/node_modules
  • cd server_manager
  • npx generate-license-file --input package.json --output www/ui_components/licenses/licenses.txt
  • cd www/ui_components/licenses
  • cat db-ip_license.txt >> licenses.txt

Done! (remember to delete the ./server_manager/node_modules folder)

Note that the third step of copying LICENSE and node_modules is required because:

  • generate-license-file iterates all dependencies under node_modules folder in the current directory
  • generate-license-file will try to find a LICENSE file in the current directory, if not found, it will use README.md instead which is not what we want

Check

To quickly look for non-compliant licenses:

bash
yarn licenses list --prod|grep -Ev \(@\|VendorUrl:\|VendorName:\|URL:\)

Overview (`client/src/cordova/apple/OutlineAppleLib/README.md`)

OutlineAppleLib

This package provides all Outline Apple logic, including the Outline Tun2socks
framework, as a Swift Package.


Overview (`client/web/views/licenses_view/licenses/README.md`)

HOWTO re-generate `licenses.txt`

Steps

  • cd to the root of your clone of this repo.
  • Ensure node_modules is up to date by running npm ci.
  • npx generate-license-file --input package.json --output client/web/views/licenses_view/licenses/licenses.txt
  • ./client/web/views/licenses_view/licenses/third_party.sh >> ./client/web/views/licenses_view/licenses/licenses.txt

Done!


Invitation Instructions (`docs/invitation-instructions.md`)

Moved to invitation_instructions.md.


Invitation Instructions (`docs/invitation_instructions.md`)

Invitation instructions

Have an invitation to connect to an Outline server? Follow these instructions to access to the open internet from your device. No personal information will be collected.

1. Copy your access key

ss://xxx located at the bottom of your invitation email or message. The Outline app will instantly add this server from your clipboard.

2. Install Outline

Download
Android โ€บ If Google Play is not accessible, get it here.
iOS โ€บ Get Outline on the App Store
Windows โ€บ Download Outline.exe and double click to launch.
macOS โ€บ Get Outline on the Mac App Store
Linux โ€บ Follow the instructions to install the Outline client on Linux.

3. Add server and connect

Open the app and confirm your new server. Tap or click to connect.
Having trouble? Try copying your access key again to add your server.


Life Of A Packet (`docs/life_of_a_packet.md`)

Life of a Packet

mermaid
flowchart LR
  A(Apps) <--> S(Socket)
  subgraph Operating System
    S <--> R(Routing Table)
  end
  R <-- Outline OFF --> IF(Network Interface)
  IF <--> INET(((Internet)))
  R <-- Outline ON --> TUN(Tun Device)
  TUN <--> T2S(Tun2sockets)
  subgraph Outline Client
    T2S <--> SC(Shadowsocks Client)
  end
  SC <--> IF
  click TUN "https://en.wikipedia.org/wiki/TUN/TAP" _blank

--- METRICS ---