How Clash Nyanpasu Generates and Patches Runtime YAML Configuration
Clash Nyanpasu maintains an in-memory runtime configuration object that serializes to YAML for display and updates atomically through a type-safe patching mechanism when users modify settings like allow-lan or mode.
Clash Nyanpasu manages proxy configurations through a dynamic runtime YAML configuration that bridges the React-based frontend and the Clash core. This article examines how the Tauri-based backend generates readable YAML from internal state and applies partial updates through a structured patching pipeline, based on the actual implementation in the libnyanpasu/clash-nyanpasu repository.
Architecture of Runtime YAML Configuration Generation
The generation flow converts the internal Mapping representation into a human-readable YAML string that reflects the currently active configuration (base config plus any merge files or scripts).
The Runtime Data Structure
The runtime state lives in the IRuntime struct defined in backend/tauri/src/config/runtime.rs. This structure holds the active configuration as an Option<Mapping> and provides thread-safe access through a singleton pattern.
// backend/tauri/src/config/runtime.rs (lines 18-26)
pub struct IRuntime {
pub config: Option<Mapping>, // The actual YAML mapping
}
impl IRuntime {
pub fn latest() -> &'static IRuntime {
// Returns the singleton instance
}
}
Serializing to YAML via Tauri Commands
When the frontend requests the runtime configuration, the get_runtime_yaml command in backend/tauri/src/ipc.rs (lines 400-410) handles the serialization:
#[tauri::command]
pub fn get_runtime_yaml() -> Result<String> {
let runtime = Config::runtime().latest();
let yaml = runtime
.config
.as_ref()
.ok_or_else(|| anyhow!("no runtime config"))
.and_then(|c| serde_yaml::to_string(c).context("failed to convert to yaml"))?;
Ok(yaml)
}
The resulting YAML string represents the merged configuration—the base profile combined with any user-defined merge files and scripts that Clash is actively using.
Patching Runtime Configuration in Clash Nyanpasu
The patching mechanism allows the UI to modify specific fields (such as allow-lan, ipv6, log-level, or mode) without replacing the entire configuration file.
The Patch Payload Structure
To ensure type safety and prevent accidental modification of read-only keys, Clash Nyanpasu defines a PatchRuntimeConfig struct that only includes editable fields:
// backend/tauri/src/config/runtime.rs (lines 6-16)
#[derive(Default, Debug, Clone, Deserialize, Serialize, specta::Type)]
pub struct PatchRuntimeConfig {
#[serde(default)] pub allow_lan: Option<bool>,
#[serde(default)] pub ipv6: Option<bool>,
#[serde(default)] pub log_level: Option<String>,
#[serde(default)] pub mode: Option<String>,
}
The Patching Pipeline
When a user toggles a setting in the UI, the following pipeline executes:
-
Frontend Invocation: The TypeScript service calls
patch_clash_configvia Tauri'sinvokeAPI. -
Command Deserialization: In
backend/tauri/src/ipc.rs(lines 444-456), the payload deserializes into aserde_yaml::Mappingand validates that it represents a YAML mapping structure. -
Feature Orchestration: The
feat::patch_clashfunction inbackend/tauri/src/feat.rs(lines 12-78) coordinates the update:- Updates the draft Clash configuration via
Config::clash().draft().patch_config() - Applies changes to the runtime via
Config::runtime().latest().patch_config(patch) - Validates critical fields (mixed-port, external-controller)
- Optionally restarts the Clash core to apply changes
- Persists the updated configuration to disk
- Updates the draft Clash configuration via
-
Runtime Application: The
patch_configmethod inbackend/tauri/src/config/runtime.rs(lines 32-58) iterates through thePatchRuntimeConfigfields and inserts or overwrites the corresponding keys in the storedMapping.
After patching completes, the runtime Mapping serves as the source for all future get_runtime_yaml calls, ensuring consistency between the active Clash process and the displayed configuration.
Code Examples for Runtime Configuration Management
Reading Runtime YAML in TypeScript
The frontend retrieves the current configuration through the Tauri service layer:
// frontend/interface/src/service/tauri.ts (line 225)
import { invoke } from '@tauri-apps/api/tauri';
export async function getRuntimeYaml(): Promise<string> {
// Returns a YAML string like "# Clash configuration\nmixed-port: 7890\n..."
return await invoke<string>('get_runtime_yaml');
}
Patching Configuration in TypeScript
To modify runtime settings, construct a PatchRuntimeConfig object and invoke the patch command:
// frontend/interface/src/service/tauri.ts (line 28)
import { invoke } from '@tauri-apps/api/tauri';
export interface PatchRuntimeConfig {
allow_lan?: boolean;
ipv6?: boolean;
log_level?: string;
mode?: string;
}
// Example: enable LAN and switch mode to "Rule"
await invoke<void>('patch_clash_config', {
payload: { allow_lan: true, mode: 'Rule' } as PatchRuntimeConfig,
});
Rust Implementation Details
The backend command implementations demonstrate how the runtime state transitions between Rust data structures and YAML strings:
// backend/tauri/src/ipc.rs – Runtime generation
#[tauri::command]
pub fn get_runtime_yaml() -> Result<String> {
let runtime = Config::runtime().latest();
let yaml = runtime
.config
.as_ref()
.ok_or_else(|| anyhow!("no runtime config"))
.and_then(|c| serde_yaml::to_string(c).context("failed to convert to yaml"))?;
Ok(yaml)
}
// backend/tauri/src/ipc.rs – Configuration patching
#[tauri::command]
pub async fn patch_clash_config(payload: PatchRuntimeConfig) -> Result {
let mapping = serde_yaml::to_value(&payload)?
.as_mapping()
.cloned()
.ok_or_else(|| IpcError::Custom("Expected a mapping".into()))?;
feat::patch_clash(mapping).await
}
Key Source Files in Clash Nyanpasu
| Role | File | Description |
|---|---|---|
| Tauri command – read | backend/tauri/src/ipc.rs (lines 400‑410) |
Implements get_runtime_yaml. |
| Tauri command – write | backend/tauri/src/ipc.rs (lines 444‑456) |
Implements patch_clash_config. |
| Runtime data structure | backend/tauri/src/config/runtime.rs (lines 18‑26, 32‑58) |
Holds the in‑memory YAML mapping and the patch_config logic. |
| Patch orchestration | backend/tauri/src/feat.rs (lines 12‑78) |
Calls Config::runtime().latest().patch_config, restarts core, persists changes. |
| Frontend service – get | frontend/interface/src/service/tauri.ts (line 225) |
Calls the Tauri get_runtime_yaml command. |
| Frontend service – patch | frontend/interface/src/service/tauri.ts (line 28) |
Calls the Tauri patch_clash_config command. |
| TypeScript bindings | frontend/interface/src/ipc/bindings.ts |
Defines the PatchRuntimeConfig type used by the UI. |
Summary
-
Runtime YAML generation in Clash Nyanpasu converts the in-memory
Mappingstored inIRuntimeto a YAML string viaserde_yaml::to_string, exposed through theget_runtime_yamlTauri command. -
Configuration patching uses a type-safe
PatchRuntimeConfigstruct to restrict editable fields toallow_lan,ipv6,log_level, andmode, preventing accidental corruption of read-only keys. -
The patching pipeline flows from TypeScript UI calls through
ipc.rsdeserialization, intofeat.rsfor orchestration (draft updates, validation, core restart), and finally applies changes viaruntime.rswhich updates the storedMapping. -
All operations are atomic and thread-safe, utilizing Rust's singleton pattern for the runtime configuration to ensure consistency between the active Clash process and the UI display.
Frequently Asked Questions
What is the runtime YAML configuration in Clash Nyanpasu?
The runtime YAML configuration is the active, in-memory representation of the Clash proxy settings currently being used by the core process. It lives in the IRuntime struct as a serde_yaml::Mapping and reflects the merged state of the base profile, merge files, and any applied scripts, distinct from the static configuration files stored on disk.
How does Clash Nyanpasu handle partial configuration updates?
Clash Nyanpasu handles partial updates through the PatchRuntimeConfig struct, which only exposes four editable fields: allow_lan, ipv6, log_level, and mode. When the UI sends a patch, the backend deserializes it into a YAML mapping, applies it to both the draft Clash config and the runtime mapping via Config::runtime().latest().patch_config(), validates critical fields like mixed-port, and optionally restarts the core to apply changes.
Where is the runtime configuration stored in the codebase?
The runtime configuration is primarily stored and managed in backend/tauri/src/config/runtime.rs, which defines the IRuntime struct holding the Option<Mapping> config field. The generation command resides in backend/tauri/src/ipc.rs (lines 400-410), while the patching orchestration logic is implemented in backend/tauri/src/feat.rs (lines 12-78). Frontend bindings exist in frontend/interface/src/service/tauri.ts.
Can I manually edit the runtime YAML configuration?
While you can read the runtime YAML via the get_runtime_yaml command for inspection or export, manual editing is not supported through the public API. The patch_clash_config command requires a strongly-typed PatchRuntimeConfig payload that only accepts specific fields (allow_lan, ipv6, log_level, mode), ensuring type safety and preventing corruption of the runtime state. Direct manipulation of the underlying Mapping would bypass validation logic and could destabilize the Clash core.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →