feat(core): Added node configuration schema support
- Imported the ConfigurationSchema class to define core type configurations - Added CoreTypes constant support for xray and v2fly configuration modes - Replaced parameter inputs in addNode.vue with node type selectors - Created the nodeConfigurator.vue component to handle configuration structure - Added Zod mode validation support for xray and v2fly configurations - Updated the README document to explain the project build and development startup steps - Added MCP HTTP service support tool list and state control interface - Imported the Rocket.rs dependency and configured the background service runtime environment
This commit is contained in:
14
README.md
14
README.md
@@ -1,7 +1,13 @@
|
||||
# Tauri + Vue + TypeScript
|
||||
# Spary
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
Proxy software launcher
|
||||
|
||||
## Recommended IDE Setup
|
||||
## Build
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||
### Generate icons
|
||||
|
||||
```shell
|
||||
yarn tauri icon src/assets/logo.svg
|
||||
```
|
||||
|
||||
### Start For Dev
|
||||
|
||||
2
api/api.http
Normal file
2
api/api.http
Normal file
@@ -0,0 +1,2 @@
|
||||
# 获取开关状态
|
||||
GET 127.0.0.1:3000/mcp/tools/list
|
||||
@@ -27,3 +27,6 @@ tauri-plugin-fs = "2"
|
||||
tauri-plugin-os = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-process = "2"
|
||||
rocket = { version = "0.5", features = ["json"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
uuid = { version = "1.0", features = ["v4", "serde"] }
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use tauri::Manager;
|
||||
use crate::spary::spary_switch;
|
||||
use std::sync::Arc;
|
||||
use tauri::menu::{Menu, MenuItem};
|
||||
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
|
||||
use tauri::Manager;
|
||||
use tauri_plugin_sql::{Migration, MigrationKind};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
mod mcp_server;
|
||||
mod spary;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
@@ -71,8 +74,60 @@ pub fn run() {
|
||||
})
|
||||
.build(app)?;
|
||||
tray.set_menu(Some(menu))?;
|
||||
|
||||
// Start the MCP HTTP server in a background task
|
||||
let app_handle = app.handle().clone();
|
||||
std::thread::spawn(move || {
|
||||
start_mcp_server(app_handle);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
fn start_mcp_server(app_handle: tauri::AppHandle) {
|
||||
// Disable MCP server for now
|
||||
if false {
|
||||
return;
|
||||
}
|
||||
use mcp_server::AppState;
|
||||
|
||||
// Create the shared application state
|
||||
let state = AppState {
|
||||
app_handle,
|
||||
spray_status: Arc::new(Mutex::new(false)), // Initial spray status is off
|
||||
};
|
||||
|
||||
// Configure Rocket with custom settings
|
||||
let rocket = rocket::build()
|
||||
.configure(rocket::Config {
|
||||
port: 3000,
|
||||
address: "127.0.0.1".parse().expect("Invalid address"),
|
||||
..rocket::Config::default()
|
||||
})
|
||||
.manage(state)
|
||||
.mount(
|
||||
"/",
|
||||
rocket::routes![
|
||||
mcp_server::initialize,
|
||||
mcp_server::get_capabilities,
|
||||
mcp_server::list_tools,
|
||||
mcp_server::call_tool,
|
||||
mcp_server::set_spray_status,
|
||||
mcp_server::get_spray_status
|
||||
],
|
||||
);
|
||||
|
||||
println!("MCP server running on http://127.0.0.1:3000");
|
||||
|
||||
// Start the server
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
rocket.launch().await.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
181
src-tauri/src/mcp_server.rs
Normal file
181
src-tauri/src/mcp_server.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use rocket::{State, serde::{Deserialize, Serialize, json::Json}};
|
||||
use serde_json::{json, Value};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tauri::AppHandle;
|
||||
|
||||
use crate::spary::spary_switch;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub app_handle: AppHandle,
|
||||
pub spray_status: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct InitializeResponse {
|
||||
protocol_version: String,
|
||||
server_info: ServerInfo,
|
||||
capabilities: Capabilities,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct ServerInfo {
|
||||
name: String,
|
||||
version: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct Capabilities {
|
||||
experimental: Option<Value>,
|
||||
tools: Option<Value>,
|
||||
}
|
||||
|
||||
#[rocket::post("/mcp/initialize", data = "<_data>")]
|
||||
pub async fn initialize(_state: &State<AppState>, _data: Json<Value>) -> Json<InitializeResponse> {
|
||||
Json(InitializeResponse {
|
||||
protocol_version: "1.0".to_string(),
|
||||
server_info: ServerInfo {
|
||||
name: "spary-mcp-server".to_string(),
|
||||
version: "0.1.0".to_string(),
|
||||
},
|
||||
capabilities: Capabilities {
|
||||
experimental: None,
|
||||
tools: Some(json!({
|
||||
"list": true,
|
||||
"call": true
|
||||
})),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[rocket::get("/mcp/capabilities")]
|
||||
pub async fn get_capabilities(_state: &State<AppState>) -> Json<Value> {
|
||||
Json(json!({
|
||||
"tools": {
|
||||
"list": true,
|
||||
"call": true
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
struct Tool {
|
||||
name: String,
|
||||
description: String,
|
||||
input_schema: Value,
|
||||
}
|
||||
|
||||
#[rocket::get("/mcp/tools/list")]
|
||||
pub async fn list_tools(_state: &State<AppState>) -> Json<Value> {
|
||||
let tools = vec![
|
||||
Tool {
|
||||
name: "spray_toggle".to_string(),
|
||||
description: "Toggle the spray functionality on or off".to_string(),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to turn spray on (true) or off (false)"
|
||||
}
|
||||
},
|
||||
"required": ["status"]
|
||||
}),
|
||||
}
|
||||
];
|
||||
|
||||
Json(json!({ "tools": tools }))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct ToolCallRequest {
|
||||
name: String,
|
||||
arguments: Value,
|
||||
}
|
||||
|
||||
#[rocket::post("/mcp/tools/call", data = "<payload>")]
|
||||
pub async fn call_tool(
|
||||
state: &State<AppState>,
|
||||
payload: Json<ToolCallRequest>,
|
||||
) -> Result<Json<Value>, rocket::http::Status> {
|
||||
match payload.name.as_str() {
|
||||
"spray_toggle" => {
|
||||
if let Some(status) = payload.arguments.get("status").and_then(|v| v.as_bool()) {
|
||||
// Call the existing spray_switch function
|
||||
spary_switch(status);
|
||||
|
||||
// Update the spray status in the shared state
|
||||
{
|
||||
let mut spray_status = state.spray_status.lock().await;
|
||||
*spray_status = status;
|
||||
}
|
||||
|
||||
Ok(Json(json!({
|
||||
"result": {
|
||||
"success": true,
|
||||
"message": format!("Spray toggled to {}", status)
|
||||
}
|
||||
})))
|
||||
} else {
|
||||
Err(rocket::http::Status::BadRequest)
|
||||
}
|
||||
}
|
||||
_ => Err(rocket::http::Status::NotFound),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct SprayRequest {
|
||||
status: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct SprayResponse {
|
||||
success: bool,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[rocket::post("/v1/spray", data = "<payload>")]
|
||||
pub async fn set_spray_status(
|
||||
state: &State<AppState>,
|
||||
payload: Json<SprayRequest>,
|
||||
) -> Json<SprayResponse> {
|
||||
// Call the existing spray_switch function
|
||||
spary_switch(payload.status);
|
||||
|
||||
// Update the spray status in the shared state
|
||||
{
|
||||
let mut spray_status = state.spray_status.lock().await;
|
||||
*spray_status = payload.status;
|
||||
}
|
||||
|
||||
Json(SprayResponse {
|
||||
success: true,
|
||||
message: format!("Spray toggled to {}", payload.status),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct SprayStatusResponse {
|
||||
status: bool,
|
||||
}
|
||||
|
||||
#[rocket::get("/v1/spray")]
|
||||
pub async fn get_spray_status(
|
||||
state: &State<AppState>,
|
||||
) -> Json<SprayStatusResponse> {
|
||||
let spray_status = state.spray_status.lock().await;
|
||||
Json(SprayStatusResponse {
|
||||
status: *spray_status,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { useI18n } from 'vue-i18n';
|
||||
import {Group, groupRepository} from "@/entities/group.ts";
|
||||
import {nodeRepository} from "@/entities/node.ts";
|
||||
import {notify} from "@/components/notify/notifyStore.ts";
|
||||
import {ConfigurationSchema} from "@/utils/core/configurator/schema/schema.ts";
|
||||
import {CoreTypes} from "@/utils/core/CoreDef.ts";
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -11,6 +13,7 @@ const props = defineProps<{ groupId: string }>()
|
||||
|
||||
const allGroups = ref<Group[]>([])
|
||||
const selectedGroupId = ref<number | null>(null)
|
||||
const configurationSchema= ref<ConfigurationSchema | null>(null)
|
||||
|
||||
const loadGroups = async () => {
|
||||
allGroups.value = await groupRepository.findAll()
|
||||
@@ -62,10 +65,14 @@ async function addNode() {
|
||||
:label="$t('addNode.nodeAlias')"
|
||||
></v-text-field>
|
||||
|
||||
<v-textarea
|
||||
v-model="nodeArguments"
|
||||
:label="$t('addNode.arguments')"
|
||||
></v-textarea>
|
||||
<v-select
|
||||
v-model="configurationSchema"
|
||||
:label="$t('addNode.nodeType')"
|
||||
:items="CoreTypes"
|
||||
item-title="name"
|
||||
variant="solo">
|
||||
</v-select>
|
||||
|
||||
<v-btn
|
||||
class="mt-2"
|
||||
type="submit"
|
||||
|
||||
14
src/components/nodeEdit/nodeConfigurator.vue
Normal file
14
src/components/nodeEdit/nodeConfigurator.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import {ConfigurationSchema} from "@/utils/core/configurator/schema/schema.ts";
|
||||
|
||||
const props = defineProps<{ configurationSchema: ConfigurationSchema }>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
8
src/utils/core/CoreDef.ts
Normal file
8
src/utils/core/CoreDef.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {XraySchema} from "@/utils/core/configurator/schema/xray.schema.ts";
|
||||
import {ConfigurationSchema} from "@/utils/core/configurator/schema/schema.ts";
|
||||
import {V2flySchema} from "@/utils/core/configurator/schema/v2fly.schema.ts";
|
||||
|
||||
export const CoreTypes = [
|
||||
new ConfigurationSchema("xray", XraySchema),
|
||||
new ConfigurationSchema("v2fly", V2flySchema),
|
||||
]
|
||||
9
src/utils/core/configurator/schema/schema.ts
Normal file
9
src/utils/core/configurator/schema/schema.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {ZodObject} from "zod";
|
||||
|
||||
export class ConfigurationSchema {
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
public readonly schema: ZodObject<any>
|
||||
) {
|
||||
}
|
||||
}
|
||||
245
src/utils/core/configurator/schema/v2fly.schema.ts
Normal file
245
src/utils/core/configurator/schema/v2fly.schema.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import {z} from "zod";
|
||||
|
||||
|
||||
export const V2flySchema = z.object({
|
||||
log: z.object({
|
||||
access: z.string().optional(),
|
||||
error: z.string().optional(),
|
||||
loglevel: z.enum(["debug", "info", "warning", "error", "none"]).optional(),
|
||||
dnsLog: z.boolean().optional(),
|
||||
maskAddress: z.enum(["quarter", "half", "full"]).optional()
|
||||
}).optional(),
|
||||
api: z.object({
|
||||
tag: z.string().optional(),
|
||||
listen: z.string().optional(),
|
||||
services: z.array(z.string()).optional()
|
||||
}).optional(),
|
||||
dns: z.object({
|
||||
hosts: z.record(z.string(), z.union([
|
||||
z.string(),
|
||||
z.array(z.string())
|
||||
])).optional(),
|
||||
servers: z.array(z.union([
|
||||
z.string(),
|
||||
z.object({
|
||||
address: z.string(),
|
||||
port: z.number().optional(),
|
||||
domains: z.array(z.string()).optional(),
|
||||
expectedIPs: z.array(z.string()).optional(),
|
||||
unexpectedIPs: z.array(z.string()).optional(),
|
||||
skipFallback: z.boolean().optional(),
|
||||
clientIP: z.string().optional(),
|
||||
queryStrategy: z.enum(["UseIP", "UseIPv4", "UseIPv6", "UseSystem"]).optional(),
|
||||
tag: z.string().optional(),
|
||||
timeoutMs: z.number().optional(),
|
||||
disableCache: z.boolean().optional(),
|
||||
finalQuery: z.boolean().optional()
|
||||
})
|
||||
])).optional(),
|
||||
clientIp: z.string().optional(),
|
||||
queryStrategy: z.enum(["UseIP", "UseIPv4", "UseIPv6", "UseSystem"]).optional(),
|
||||
disableCache: z.boolean().optional(),
|
||||
disableFallback: z.boolean().optional(),
|
||||
disableFallbackIfMatch: z.boolean().optional(),
|
||||
useSystemHosts: z.boolean().optional(),
|
||||
tag: z.string().optional()
|
||||
}).optional(),
|
||||
routing: z.object({
|
||||
domainStrategy: z.enum(["AsIs", "IPIfNonMatch", "IPOnDemand"]).optional(),
|
||||
domainMatcher: z.enum(["hybrid", "linear"]).optional(),
|
||||
rules: z.array(z.object({
|
||||
domainMatcher: z.enum(["hybrid", "linear"]).optional(),
|
||||
type: z.literal("field").optional(),
|
||||
domain: z.array(z.string()).optional(),
|
||||
ip: z.array(z.string()).optional(),
|
||||
port: z.union([z.number(), z.string()]).optional(),
|
||||
sourcePort: z.union([z.number(), z.string()]).optional(),
|
||||
network: z.enum(["tcp", "udp", "tcp,udp"]).optional(),
|
||||
source: z.array(z.string()).optional(),
|
||||
user: z.array(z.string()).optional(),
|
||||
inboundTag: z.array(z.string()).optional(),
|
||||
protocol: z.array(z.enum(["http", "tls", "bittorrent"])).optional(),
|
||||
attrs: z.record(z.string(), z.string()).optional(),
|
||||
outboundTag: z.string().optional(),
|
||||
balancerTag: z.string().optional()
|
||||
})).optional(),
|
||||
balancers: z.array(z.object({
|
||||
tag: z.string().optional(),
|
||||
selector: z.array(z.string()).optional()
|
||||
})).optional()
|
||||
}).optional(),
|
||||
policy: z.object({
|
||||
levels: z.record(z.string(), z.object({
|
||||
handshake: z.number().optional(),
|
||||
connIdle: z.number().optional(),
|
||||
uplinkOnly: z.number().optional(),
|
||||
downlinkOnly: z.number().optional(),
|
||||
statsUserUplink: z.boolean().optional(),
|
||||
statsUserDownlink: z.boolean().optional(),
|
||||
statsUserOnline: z.boolean().optional(),
|
||||
bufferSize: z.number().optional()
|
||||
})).optional(),
|
||||
system: z.object({
|
||||
statsInboundUplink: z.boolean().optional(),
|
||||
statsInboundDownlink: z.boolean().optional(),
|
||||
statsOutboundUplink: z.boolean().optional(),
|
||||
statsOutboundDownlink: z.boolean().optional()
|
||||
}).optional()
|
||||
}).optional(),
|
||||
inbounds: z.array(z.object({
|
||||
listen: z.string().optional(),
|
||||
port: z.union([z.number(), z.string()]).optional(),
|
||||
protocol: z.enum(["dokodemo-door", "http", "shadowsocks", "socks", "vless", "vmess", "trojan", "wireguard"]).optional(),
|
||||
settings: z.record(z.string(), z.any()).optional(),
|
||||
streamSettings: z.record(z.string(), z.any()).optional(),
|
||||
tag: z.string().optional(),
|
||||
sniffing: z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
destOverride: z.array(z.enum(["http", "tls", "quic", "fakedns"])).optional(),
|
||||
metadataOnly: z.boolean().optional(),
|
||||
domainsExcluded: z.array(z.string()).optional(),
|
||||
routeOnly: z.boolean().optional()
|
||||
}).optional(),
|
||||
allocate: z.object({
|
||||
strategy: z.enum(["always", "random"]).optional(),
|
||||
refresh: z.number().optional(),
|
||||
concurrency: z.number().optional()
|
||||
}).optional()
|
||||
})).optional(),
|
||||
outbounds: z.array(z.object({
|
||||
sendThrough: z.string().optional(),
|
||||
protocol: z.string().optional(),
|
||||
settings: z.record(z.string(), z.any()).optional(),
|
||||
tag: z.string().optional(),
|
||||
streamSettings: z.record(z.string(), z.any()).optional(),
|
||||
proxySettings: z.object({
|
||||
tag: z.string().optional()
|
||||
}).optional(),
|
||||
mux: z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
concurrency: z.number().optional(),
|
||||
xudpConcurrency: z.number().optional(),
|
||||
xudpProxyUDP443: z.enum(["reject", "allow", "skip"]).optional()
|
||||
}).optional(),
|
||||
targetStrategy: z.enum([
|
||||
"AsIs", "UseIP", "UseIPv6v4", "UseIPv6", "UseIPv4v6", "UseIPv4",
|
||||
"ForceIP", "ForceIPv6v4", "ForceIPv6", "ForceIPv4v6", "ForceIPv4"
|
||||
]).optional()
|
||||
})).optional(),
|
||||
transport: z.object({
|
||||
network: z.enum(["raw", "xhttp", "kcp", "grpc", "ws", "httpupgrade"]).optional(),
|
||||
security: z.enum(["none", "tls", "reality"]).optional(),
|
||||
tlsSettings: z.object({
|
||||
serverName: z.string().optional(),
|
||||
rejectUnknownSni: z.boolean().optional(),
|
||||
verifyPeerCertInNames: z.array(z.string()).optional(),
|
||||
allowInsecure: z.boolean().optional(),
|
||||
alpn: z.array(z.string()).optional(),
|
||||
minVersion: z.string().optional(),
|
||||
maxVersion: z.string().optional(),
|
||||
cipherSuites: z.string().optional(),
|
||||
certificates: z.array(z.object({
|
||||
ocspStapling: z.number().optional(),
|
||||
oneTimeLoading: z.boolean().optional(),
|
||||
usage: z.enum(["encipherment", "verify", "issue"]).optional(),
|
||||
buildChain: z.boolean().optional(),
|
||||
certificateFile: z.string().optional(),
|
||||
keyFile: z.string().optional(),
|
||||
certificate: z.array(z.string()).optional()
|
||||
})).optional(),
|
||||
disableSystemRoot: z.boolean().optional(),
|
||||
enableSessionResumption: z.boolean().optional(),
|
||||
fingerprint: z.string().optional(),
|
||||
pinnedPeerCertificateChainSha256: z.array(z.string()).optional(),
|
||||
masterKeyLog: z.string().optional()
|
||||
}).optional(),
|
||||
realitySettings: z.object({
|
||||
show: z.boolean().optional(),
|
||||
dest: z.string().optional(),
|
||||
xver: z.number().optional(),
|
||||
serverNames: z.array(z.string()).optional(),
|
||||
privateKey: z.string().optional(),
|
||||
minClientVer: z.string().optional(),
|
||||
maxClientVer: z.string().optional(),
|
||||
maxTimeDiff: z.number().optional(),
|
||||
shortIds: z.array(z.string()).optional(),
|
||||
fingerprint: z.string().optional(),
|
||||
serverName: z.string().optional(),
|
||||
publicKey: z.string().optional(),
|
||||
shortId: z.string().optional(),
|
||||
spiderX: z.string().optional()
|
||||
}).optional(),
|
||||
rawSettings: z.object({}).optional(),
|
||||
xhttpSettings: z.object({}).optional(),
|
||||
kcpSettings: z.object({}).optional(),
|
||||
grpcSettings: z.object({}).optional(),
|
||||
wsSettings: z.object({}).optional(),
|
||||
httpupgradeSettings: z.object({}).optional(),
|
||||
sockopt: z.object({
|
||||
mark: z.number().optional(),
|
||||
tcpMaxSeg: z.number().optional(),
|
||||
tcpFastOpen: z.boolean().optional(),
|
||||
tproxy: z.enum(["off", "redirect", "tproxy"]).optional(),
|
||||
domainStrategy: z.enum(["AsIs", "UseIP", "UseIPv4", "UseIPv6"]).optional(),
|
||||
happyEyeballs: z.object({
|
||||
tryDelayMs: z.number().optional()
|
||||
}).optional(),
|
||||
dialerProxy: z.string().optional(),
|
||||
acceptProxyProtocol: z.boolean().optional(),
|
||||
tcpKeepAliveInterval: z.number().optional(),
|
||||
tcpKeepAliveIdle: z.number().optional(),
|
||||
tcpUserTimeout: z.number().optional(),
|
||||
tcpCongestion: z.string().optional(),
|
||||
interface: z.string().optional(),
|
||||
v6only: z.boolean().optional(),
|
||||
tcpWindowClamp: z.number().optional(),
|
||||
tcpMptcp: z.boolean().optional(),
|
||||
tcpNoDelay: z.boolean().optional()
|
||||
}).optional()
|
||||
}).optional(),
|
||||
stats: z.object({
|
||||
// StatsObject currently doesn't require any parameters
|
||||
// Internal statistics will be enabled as long as this object exists
|
||||
}).optional(),
|
||||
reverse: z.object({
|
||||
bridges: z.array(z.object({
|
||||
tag: z.string().optional(),
|
||||
domain: z.string().optional()
|
||||
})).optional(),
|
||||
portals: z.array(z.object({
|
||||
tag: z.string().optional(),
|
||||
domain: z.string().optional()
|
||||
})).optional()
|
||||
}).optional(),
|
||||
fakedns: z.union([
|
||||
z.object({
|
||||
ipPool: z.string().optional(),
|
||||
poolSize: z.number().optional()
|
||||
}).optional(),
|
||||
z.array(z.object({
|
||||
ipPool: z.string(),
|
||||
poolSize: z.number().optional()
|
||||
}))
|
||||
]).optional(),
|
||||
metrics: z.object({
|
||||
tag: z.string().optional()
|
||||
}).optional(),
|
||||
observatory: z.object({
|
||||
subjectSelector: z.array(z.string()).optional(),
|
||||
probeUrl: z.string().optional(),
|
||||
probeInterval: z.string().optional(),
|
||||
enableConcurrency: z.boolean().optional()
|
||||
}).optional(),
|
||||
burstObservatory: z.object({
|
||||
subjectSelector: z.array(z.string()).optional(),
|
||||
pingConfig: z.object({
|
||||
destination: z.string().optional(),
|
||||
connectivity: z.string().optional(),
|
||||
interval: z.string().optional(),
|
||||
sampling: z.number().optional(),
|
||||
timeout: z.string().optional()
|
||||
}).optional()
|
||||
}).optional()
|
||||
})
|
||||
|
||||
export type v2flyConfig = z.infer<typeof V2flySchema>;
|
||||
245
src/utils/core/configurator/schema/xray.schema.ts
Normal file
245
src/utils/core/configurator/schema/xray.schema.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import {z} from "zod";
|
||||
|
||||
|
||||
export const XraySchema = z.object({
|
||||
log: z.object({
|
||||
access: z.string().optional(),
|
||||
error: z.string().optional(),
|
||||
loglevel: z.enum(["debug", "info", "warning", "error", "none"]).optional(),
|
||||
dnsLog: z.boolean().optional(),
|
||||
maskAddress: z.enum(["quarter", "half", "full"]).optional()
|
||||
}).optional(),
|
||||
api: z.object({
|
||||
tag: z.string().optional(),
|
||||
listen: z.string().optional(),
|
||||
services: z.array(z.string()).optional()
|
||||
}).optional(),
|
||||
dns: z.object({
|
||||
hosts: z.record(z.string(), z.union([
|
||||
z.string(),
|
||||
z.array(z.string())
|
||||
])).optional(),
|
||||
servers: z.array(z.union([
|
||||
z.string(),
|
||||
z.object({
|
||||
address: z.string(),
|
||||
port: z.number().optional(),
|
||||
domains: z.array(z.string()).optional(),
|
||||
expectedIPs: z.array(z.string()).optional(),
|
||||
unexpectedIPs: z.array(z.string()).optional(),
|
||||
skipFallback: z.boolean().optional(),
|
||||
clientIP: z.string().optional(),
|
||||
queryStrategy: z.enum(["UseIP", "UseIPv4", "UseIPv6", "UseSystem"]).optional(),
|
||||
tag: z.string().optional(),
|
||||
timeoutMs: z.number().optional(),
|
||||
disableCache: z.boolean().optional(),
|
||||
finalQuery: z.boolean().optional()
|
||||
})
|
||||
])).optional(),
|
||||
clientIp: z.string().optional(),
|
||||
queryStrategy: z.enum(["UseIP", "UseIPv4", "UseIPv6", "UseSystem"]).optional(),
|
||||
disableCache: z.boolean().optional(),
|
||||
disableFallback: z.boolean().optional(),
|
||||
disableFallbackIfMatch: z.boolean().optional(),
|
||||
useSystemHosts: z.boolean().optional(),
|
||||
tag: z.string().optional()
|
||||
}).optional(),
|
||||
routing: z.object({
|
||||
domainStrategy: z.enum(["AsIs", "IPIfNonMatch", "IPOnDemand"]).optional(),
|
||||
domainMatcher: z.enum(["hybrid", "linear"]).optional(),
|
||||
rules: z.array(z.object({
|
||||
domainMatcher: z.enum(["hybrid", "linear"]).optional(),
|
||||
type: z.literal("field").optional(),
|
||||
domain: z.array(z.string()).optional(),
|
||||
ip: z.array(z.string()).optional(),
|
||||
port: z.union([z.number(), z.string()]).optional(),
|
||||
sourcePort: z.union([z.number(), z.string()]).optional(),
|
||||
network: z.enum(["tcp", "udp", "tcp,udp"]).optional(),
|
||||
source: z.array(z.string()).optional(),
|
||||
user: z.array(z.string()).optional(),
|
||||
inboundTag: z.array(z.string()).optional(),
|
||||
protocol: z.array(z.enum(["http", "tls", "bittorrent"])).optional(),
|
||||
attrs: z.record(z.string(), z.string()).optional(),
|
||||
outboundTag: z.string().optional(),
|
||||
balancerTag: z.string().optional()
|
||||
})).optional(),
|
||||
balancers: z.array(z.object({
|
||||
tag: z.string().optional(),
|
||||
selector: z.array(z.string()).optional()
|
||||
})).optional()
|
||||
}).optional(),
|
||||
policy: z.object({
|
||||
levels: z.record(z.string(), z.object({
|
||||
handshake: z.number().optional(),
|
||||
connIdle: z.number().optional(),
|
||||
uplinkOnly: z.number().optional(),
|
||||
downlinkOnly: z.number().optional(),
|
||||
statsUserUplink: z.boolean().optional(),
|
||||
statsUserDownlink: z.boolean().optional(),
|
||||
statsUserOnline: z.boolean().optional(),
|
||||
bufferSize: z.number().optional()
|
||||
})).optional(),
|
||||
system: z.object({
|
||||
statsInboundUplink: z.boolean().optional(),
|
||||
statsInboundDownlink: z.boolean().optional(),
|
||||
statsOutboundUplink: z.boolean().optional(),
|
||||
statsOutboundDownlink: z.boolean().optional()
|
||||
}).optional()
|
||||
}).optional(),
|
||||
inbounds: z.array(z.object({
|
||||
listen: z.string().optional(),
|
||||
port: z.union([z.number(), z.string()]).optional(),
|
||||
protocol: z.enum(["dokodemo-door", "http", "shadowsocks", "socks", "vless", "vmess", "trojan", "wireguard"]).optional(),
|
||||
settings: z.record(z.string(), z.any()).optional(),
|
||||
streamSettings: z.record(z.string(), z.any()).optional(),
|
||||
tag: z.string().optional(),
|
||||
sniffing: z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
destOverride: z.array(z.enum(["http", "tls", "quic", "fakedns"])).optional(),
|
||||
metadataOnly: z.boolean().optional(),
|
||||
domainsExcluded: z.array(z.string()).optional(),
|
||||
routeOnly: z.boolean().optional()
|
||||
}).optional(),
|
||||
allocate: z.object({
|
||||
strategy: z.enum(["always", "random"]).optional(),
|
||||
refresh: z.number().optional(),
|
||||
concurrency: z.number().optional()
|
||||
}).optional()
|
||||
})).optional(),
|
||||
outbounds: z.array(z.object({
|
||||
sendThrough: z.string().optional(),
|
||||
protocol: z.string().optional(),
|
||||
settings: z.record(z.string(), z.any()).optional(),
|
||||
tag: z.string().optional(),
|
||||
streamSettings: z.record(z.string(), z.any()).optional(),
|
||||
proxySettings: z.object({
|
||||
tag: z.string().optional()
|
||||
}).optional(),
|
||||
mux: z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
concurrency: z.number().optional(),
|
||||
xudpConcurrency: z.number().optional(),
|
||||
xudpProxyUDP443: z.enum(["reject", "allow", "skip"]).optional()
|
||||
}).optional(),
|
||||
targetStrategy: z.enum([
|
||||
"AsIs", "UseIP", "UseIPv6v4", "UseIPv6", "UseIPv4v6", "UseIPv4",
|
||||
"ForceIP", "ForceIPv6v4", "ForceIPv6", "ForceIPv4v6", "ForceIPv4"
|
||||
]).optional()
|
||||
})).optional(),
|
||||
transport: z.object({
|
||||
network: z.enum(["raw", "xhttp", "kcp", "grpc", "ws", "httpupgrade"]).optional(),
|
||||
security: z.enum(["none", "tls", "reality"]).optional(),
|
||||
tlsSettings: z.object({
|
||||
serverName: z.string().optional(),
|
||||
rejectUnknownSni: z.boolean().optional(),
|
||||
verifyPeerCertInNames: z.array(z.string()).optional(),
|
||||
allowInsecure: z.boolean().optional(),
|
||||
alpn: z.array(z.string()).optional(),
|
||||
minVersion: z.string().optional(),
|
||||
maxVersion: z.string().optional(),
|
||||
cipherSuites: z.string().optional(),
|
||||
certificates: z.array(z.object({
|
||||
ocspStapling: z.number().optional(),
|
||||
oneTimeLoading: z.boolean().optional(),
|
||||
usage: z.enum(["encipherment", "verify", "issue"]).optional(),
|
||||
buildChain: z.boolean().optional(),
|
||||
certificateFile: z.string().optional(),
|
||||
keyFile: z.string().optional(),
|
||||
certificate: z.array(z.string()).optional()
|
||||
})).optional(),
|
||||
disableSystemRoot: z.boolean().optional(),
|
||||
enableSessionResumption: z.boolean().optional(),
|
||||
fingerprint: z.string().optional(),
|
||||
pinnedPeerCertificateChainSha256: z.array(z.string()).optional(),
|
||||
masterKeyLog: z.string().optional()
|
||||
}).optional(),
|
||||
realitySettings: z.object({
|
||||
show: z.boolean().optional(),
|
||||
dest: z.string().optional(),
|
||||
xver: z.number().optional(),
|
||||
serverNames: z.array(z.string()).optional(),
|
||||
privateKey: z.string().optional(),
|
||||
minClientVer: z.string().optional(),
|
||||
maxClientVer: z.string().optional(),
|
||||
maxTimeDiff: z.number().optional(),
|
||||
shortIds: z.array(z.string()).optional(),
|
||||
fingerprint: z.string().optional(),
|
||||
serverName: z.string().optional(),
|
||||
publicKey: z.string().optional(),
|
||||
shortId: z.string().optional(),
|
||||
spiderX: z.string().optional()
|
||||
}).optional(),
|
||||
rawSettings: z.object({}).optional(),
|
||||
xhttpSettings: z.object({}).optional(),
|
||||
kcpSettings: z.object({}).optional(),
|
||||
grpcSettings: z.object({}).optional(),
|
||||
wsSettings: z.object({}).optional(),
|
||||
httpupgradeSettings: z.object({}).optional(),
|
||||
sockopt: z.object({
|
||||
mark: z.number().optional(),
|
||||
tcpMaxSeg: z.number().optional(),
|
||||
tcpFastOpen: z.boolean().optional(),
|
||||
tproxy: z.enum(["off", "redirect", "tproxy"]).optional(),
|
||||
domainStrategy: z.enum(["AsIs", "UseIP", "UseIPv4", "UseIPv6"]).optional(),
|
||||
happyEyeballs: z.object({
|
||||
tryDelayMs: z.number().optional()
|
||||
}).optional(),
|
||||
dialerProxy: z.string().optional(),
|
||||
acceptProxyProtocol: z.boolean().optional(),
|
||||
tcpKeepAliveInterval: z.number().optional(),
|
||||
tcpKeepAliveIdle: z.number().optional(),
|
||||
tcpUserTimeout: z.number().optional(),
|
||||
tcpCongestion: z.string().optional(),
|
||||
interface: z.string().optional(),
|
||||
v6only: z.boolean().optional(),
|
||||
tcpWindowClamp: z.number().optional(),
|
||||
tcpMptcp: z.boolean().optional(),
|
||||
tcpNoDelay: z.boolean().optional()
|
||||
}).optional()
|
||||
}).optional(),
|
||||
stats: z.object({
|
||||
// StatsObject currently doesn't require any parameters
|
||||
// Internal statistics will be enabled as long as this object exists
|
||||
}).optional(),
|
||||
reverse: z.object({
|
||||
bridges: z.array(z.object({
|
||||
tag: z.string().optional(),
|
||||
domain: z.string().optional()
|
||||
})).optional(),
|
||||
portals: z.array(z.object({
|
||||
tag: z.string().optional(),
|
||||
domain: z.string().optional()
|
||||
})).optional()
|
||||
}).optional(),
|
||||
fakedns: z.union([
|
||||
z.object({
|
||||
ipPool: z.string().optional(),
|
||||
poolSize: z.number().optional()
|
||||
}).optional(),
|
||||
z.array(z.object({
|
||||
ipPool: z.string(),
|
||||
poolSize: z.number().optional()
|
||||
}))
|
||||
]).optional(),
|
||||
metrics: z.object({
|
||||
tag: z.string().optional()
|
||||
}).optional(),
|
||||
observatory: z.object({
|
||||
subjectSelector: z.array(z.string()).optional(),
|
||||
probeUrl: z.string().optional(),
|
||||
probeInterval: z.string().optional(),
|
||||
enableConcurrency: z.boolean().optional()
|
||||
}).optional(),
|
||||
burstObservatory: z.object({
|
||||
subjectSelector: z.array(z.string()).optional(),
|
||||
pingConfig: z.object({
|
||||
destination: z.string().optional(),
|
||||
connectivity: z.string().optional(),
|
||||
interval: z.string().optional(),
|
||||
sampling: z.number().optional(),
|
||||
timeout: z.string().optional()
|
||||
}).optional()
|
||||
}).optional()
|
||||
})
|
||||
|
||||
export type xrayConfig = z.infer<typeof XraySchema>;
|
||||
Reference in New Issue
Block a user