-
Notifications
You must be signed in to change notification settings - Fork 28
doc: add clients config setup flow #433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kp-antonio-yang
wants to merge
6
commits into
main
Choose a base branch
from
doc/client-config
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
fa1b304
doc: add flow about how config determined
kp-antonio-yang 9371587
doc: config: udpate server side descriptions
kp-antonio-yang 65d3866
doc: add links from source code to md file
kp-antonio-yang 861861d
doc: update using case for general config
kp-antonio-yang 98d8721
doc: config: restructure with future plan section
kp-antonio-yang e3a2298
doc: config: restruct in Thesis format
kp-antonio-yang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| # General Config Design Rationale | ||
|
|
||
| ## Introduction | ||
|
|
||
| The config generation is unified across all clients and the server. For the server, the scope is simple — it only supports native builds on Linux. For the client side, real-world usage is considerably broader, particularly when generating JSON schema for any platform target from a macOS machine, which is a common working scenario for a small team. | ||
|
|
||
| This document is not meant to impose restrictions on development. Instead, it addresses development pain points ahead of time by providing guides that help you leverage what is currently available. | ||
|
|
||
| --- | ||
|
|
||
| ## Methods | ||
|
|
||
| ### Config as Single Source of Truth | ||
|
|
||
| `Config` is the single source of truth for all clients — all user inputs, whether from a UI or a file, flow through it. JSON schema generation from the CLI is designed to be a general-purpose mechanism for all client tooling. | ||
|
|
||
| `Config` derives both `ConfigPatch` and `JsonSchema`, which enables the layered override flow described below. | ||
|
|
||
| ### Config Flow | ||
|
|
||
| **Desktop client flow (steps 0–6):** | ||
|
|
||
| Starting from default values (0), the config evolves through each step along the bold lines. `ConfigPatch` plays a central role along the dot lines, generating patches by deserializing from a file, environment variables, and CLI options — each applied as a layered override in sequence. | ||
|
|
||
| **Mobile client flow (steps 0, 1, 2, 6):** | ||
|
|
||
| Mobile takes a shorter path, skipping the intermediate steps after `2.ConfigContent`. Rather than reading from a file, config content comes from a Dynamic UI driven by a JSON schema file generated at compile time from the same `Config` struct via the CLI client. Both desktop and mobile ultimately share the same `Config` source of truth, with the mobile flow being a streamlined subset. | ||
|
|
||
| **Server flow (steps 0–6):** | ||
|
|
||
| Identical to the CLI client flow, but all parameters use `SERVER` keywords — e.g. `LW_CLIENT_*` becomes `LW_SERVER_*`. | ||
|
|
||
| ```mermaid | ||
| flowchart TB | ||
| clientFn("client(config)") | ||
| E0@{ shape: paper-tape, label: "SchemaFile" } --> A0 | ||
| 0 -. "JsonSchema" .-> E0 | ||
| 0 -. "Serialize" .-> E1 | ||
|
|
||
| subgraph "android (foreign)" | ||
| A0@{ shape: manual-input, label: "Dynamic UI"} | ||
| end | ||
|
|
||
| A0 --"mobile"--> 2 | ||
| 2 --"mobile shortcut" --> 6 | ||
| E1@{ shape: paper-tape, label: "ConfigFile" } --"cli"--> 2 | ||
|
|
||
| subgraph main.rs or mobile.rs | ||
| 1("1: Config::default()") ==> 2 | ||
| 2("2: ConfigContent") ==> 3 | ||
| 3("3:Envars(LW_CLIENT_*)") ==> 4 | ||
| 4("4: CLI Option") ==> 5 | ||
| 5("5: Special Envars(LW_CLIENT_RUST_LOG)") ==> 6 | ||
| 6("6: Config (determined)") | ||
| end | ||
|
|
||
| subgraph config.rs | ||
| 0@{ shape: braces, label: "0: Config" } == "Default" ==>1 | ||
| 0 -. "Patch" .-> 0.1 | ||
| 0.1 -. "Deserialize" .-> 3 | ||
| 0.1@{ shape: braces, label: "ConfigPatch" } -. "Parser" .-> 4 | ||
| end | ||
|
|
||
| 0.1 -. "Deserialize" .-> 2 | ||
| 6 ==> clientFn | ||
| ``` | ||
|
|
||
| ### Design Principles | ||
|
|
||
| When adding a platform-specific field, a feature gate may be required — e.g. `#[cfg(feature = "...")]` — with platform intent communicated via `x-cfg` and `format` attributes in the JSON schema. Critically, `#[cfg(target)]` must **not** be applied to fields: if it were, those fields would be absent when generating schema on a non-matching host, making it impossible to generate all schemas from any desktop machine. | ||
|
|
||
| To keep things clean, the practical approach with the least friction is: | ||
|
|
||
| 1. Feature gates (if used) belong on **fields** of the `Config` struct — never with a target gate. | ||
| 2. `cfg` target attributes belong on **functions**. | ||
|
|
||
| Following this pattern, the feature gate lives only in `Config` and is handed off to the target gate in the function layer. A further benefit is that functions sharing the same signature with `#[cfg(target)]` selection at compile time means a Windows developer and an Android developer work in almost the same domain language: | ||
|
|
||
| ```rust | ||
| struct Config { | ||
| #[cfg(feature="windows")] // optional | ||
| #[schemars(extend("x-cfg" = "windows"))] | ||
| win_only_field: usize, | ||
|
|
||
| #[cfg(feature="android")] // optional | ||
| #[schemars(extend("x-cfg" = "android"))] | ||
| android_only_field: usize, | ||
| // ... | ||
| } | ||
|
|
||
| fn main() { | ||
| let config = Config::load(); | ||
| client(config) | ||
| } | ||
|
|
||
| #[cfg(windows)] | ||
| fn client(config: Config) { | ||
| let Config { win_only_field, .. } = config; | ||
| if win_only_field > 256 { | ||
| // ... | ||
| } | ||
| } | ||
|
|
||
| #[cfg(android)] | ||
| fn client(config: Config) { | ||
| let Config { android_only_field, .. } = config; | ||
| let tun = Tun::new(android_only_field); | ||
| } | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Results | ||
|
|
||
| ### Current State (Status 0): Mobile Only | ||
|
|
||
| JSON schema generation is currently only supported for mobile clients. Existing clients do not use JSON schema and do not yet follow this design. When JSON schema support is needed for a new client, the Android implementation serves as a practical reference. | ||
|
|
||
| | Persona | Use Case | Command | | ||
| |---|---|---| | ||
| | Native developer | Windows build | `cargo build` | | ||
| | Native developer | macOS build | `cargo build` | | ||
| | Cross-platform developer | Mobile from desktop | `cargo build --feature=mobile` | | ||
| | Frontend / Designer | Android schema from Linux | `cargo run -g jsonschema --all-features` | | ||
| | Frontend / Designer | All schema from any desktop | **Not yet supported** | | ||
|
|
||
| ### Extended Personas | ||
|
|
||
| Previous clients were built in a straightforward native way. Introducing the mobile feature and JSON schema expanded the scope of use cases beyond native development, extending the user personas from 1 to 3: | ||
|
|
||
| - **Native developer** — builds on the target machine directly. | ||
| - **Cross-platform developer** — cross-compiles for a different target from their own machine. | ||
| - **Frontend / Designer** — generates JSON schema to build and design a dynamic UI, without needing a target-specific machine. | ||
|
|
||
| This broader scope is key infrastructure not just for internal use, but for the wider Lightway community and external developers. | ||
|
|
||
| --- | ||
|
|
||
| ## Discussion | ||
|
|
||
| ### Future Plans | ||
|
|
||
| Both options below share a common goal: enabling developers, frontend engineers, and designers to generate and tailor the config or schema from any working branch without needing a specific target machine. | ||
|
|
||
| #### Option 1: Align All Clients with Feature Gates in Config *(Preferred)* | ||
|
|
||
| | Persona | Use Case | Command | | ||
| |---|---|---| | ||
| | Native developer | Windows build | `cargo build --feature=windows` | | ||
| | Native developer | macOS build | `cargo build --feature=macos` | | ||
| | Cross-platform developer | Mobile from desktop | `cargo build --feature=mobile` | | ||
| | Frontend / Designer | All schema from any desktop | `cargo run -g jsonschema --all-features` | | ||
|
|
||
| No extra fields are compiled in for any use case — every field is exactly what the target needs. Specifying both a feature and a target flag can feel verbose, but since builds are already wrapped in `Makefile.toml`, this is easily managed without affecting day-to-day usage. The design principles above — feature gates on fields, `cfg` targets on functions — apply specifically to this option. | ||
|
|
||
| #### Option 2: Keep All Extra Fields without Feature or Target Gates | ||
|
|
||
| | Persona | Use Case | Command | | ||
| |---|---|---| | ||
| | Native developer | Windows build | `cargo build` | | ||
| | Native developer | macOS build | `cargo build` | | ||
| | Cross-platform developer | Mobile from desktop | `cargo build --feature=mobile` | | ||
| | Frontend / Designer | All schema from any desktop | `cargo run -g jsonschema` | | ||
|
|
||
| This always compiles extra fields into the config regardless of the target, and is syntactically inconsistent with the cross-build cases. While native builds appear simpler with no feature flag, since builds are invoked through `Makefile.toml` rather than `cargo build` directly, that surface simplicity does not translate into a real workflow benefit. | ||
|
|
||
| ### Open Question | ||
|
|
||
| The broader approach to consuming JSON schema across all clients remains an open question. See the ongoing [discussion](https://github.com/expressvpn/lightway/pull/411#discussion_r3166422937) for context. A consensus between Option 1 and Option 2 is needed before proceeding with further client adoption. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.