# Technical Documentation: OutlineFoundation/outline-apps

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/OutlineFoundation/outline-apps)

### Overview (`README.md`)

# <img alt="Outline Manager Logo" src="docs/resources/logo_manager.png" title="Outline Manager" width="32">&nbsp;&nbsp;Outline Apps&nbsp;&nbsp;<img alt="Outline Client Logo" src="docs/resources/logo_client.png" title="Outline Client" width="32">

[](https://www.reddit.com/r/outlinevpn/) [](https://community.internetfreedomfestival.org/community/channels/outline-community) [](https://github.com/google/gts)

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:**

&nbsp;&nbsp;&nbsp;&nbsp;<img alt="Outline Manager Logo" src="docs/resources/logo_manager.png" title="Outline Manager" width="14">&nbsp;&nbsp;**Outline Manager** ([`/server_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](https://getoutline.org/get-started/#step-1). See [`server_manager/README.md`](./server_manager/README.md) for more information.


&nbsp;&nbsp;&nbsp;&nbsp;<img alt="Outline Client Logo" src="docs/resources/logo_client.png" title="Outline Client" width="14">&nbsp;&nbsp;**Outline Client** ([`/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](https://shadowsocks.org/) server. [You can install the client here](https://getoutline.org/get-started/#step-3). See [`client/README.md`](./client/README.md) for more information.


## Community and Support

Interested in **contributing to Outline?** See our [Contributing Guide](CONTRIBUTING.md) for more information.

See [AGENTS.md](./AGENTS.md) for AI agent and developer guidance.

You can also **join the Outline Community** by signing up for the [IFF Mattermost](https://wiki.digitalrights.community/index.php?title=IFF_Mattermost)!

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

---

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

# <img alt="Outline Client Logo" src="../docs/resources/logo_client.png" title="Outline Client" width="32">&nbsp;&nbsp;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](https://github.com/OutlineFoundation/outline-server) software, but it is fully compatible with any [Shadowsocks](https://shadowsocks.org/) server.

The client's user interface is implemented in [Polymer](https://www.polymer-project.org/) 2.0. Platform support is provided by [Cordova](https://cordova.apache.org/) and [Electron](https://electronjs.org/), with additional native components in this repository.


## Requirements for all builds

All builds require [Node](https://nodejs.org/) 22 (lts/hydrogen), and [Go](https://golang.org/) 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](web/app). UI components are located in [web/ui_components](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?](/docs/life_of_a_packet.md)

## Accepting a server invite

[Looking for instructions on how to accept a server invite?](/docs/invitation_instructions.md)

## Platform-specific development

Each platform is handled differently:

1. [Developing for Apple **(MacOS and iOS)**](src/cordova/apple)
2. [Developing for **Android**](src/cordova/android)
3. [Developing for Electron **(Windows and Linux)**](electron)

## Error reporting

To enable error reporting through [Sentry](https://sentry.io/) 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`)

# <img alt="Outline Manager Logo" src="../docs/resources/logo_manager.png" title="Outline Manager" width="32">&nbsp;&nbsp;Outline Manager



## Running

To run the Outline Manager Electron app:

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

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

```
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:

```
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:

```
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](https://www.winehq.org/download) installed.
- Linux: An `.AppImage` file.
- macOS: A `.dmg` and a `.zip` file as [required by auto update](https://www.electron.build/mac#target).

> 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

```
npm run action server_manager/test
```

## Error reporting

To enable error reporting through [Sentry](https://sentry.io/) 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](https://nodejs.org/) 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](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`](https://github.com/ionic-team/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](https://developer.apple.com/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](https://github.com/OutlineFoundation/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](https://developer.android.com/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](https://github.com/OutlineFoundation/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](https://developer.android.com/tools/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](https://dev.to/kristoff/zig-makes-go-cross-compilation-just-work-29ho).

[Install zig](https://ziglang.org/learn/getting-started/#installing-zig) and make sure it's in the PATH.

You can download the binary tarball, or [use a package manager](https://github.com/ziglang/zig/wiki/Install-Zig-from-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

- `patchelf` 0.14+ is required for building the Debian package:
  - Debian/Ubuntu: `sudo apt install patchelf`
  - macOS: `brew install patchelf`
  - Windows: https://github.com/NixOS/patchelf/releases

## Release

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

- [Java 8+ Runtime](https://www.java.com/en/download/). This is required for the cross-platform Windows executable signing tool [Jsign](https://ebourg.github.io/jsign/). If you don't need to sign the executables, feel free to skip this.

## Electron Builder

The [Electron Builder config](./electron-builder.json) 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](./app_paths.ts)).

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+](https://visualstudio.microsoft.com/downloads/) 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](https://visualstudio.microsoft.com/downloads/?q=build+tools#build-tools-for-visual-studio-2022) 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 (`ParseFunc`s), 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](./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`](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+](https://jdk.java.net/archive/). 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+](https://gradle.org/install/). On macOS: `brew install gradle`.

Then we need to install and configure the Android components. You can follow the [Cordova Android Platform Guide](https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html), 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](client/src/cordova/android/OutlineAndroidLib/build.gradle)) we use ([compatibility table](https://developer.android.com/studio/releases#android_gradle_plugin_and_android_studio_compatibility)).

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
    ```

1. Install the [Android command-line tools](https://developer.android.com/studio#command-line-tools-only) 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/"
    ```
  
1. 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](../plugin/android/build-extras.gradle), whose comment explains why we pin it (AGP uses it to strip the packaged native libraries).

1. 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](https://developer.android.com/google/play/requirements/target-sdk) | [config.xml](../../../config.xml), [build.gradle](./OutlineAndroidLib/outline/build.gradle) |
| cordova-android | 15 | Android API Level | [package.json](../../../package.json) |
| JDK | 17 | [cordova-android](https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html#android-api-level-support) | install instruction |
| Gradle | 8.14.2+ | [cordova-android](https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html#android-api-level-support) | [gradle-wrapper.properties](./OutlineAndroidLib/gradle/wrapper/gradle-wrapper.properties) |
| Android Gradle Plugin (AGP) | 8.10.1 | [cordova-android](https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html#android-api-level-support) | [build.gradle](../android/OutlineAndroidLib/build.gradle) |
| Android Build Tools | 36.0.0+ | [cordova-android](https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html#android-api-level-support) | install instructions |
| Android NDK | 28.2.13676358 | [native-lib stripping](../plugin/android/build-extras.gradle) | [build-extras.gradle](../plugin/android/build-extras.gradle), install instructions |
| Android Studio | 2025.1.1 (Narwhal) | [AGP](https://developer.android.com/studio/releases#android_gradle_plugin_and_android_studio_compatibility) | |

## 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](https://developer.android.com/studio/debug/dev-options.html#enable).
1. 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](https://developer.android.com/studio/debug/dev-options.html#enable), then on Chrome:

- Go to [chrome://inspect](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](https://developer.android.com/studio/debug/logcat):

1. Start the app
1. Select the "Logcat" tab at the bottom
1. 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](https://developer.android.com/tools/logcat).

---

### 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](https://nodejs.org/en/download/)),
- An Apple Device and Developer Account.
- XCode 15.2 ([download](https://developer.apple.com/xcode/))
- 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.
1. 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".
1. 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.
1. Change the bundle identifier (e.g. `org.outline.ios.client`) to something unique.
1. Remove the app group `group.org.getoutline.client`.
1. 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](https://developer.apple.com/documentation/xcode/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](https://github.com/iGhibli/iOS-DeviceSupport/tree/master/DeviceSupport).
> 2. Unzip the file and add it to XCode:
>
> ```
> Applications >> Xcode >> Right Click >> Show Package Contents >> Contents >> Developer >> Platforms >> iPhoneOS.platform >> DeviceSupport
> ```

3. 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 <kbd>⌘</kbd> <kbd>⇧</kbd> <kbd>K</kbd>)

> [!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 <kbd>⌘</kbd> <kbd>R</kbd>), via the menu or the play button:

<img width="802" alt="image" src="https://github.com/OutlineFoundation/outline-sdk/assets/113565/f3289c08-5f33-423a-a496-d5d764f4fce0">


## 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`](./xcode) or [`src/cordova/plugin/apple`](../plugin/apple).

Changes to the [OutlineAppleLib](./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":

<img width="1371" alt="image" src="https://github.com/OutlineFoundation/outline-apps/assets/113565/812c9e14-be11-4a64-b90f-58a4bac138b1">

> 💡 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](https://developer.apple.com/forums/thread/705868/) and [Mac Logging and the log Command: A Guide for Apple Admins](https://blog.kandji.io/mac-logging-and-the-log-command-a-guide-for-apple-admins#:~:text=The%20log%20command%20is%20built,(Press%20q%20to%20exit.)).


## 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](#Inspect-Logs)" instructions.
For more info, see [Debug, Profile, and Test Your App Extension](https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionCreation.html#//apple_ref/doc/uid/TP40014214-CH5-SW8).

### Debug `VpnStartFailure`

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

<img width="283" alt="image" src="https://github.com/OutlineFoundation/outline-apps/assets/113565/5f6ff845-8be3-431d-a40c-98bcfbd6ec8a">

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
1. Go to *VPN*
1. Press the Info button for the Outline service
1. 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
1. 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).
1. 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](https://shadowsocks.org/).

## 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](https://github.com/OutlineFoundation/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](https://github.com/OutlineFoundation/outline-ss-server).

## Additional Apple Dependencies

We use [Swift Packages](https://developer.apple.com/documentation/xcode/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](https://github.com/OutlineFoundation/outline-apps/blob/master/client/src/cordova/apple/xcode/Outline.xcodeproj/project.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.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 ›](https://play.google.com/store/apps/details?id=org.outline.android.client) | If Google Play is not accessible, [get it here](https://s3.amazonaws.com/outline-releases/client/android/stable/Outline-Client.apk). |
| [iOS ›](https://apps.apple.com/app/outline-app/id1356177741) | Get Outline on the App Store |
| [Windows ›](https://s3.amazonaws.com/outline-releases/client/windows/stable/Outline-Client.exe) | Download Outline.exe and double click to launch. |
| [macOS ›](https://apps.apple.com/app/outline-app/id1356178125) | Get Outline on the Mac App Store |
| [Linux ›](https://support.getoutline.org/client/getting-started/install-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 ---
- Files Extracted: 16
- Estimated Token Budget: ~12436 tokens
- Canonical Reference: https://codewiki.google/github.com/OutlineFoundation/outline-apps
